The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
theoryforge treats a scientific theory as a versioned,
machine-checkable object. This vignette walks through the core loop of
building a theory, checking its rigour and diagramming it (see the package
overview for the R/Python parity guarantee).
A theory is built incrementally with the BUILDING-mode verbs. Each verb appends to the theory and records a provenance entry, and each returns the theory, so the steps compose with the native pipe.
theory <- tf_theory("panic-network", "A network theory of panic") |>
tf_add_construct("c_arousal", "Physiological arousal",
"Bodily activation in response to a stressor.",
measurement = "heart rate variability",
boundary_conditions = "awake adults") |>
tf_add_construct("c_threat", "Perceived threat",
"Appraised danger of bodily sensations.",
measurement = "self-report appraisal scale",
boundary_conditions = "awake adults") |>
tf_add_proposition(
"p1", "c_arousal", "c_threat", "causes",
mechanism = "Activation raises the salience of threat cues."
) |>
tf_add_prediction("h1", "Arousal raises threat appraisal by a fixed amount.",
"point", derives_from = "p1")
isTRUE(tf_validate(theory)) # structural checks: required fields and enums[1] TRUE
# also checks referential integrity of ids and cross-references
isTRUE(tf_validate(theory, full = TRUE))[1] TRUE
tf_validate() returns TRUE invisibly on
success and stops with a message listing every problem otherwise, which
is why the calls above are wrapped in isTRUE() to show the
result. With full = TRUE it additionally checks referential
integrity, that ids are unique and every cross-reference points to a
declared id. It does not require any optional dependency.
The failure path is the more informative one. Pointing a prediction
at a proposition that was never declared leaves the structural pass
untouched, since every required field is still present and well formed,
so only full = TRUE reports the problem.
broken <- theory
broken$predictions[[1]]$derives_from <- "p_missing"
tf_validate(broken, full = TRUE)Error:
! invalid theory object: prediction[0] derives_from 'p_missing' is not a known proposition
The building vocabulary has two further verbs.
tf_add_assumption() records an auxiliary assumption, and
tf_set_formal_model() attaches a formal-model reference.
Applied to a copy of the theory, they leave the original untouched.
extended <- theory |>
tf_add_assumption("a1", "Arousal is measured at rest.", added_for = "h1") |>
tf_set_formal_model("sem", spec_ref = "panic-sem.lavaan")Every verb appends a provenance entry, recording the step, the action and its detail, so the record of how a theory was built travels with the object.
step action detail
1 1 tf_theory panic-network
2 2 tf_add_construct c_arousal
3 3 tf_add_construct c_threat
4 4 tf_add_proposition p1
5 5 tf_add_prediction h1
6 6 tf_add_assumption a1
7 7 tf_set_formal_model sem
A theory can be written to and read back from disk. The format
follows the file extension (.json for JSON, otherwise
YAML).
path <- tempfile(fileext = ".yaml")
tf_write(theory, path)
roundtrip <- tf_read(path)
identical(roundtrip$id, theory$id)[1] TRUE
tf_check() runs the 12-item rigour checklist defined in
the vendored rigor_checklist.yaml.
[1] 77.6
[1] "pass"
[1] 0
[1] "falsifiability"
[1] "pass"
[1] 1
Render it as JSON (valid, pretty-printed) or as an HTML fragment. The JSON string is an artefact in its own right, shown here verbatim:
{
"theory_id": "panic-network",
"schema_version": "1.0",
"checklist_version": "1.0",
"maturity": "building",
"aggregate_score": 77.6,
"gate": "pass",
"n_blockers_failed": 0,
"items": [
{
"id": "falsifiability",
"status": "pass",
"score": 1,
"weight": 0.15,
"severity_if_fail": "blocker",
"citation": "Popper (1959); Bacharach (1989)"
},
{
"id": "precision",
"status": "pass",
"score": 1,
"weight": 0.1,
"severity_if_fail": "warning",
"citation": "Meehl (1967, 1990)"
},
{
"id": "risk_severity",
"status": "warn",
"score": 0,
"weight": 0.1,
"severity_if_fail": "warning",
"citation": "Mayo (2018); Meehl (1990)"
},
{
"id": "parsimony",
"status": "pass",
"score": 1,
"weight": 0.08,
"severity_if_fail": "warning",
"citation": "Forster & Sober (1994); Lakatos (1970)"
},
{
"id": "non_redundancy",
"status": "pass",
"score": 0.857,
"weight": 0.1,
"severity_if_fail": "warning",
"citation": "Kelley (1927); Le et al. (2010); Lawson & Robins (2021)"
},
{
"id": "construct_clarity",
"status": "pass",
"score": 1,
"weight": 0.08,
"severity_if_fail": "warning",
"citation": "Suddaby (2010); Cronbach & Meehl (1955); Flake & Fried (2020)"
},
{
"id": "scope",
"status": "pass",
"score": 1,
"weight": 0.06,
"severity_if_fail": "warning",
"citation": "Whetten (1989); Bacharach (1989)"
},
{
"id": "logical_why",
"status": "pass",
"score": 1,
"weight": 0.08,
"severity_if_fail": "warning",
"citation": "Sutton & Staw (1995); Whetten (1989)"
},
{
"id": "causal_testability",
"status": "pass",
"score": 1,
"weight": 0.06,
"severity_if_fail": "warning",
"citation": "Textor et al. (2016); Eronen & Bringmann (2021)"
},
{
"id": "diagnosticity",
"status": "warn",
"score": 0,
"weight": 0.06,
"severity_if_fail": "warning",
"citation": "Platt (1964); Fiedler (2017)"
},
{
"id": "formalisation",
"status": "warn",
"score": 0,
"weight": 0.05,
"severity_if_fail": "warning",
"citation": "Robinaugh et al. (2021); Guest & Martin (2021)"
},
{
"id": "derivation_chain",
"status": "pass",
"score": 1,
"weight": 0.08,
"severity_if_fail": "blocker",
"citation": "Scheel et al. (2021); Szollosi et al. (2020)"
}
]
}
The HTML fragment drops straight into a page and renders as a real table:
Aggregate score: 77.6 · gate: pass
| item | status | score | grounding |
|---|---|---|---|
| falsifiability | pass | 1.0 | Popper (1959); Bacharach (1989) |
| precision | pass | 1.0 | Meehl (1967, 1990) |
| risk_severity | warn | 0.0 | Mayo (2018); Meehl (1990) |
| parsimony | pass | 1.0 | Forster & Sober (1994); Lakatos (1970) |
| non_redundancy | pass | 0.857 | Kelley (1927); Le et al. (2010); Lawson & Robins (2021) |
| construct_clarity | pass | 1.0 | Suddaby (2010); Cronbach & Meehl (1955); Flake & Fried (2020) |
| scope | pass | 1.0 | Whetten (1989); Bacharach (1989) |
| logical_why | pass | 1.0 | Sutton & Staw (1995); Whetten (1989) |
| causal_testability | pass | 1.0 | Textor et al. (2016); Eronen & Bringmann (2021) |
| diagnosticity | warn | 0.0 | Platt (1964); Fiedler (2017) |
| formalisation | warn | 0.0 | Robinaugh et al. (2021); Guest & Martin (2021) |
| derivation_chain | pass | 1.0 | Scheel et al. (2021); Szollosi et al. (2020) |
The lexical redundancy screen reports the Jaccard similarity of every pair of construct definitions, sorted by descending similarity.
a b similarity flag
1 c_arousal c_threat 0.143 ok
The lexical screen is deterministic but shallow. When a semantic
comparison is wanted, tf_embedding_redundancy() takes any
embedder that maps a definition to a numeric vector and reports the
cosine similarity of each construct pair. It needs no optional
dependency, since the embedder is supplied by the caller. A toy
bag-of-words embedder stands in for a real language model here.
vocab <- c("bodily", "activation", "appraised", "danger", "salience")
embedder <- function(def) {
words <- strsplit(tolower(def), "[^a-z]+")[[1]]
vapply(vocab, function(w) sum(words == w), numeric(1))
}
tf_embedding_redundancy(theory, embedder) a b cosine flag
1 c_arousal c_threat 0.408248 ok
tf_diagram() emits deterministic intermediate
representations for several diagram types. The digraphs are Graphviz
DOT, and the causal DAG uses dagitty syntax.
digraph nomological_net {
graph [rankdir=LR, bgcolor="transparent", fontname="Helvetica", fontsize=11, pad="0.2", nodesep="0.3", ranksep="0.45"];
node [fontname="Helvetica", fontsize=11, shape=box, style="rounded,filled", color="#33567A", fillcolor="#F2F6F9", fontcolor="#12283A", penwidth=1.1, margin="0.16,0.1"];
edge [fontname="Helvetica", fontsize=10, color="#7B909F", fontcolor="#0F6E6E", arrowsize=0.7];
"c_arousal" [label="Physiological\narousal", fillcolor="#E4F1F1", color="#1E7B7B"];
"c_threat" [label="Perceived threat", fillcolor="#E4F1F1", color="#1E7B7B"];
"c_arousal" -> "c_threat" [label="causes"];
}
dag {
c_arousal -> c_threat
}
The DOT strings render with any Graphviz tool, and
tf_render_diagram() does this without leaving R: it returns
a DiagrammeR widget for the viewer and for R Markdown, or a standalone
SVG string with as = "svg". The packages it uses are
optional, in Suggests, so the deterministic core stays dependency-free.
Rendered, the nomological net above reads as a figure.
cat(
'<div class="tf-figure tf-diagram">',
tf_render_diagram(theory, "nomological_net", as = "svg"),
'</div>',
sep = ""
)The causal DAG is the one view this does not cover, since it emits
dagitty syntax rather than DOT. Paste it into a dagitty tool such as dagitty.net instead.
tf_implications() reads the same subgraph without leaving
R, checking it for acyclicity and returning the conditional
independencies it entails, which the Developing and testing article
works through.
tf_simulate() treats each construct as a state variable
and integrates the signed proposition network as a linear dynamical
system. The trajectory is deterministic:
[1] "c_arousal" "c_threat"
[1] 1 1
[1] 0.773781 1.181034
The two states separate immediately: arousal, which receives no incoming coupling, decays under the damping term, while perceived threat is pushed up by the positive coupling from arousal before the decay eventually takes over.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.