Getting Started

if(!requireNamespace("fabricatr", quietly = TRUE)) {
  install.packages("fabricatr")
}

library(CausalQueries)
library(dplyr)
library(knitr)

Make a model

Generating: To make a model you need to provide a DAG statement to make_model.
For instance

# examples of models
xy_model <- make_model("X -> Y")
iv_model <- make_model("Z -> X -> Y <-> X")

Graphing: Once you have made a model you can inspect the DAG:

plot(xy_model)

Simple summaries: You can access a simple summary using summary()

summary(xy_model)
#> 
#> Causal statement: 
#> X -> Y
#> 
#> Nodal types: 
#> $X
#> 0  1
#> 
#>   node position display interpretation
#> 1    X       NA      X0          X = 0
#> 2    X       NA      X1          X = 1
#> 
#> $Y
#> 00  10  01  11
#> 
#>   node position display interpretation
#> 1    Y        1   Y[*]*      Y | X = 0
#> 2    Y        2   Y*[*]      Y | X = 1
#> 
#> Number of types by node:
#> X Y 
#> 2 4 
#> 
#> Number of causal types:  8
#> 
#> Note: Model does not contain: posterior_distribution, stan_objects;
#> to include these objects use update_model()
#> 
#> Note: To pose causal queries of this model use query_model()

or you can examine model details using inspect().

Inspecting: The model has a set of parameters and a default distribution over these.

xy_model |> inspect("parameters_df") 
#> 
#> parameters_df
#> Mapping of model parameters to nodal types: 
#> 
#>   param_names: name of parameter
#>   node:        name of endogeneous node associated
#>                with the parameter
#>   gen:         partial causal ordering of the
#>                parameter's node
#>   param_set:   parameter groupings forming a simplex
#>   given:       if model has confounding gives
#>                conditioning nodal type
#>   param_value: parameter values
#>   priors:      hyperparameters of the prior
#>                Dirichlet distribution 
#> 
#>   param_names node gen param_set nodal_type given param_value priors
#> 1         X.0    X   1         X          0              0.50      1
#> 2         X.1    X   1         X          1              0.50      1
#> 3        Y.00    Y   2         Y         00              0.25      1
#> 4        Y.10    Y   2         Y         10              0.25      1
#> 5        Y.01    Y   2         Y         01              0.25      1
#> 6        Y.11    Y   2         Y         11              0.25      1

Tailoring: These features can be edited using set_restrictions, set_priors and set_parameters.

Here is an example of setting a monotonicity restriction (see ?set_restrictions for more):

iv_model <- 
  iv_model |> set_restrictions(decreasing('Z', 'X'))

Here is an example of setting priors (see ?set_priors for more):

iv_model <- 
  iv_model |> set_priors(distribution = "jeffreys")
#> Altering all parameters.

Simulation: Data can be drawn from a model like this:

data <- make_data(iv_model, n = 4) 

data |> kable()
Z X Y
0 0 0
0 0 0
1 1 0
1 1 0

Update the model

Updating: Update using update_model. You can pass all rstan arguments to update_model.

df <- 
  data.frame(X = rbinom(100, 1, .5)) |>
  mutate(Y = rbinom(100, 1, .25 + X*.5))

xy_model <- 
  xy_model |> 
  update_model(df, refresh = 0)

Inspecting: You can access the posterior distribution on model parameters directly thus:


xy_model |> grab("posterior_distribution") |> 
  head() |> kable()
X.0 X.1 Y.00 Y.10 Y.01 Y.11
0.6036868 0.3963132 0.0541182 0.3045143 0.5026073 0.1387602
0.5410469 0.4589531 0.1754604 0.1201301 0.4878345 0.2165750
0.5876625 0.4123375 0.0830585 0.1461882 0.4991199 0.2716334
0.4990042 0.5009958 0.1471899 0.1463460 0.5413010 0.1651632
0.6474818 0.3525182 0.0588975 0.2015043 0.5542513 0.1853468
0.5848194 0.4151806 0.1531487 0.1683044 0.4717736 0.2067733

where each row is a draw of parameters.

Query the model

Arbitrary queries

Querying: You ask arbitrary causal queries of the model.

Examples of unconditional queries:

xy_model |> 
  query_model("Y[X=1] > Y[X=0]", using = c("priors", "posteriors")) 
#> 
#> Causal queries generated by query_model (all at population level)
#> 
#> |query           |using      |  mean|    sd| cred.low| cred.high|
#> |:---------------|:----------|-----:|-----:|--------:|---------:|
#> |Y[X=1] > Y[X=0] |priors     | 0.247| 0.190|    0.009|     0.709|
#> |Y[X=1] > Y[X=0] |posteriors | 0.433| 0.117|    0.185|     0.630|

Examples of conditional queries:

xy_model |> 
  query_model("Y[X=1] > Y[X=0]", using = c("priors", "posteriors"),
              given = "X==1 & Y == 1") 
#> 
#> Causal queries generated by query_model (all at population level)
#> 
#> |query           |given         |using      |  mean|    sd| cred.low| cred.high|
#> |:---------------|:-------------|:----------|-----:|-----:|--------:|---------:|
#> |Y[X=1] > Y[X=0] |X==1 & Y == 1 |priors     | 0.507| 0.286|    0.028|     0.975|
#> |Y[X=1] > Y[X=0] |X==1 & Y == 1 |posteriors | 0.642| 0.169|    0.296|     0.948|

Queries can even be conditional on counterfactual quantities. Here the probability of a positive effect given some effect:

xy_model |> 
  query_model("Y[X=1] > Y[X=0]", using = c("priors", "posteriors"),
              given = "Y[X=1] != Y[X=0]") 
#> 
#> Causal queries generated by query_model (all at population level)
#> 
#> |query           |given            |using      |  mean|    sd| cred.low| cred.high|
#> |:---------------|:----------------|:----------|-----:|-----:|--------:|---------:|
#> |Y[X=1] > Y[X=0] |Y[X=1] != Y[X=0] |priors     | 0.503| 0.289|    0.024|     0.976|
#> |Y[X=1] > Y[X=0] |Y[X=1] != Y[X=0] |posteriors | 0.746| 0.105|    0.573|     0.970|

Output

Query output is ready for printing as tables, but can also be plotted, which is especially useful with batch requests:

batch_queries <- xy_model |> 
  query_model(queries = c("Y[X=1] - Y[X=0]", "Y[X=1] > Y[X=0]"), 
              using = c("priors", "posteriors"), 
              given = c(TRUE, "Y[X=1] != Y[X=0]"),
              expand_grid = TRUE) 

batch_queries |> kable(digits = 2, caption = "tabular output")
tabular output
query given using case_level mean sd cred.low cred.high
Y[X=1] - Y[X=0] - priors FALSE 0.00 0.32 -0.63 0.63
Y[X=1] - Y[X=0] - posteriors FALSE 0.27 0.09 0.08 0.44
Y[X=1] - Y[X=0] Y[X=1] != Y[X=0] priors FALSE 0.00 0.58 -0.95 0.95
Y[X=1] - Y[X=0] Y[X=1] != Y[X=0] posteriors FALSE 0.49 0.21 0.15 0.94
Y[X=1] > Y[X=0] - priors FALSE 0.25 0.19 0.01 0.71
Y[X=1] > Y[X=0] - posteriors FALSE 0.43 0.12 0.19 0.63
Y[X=1] > Y[X=0] Y[X=1] != Y[X=0] priors FALSE 0.50 0.29 0.03 0.98
Y[X=1] > Y[X=0] Y[X=1] != Y[X=0] posteriors FALSE 0.75 0.10 0.57 0.97
batch_queries |> plot()