Bayes Nets

Quick Introduction

Bayesian
Bayesian Network
Bayes Net
R
Python
Stan
rstan
cmdstanr
pystan
cmdstanpy
ggdag
dagitty
Published

August 25, 2025

An image of a wet spider web.

Photo by Michael Podger on Unsplash

I will be the first to state that I am not an expert in the field of conducting psychometric models, Bayesian networks, Bayesian analyses, but I have been struggling to find any blog posts about conducting a bayes net with latent variables that uses Stan. The purpose of this post is to walk through Stan and some bayes net terminology to get a basic understanding of some psychometric models conducted using Bayesian inference.

To get started, make sure you follow the detailed instructions on installing RStan or PyStan. I know if using Mac, make sure to also download Xcode so that Stan will work correctly. For posts having to do with Bayes Net, I will be doing my programming in either R or Python, while calling on Stan to conduct the Markov Chain Monte Carlo (MCMC) sampling.

For these models, I’ll be using the ECPE dataset from the edmdata package in R. This dataset has 28 items for 2,922 students with 3 latent attributes/variables. A 1 on for each item indicates whether a student got the item correct and a 0 indicates they got the item incorrect.

While I will be discussing bayes net through an educational measurement lens, bayes net can be used outside of education to show that individuals have skills that are not directly measured. Instead of items on an assessment, tasks that capture each skill can be assessed. Before walking through some bayes net terminology, it is important to note that this model is simply for educational purposes. Components of the psychometric models I will be writing about require expert opinion and domain knowledge. For example, bayes net models require expert opinions on the assignment of items to skills. Additionally, bayes net models require expert opinion on the priors for the lambda (\(\lambda\)) parameters.

Since there are different opinions on using different terms, I am going to stick to the following terms.

For this introductory post into bayes net, I thought it would be best to include some visuals for the models I’ll be testing. For both R and Python, I will be using the cmdstan version; whether it be cmdstanpy or cmdstanr. Finally, I will state that while this is introductory to a bayes net model, this post assumes that you have a basic understanding of Bayesian inference.

Setting up the Data

Code
library(tidyverse)
library(cmdstanr)
library(bayesplot)
library(posterior)
library(ggdag)
library(dagitty)

y <- read_csv("https://raw.githubusercontent.com/jpedroza1228/projects_portfolio_and_practice/refs/heads/main/projects/dcm/lcdm_py/ecpe_data.csv") |>
  janitor::clean_names()

q <- read_csv("https://raw.githubusercontent.com/jpedroza1228/projects_portfolio_and_practice/refs/heads/main/projects/dcm/lcdm_py/ecpe_qmatrix.csv") |>
  janitor::clean_names()

Below shows the first 10 rows for all the items in the dataset. Each row is a student and each cell is whether or not that student got the correct answer for each item.

Code
react_table <- function(data){
  reactable::reactable(
    {{data}},
    filterable = TRUE,
    sortable = TRUE,
    highlight = TRUE,
    searchable = TRUE
  )
  }

react_table(y |> head(10) |> rowid_to_column())

Q Matrix

Code
q |>
  rowid_to_column() |>
  react_table()

Okay, now on to the Q-matrix. For the ECPE dataset, the q-matrix has already been created from expert knowledge. Another option could be to try and learn the q-matrix from a structural learning algorithm, but that would have to be for another post.

Each row in the Q-matrix corresponds to an item with each column corresponding to a latent attribute. So for the first row, the 1 values for the first and second attributes indicates that item 1 measures attribute 1 and attribute 2, but not attribute 3.

The shape of both the data (y) and the Q-matrix is important for how we will load data into Stan. The dataset has 2922 rows (number of students) and 28 columns (number of items). The Q-matrix will also be included in the list for Stan, where we’ll take the number of attributes (3) and the values for each attribute in the Q-matrix.

Attribute Profile Matrix

In addition to the student data and the Q-matrix, I also am going to create an attribute profile matrix where each row is a latent class and the columns are the attributes. These classes correspond to whether the class has mastered each attribute. For instance, class number 1 will not master any attributes. I like to think of these as profiles of mastery. For instance, the class with no attributes mastered is the class profile 000.

I am going to create this matrix by creating every possible combination of skills, which will create every potential latent class. Then I will just add each row as a numbered class. Below is the final matrix created for 3 skills.

Code
skills <- 3
skill_combo <- rep(list(0:1), skills)
alpha <- expand.grid(skill_combo)

alpha <- alpha |>
  rename(
    att1 = Var1,
    att2 = Var2,
    att3 = Var3
  ) |>
  mutate(
    class = seq(1:nrow(alpha)),
    .before = att1
  )

alpha |> react_table()

So now we have everything to build our bayes net model. Before we get to that, I do want to visually show the models I will be creating in this series.

Models

The first model I will go over is a Log-Linear Cognitive Diagnostic Model (LCDM). This model is useful as a general diagnostic model that allows for different probabilities for each latent class. I’ll then make small changes to the model to include edges between the latent attributes in different ways.

Log-linear Cognitive Diagnostic Model (LCDM)

Code
lcdm_dag <- dagitty('dag {
Att1 [latent,pos="-0.533,-0.496"]
Att2 [latent,pos="-0.495,-0.497"]
Att3 [latent,pos="-0.457,-0.499"]
I1 [pos="-0.519,-0.522"]
I10 [pos="-0.535,-0.523"]
I11 [pos="-0.491,-0.512"]
I12 [pos="-0.489,-0.514"]
I13 [pos="-0.532,-0.522"]
I14 [pos="-0.528,-0.523"]
I15 [pos="-0.464,-0.531"]
I16 [pos="-0.487,-0.516"]
I17 [pos="-0.476,-0.521"]
I18 [pos="-0.461,-0.531"]
I19 [pos="-0.458,-0.531"]
I2 [pos="-0.516,-0.529"]
I20 [pos="-0.485,-0.519"]
I21 [pos="-0.483,-0.521"]
I22 [pos="-0.455,-0.531"]
I23 [pos="-0.510,-0.529"]
I24 [pos="-0.507,-0.529"]
I25 [pos="-0.525,-0.523"]
I26 [pos="-0.451,-0.531"]
I27 [pos="-0.522,-0.522"]
I28 [pos="-0.448,-0.531"]
I3 [pos="-0.496,-0.507"]
I4 [pos="-0.476,-0.530"]
I5 [pos="-0.473,-0.530"]
I6 [pos="-0.470,-0.531"]
I7 [pos="-0.494,-0.509"]
I8 [pos="-0.513,-0.529"]
I9 [pos="-0.467,-0.531"]
L1 [pos="-0.533,-0.490"]
L20 [pos="-0.498,-0.490"]
L21 [pos="-0.492,-0.490"]
L30 [pos="-0.461,-0.490"]
L31 [pos="-0.453,-0.491"]
Att1 -> I1
Att1 -> I10
Att1 -> I11
Att1 -> I12
Att1 -> I13
Att1 -> I14
Att1 -> I16
Att1 -> I20
Att1 -> I21
Att1 -> I25
Att1 -> I27
Att1 -> I3
Att1 -> I7
Att2 -> I1
Att2 -> I17
Att2 -> I2
Att2 -> I23
Att2 -> I24
Att2 -> I8
Att3 -> I11
Att3 -> I12
Att3 -> I15
Att3 -> I16
Att3 -> I17
Att3 -> I18
Att3 -> I19
Att3 -> I20
Att3 -> I21
Att3 -> I22
Att3 -> I26
Att3 -> I28
Att3 -> I3
Att3 -> I4
Att3 -> I5
Att3 -> I6
Att3 -> I7
Att3 -> I9
L1 -> Att1
L20 -> Att2
L21 -> Att2
L30 -> Att3
L31 -> Att3
}
')

ggdag(lcdm_dag) + theme_dag()

Linear LCDM-like Bayes Net

Code
linear_lcdm <- dagitty('dag {
Att1 [latent,pos="-0.533,-0.496"]
Att2 [latent,pos="-0.495,-0.497"]
Att3 [latent,pos="-0.457,-0.499"]
I1 [pos="-0.519,-0.522"]
I10 [pos="-0.535,-0.523"]
I11 [pos="-0.491,-0.512"]
I12 [pos="-0.489,-0.514"]
I13 [pos="-0.532,-0.522"]
I14 [pos="-0.528,-0.523"]
I15 [pos="-0.464,-0.531"]
I16 [pos="-0.487,-0.516"]
I17 [pos="-0.476,-0.521"]
I18 [pos="-0.461,-0.531"]
I19 [pos="-0.458,-0.531"]
I2 [pos="-0.516,-0.529"]
I20 [pos="-0.485,-0.519"]
I21 [pos="-0.483,-0.521"]
I22 [pos="-0.455,-0.531"]
I23 [pos="-0.510,-0.529"]
I24 [pos="-0.507,-0.529"]
I25 [pos="-0.525,-0.523"]
I26 [pos="-0.451,-0.531"]
I27 [pos="-0.522,-0.522"]
I28 [pos="-0.448,-0.531"]
I3 [pos="-0.496,-0.507"]
I4 [pos="-0.476,-0.530"]
I5 [pos="-0.473,-0.530"]
I6 [pos="-0.470,-0.531"]
I7 [pos="-0.494,-0.509"]
I8 [pos="-0.513,-0.529"]
I9 [pos="-0.467,-0.531"]
L1 [pos="-0.533,-0.490"]
L20 [pos="-0.498,-0.490"]
L21 [pos="-0.492,-0.490"]
L30 [pos="-0.461,-0.490"]
L31 [pos="-0.453,-0.491"]
Att1 -> Att2
Att1 -> I1
Att1 -> I10
Att1 -> I11
Att1 -> I12
Att1 -> I13
Att1 -> I14
Att1 -> I16
Att1 -> I20
Att1 -> I21
Att1 -> I25
Att1 -> I27
Att1 -> I3
Att1 -> I7
Att2 -> Att3
Att2 -> I1
Att2 -> I17
Att2 -> I2
Att2 -> I23
Att2 -> I24
Att2 -> I8
Att3 -> I11
Att3 -> I12
Att3 -> I15
Att3 -> I16
Att3 -> I17
Att3 -> I18
Att3 -> I19
Att3 -> I20
Att3 -> I21
Att3 -> I22
Att3 -> I26
Att3 -> I28
Att3 -> I3
Att3 -> I4
Att3 -> I5
Att3 -> I6
Att3 -> I7
Att3 -> I9
L1 -> Att1
L20 -> Att2
L21 -> Att2
L30 -> Att3
L31 -> Att3
}
')

ggdag(linear_lcdm) + theme_dag()

Convergent LCDM-like Bayes Net

Code
convergent_lcdm <- dagitty('
dag {
Att1 [latent,pos="-0.533,-0.496"]
Att2 [latent,pos="-0.495,-0.502"]
Att3 [latent,pos="-0.457,-0.499"]
I1 [pos="-0.519,-0.522"]
I10 [pos="-0.535,-0.523"]
I11 [pos="-0.491,-0.512"]
I12 [pos="-0.489,-0.514"]
I13 [pos="-0.532,-0.522"]
I14 [pos="-0.528,-0.523"]
I15 [pos="-0.464,-0.531"]
I16 [pos="-0.487,-0.516"]
I17 [pos="-0.476,-0.521"]
I18 [pos="-0.461,-0.531"]
I19 [pos="-0.458,-0.531"]
I2 [pos="-0.516,-0.529"]
I20 [pos="-0.485,-0.519"]
I21 [pos="-0.483,-0.521"]
I22 [pos="-0.455,-0.531"]
I23 [pos="-0.510,-0.529"]
I24 [pos="-0.507,-0.529"]
I25 [pos="-0.525,-0.523"]
I26 [pos="-0.451,-0.531"]
I27 [pos="-0.522,-0.522"]
I28 [pos="-0.448,-0.531"]
I3 [pos="-0.496,-0.507"]
I4 [pos="-0.476,-0.530"]
I5 [pos="-0.473,-0.530"]
I6 [pos="-0.470,-0.531"]
I7 [pos="-0.494,-0.509"]
I8 [pos="-0.513,-0.529"]
I9 [pos="-0.467,-0.531"]
L1 [pos="-0.533,-0.490"]
L20 [pos="-0.498,-0.490"]
L21 [pos="-0.492,-0.490"]
L30 [pos="-0.461,-0.490"]
L31 [pos="-0.453,-0.491"]
Att1 -> Att3
Att1 -> I1
Att1 -> I10
Att1 -> I11
Att1 -> I12
Att1 -> I13
Att1 -> I14
Att1 -> I16
Att1 -> I20
Att1 -> I21
Att1 -> I25
Att1 -> I27
Att1 -> I3
Att1 -> I7
Att2 -> Att3
Att2 -> I1
Att2 -> I17
Att2 -> I2
Att2 -> I23
Att2 -> I24
Att2 -> I8
Att3 -> I11
Att3 -> I12
Att3 -> I15
Att3 -> I16
Att3 -> I17
Att3 -> I18
Att3 -> I19
Att3 -> I20
Att3 -> I21
Att3 -> I22
Att3 -> I26
Att3 -> I28
Att3 -> I3
Att3 -> I4
Att3 -> I5
Att3 -> I6
Att3 -> I7
Att3 -> I9
L1 -> Att1
L20 -> Att2
L21 -> Att2
L30 -> Att3
L31 -> Att3
}

')

ggdag(convergent_lcdm) + theme_dag()

Divergent LCDM-like Bayes Net

Code
divergent_lcdm <- dagitty('
dag {
Att1 [latent,pos="-0.533,-0.496"]
Att2 [latent,pos="-0.495,-0.502"]
Att3 [latent,pos="-0.457,-0.499"]
I1 [pos="-0.519,-0.522"]
I10 [pos="-0.535,-0.523"]
I11 [pos="-0.491,-0.512"]
I12 [pos="-0.489,-0.514"]
I13 [pos="-0.532,-0.522"]
I14 [pos="-0.528,-0.523"]
I15 [pos="-0.464,-0.531"]
I16 [pos="-0.487,-0.516"]
I17 [pos="-0.476,-0.521"]
I18 [pos="-0.461,-0.531"]
I19 [pos="-0.458,-0.531"]
I2 [pos="-0.516,-0.529"]
I20 [pos="-0.485,-0.519"]
I21 [pos="-0.483,-0.521"]
I22 [pos="-0.455,-0.531"]
I23 [pos="-0.510,-0.529"]
I24 [pos="-0.507,-0.529"]
I25 [pos="-0.525,-0.523"]
I26 [pos="-0.451,-0.531"]
I27 [pos="-0.522,-0.522"]
I28 [pos="-0.448,-0.531"]
I3 [pos="-0.496,-0.507"]
I4 [pos="-0.476,-0.530"]
I5 [pos="-0.473,-0.530"]
I6 [pos="-0.470,-0.531"]
I7 [pos="-0.494,-0.509"]
I8 [pos="-0.513,-0.529"]
I9 [pos="-0.467,-0.531"]
L1 [pos="-0.533,-0.490"]
L20 [pos="-0.498,-0.490"]
L21 [pos="-0.492,-0.490"]
L30 [pos="-0.461,-0.490"]
L31 [pos="-0.453,-0.491"]
Att1 -> Att2
Att1 -> Att3
Att1 -> I1
Att1 -> I10
Att1 -> I11
Att1 -> I12
Att1 -> I13
Att1 -> I14
Att1 -> I16
Att1 -> I20
Att1 -> I21
Att1 -> I25
Att1 -> I27
Att1 -> I3
Att1 -> I7
Att2 -> I1
Att2 -> I17
Att2 -> I2
Att2 -> I23
Att2 -> I24
Att2 -> I8
Att3 -> I11
Att3 -> I12
Att3 -> I15
Att3 -> I16
Att3 -> I17
Att3 -> I18
Att3 -> I19
Att3 -> I20
Att3 -> I21
Att3 -> I22
Att3 -> I26
Att3 -> I28
Att3 -> I3
Att3 -> I4
Att3 -> I5
Att3 -> I6
Att3 -> I7
Att3 -> I9
L1 -> Att1
L20 -> Att2
L21 -> Att2
L30 -> Att3
L31 -> Att3
}
')

ggdag(divergent_lcdm) + theme_dag()

  • I = Item

  • L = Lambda

  • Att = Latent Attribute

Deterministic Inputs, Noisy “And” Gate (DINA)

Below are DINA models through a Bayesian Network framework. Essentially, the model assumes that each student has mastered all skills in order to correctly respond to an assessment item. See here for an excellent post about the DINA model.

Code
dina_dag <- dagitty('
dag {
Att1 [latent,pos="-0.533,-0.495"]
Att2 [latent,pos="-0.491,-0.496"]
Att3 [latent,pos="-0.456,-0.495"]
D [pos="-0.490,-0.509"]
FP [pos="-0.497,-0.536"]
I1 [pos="-0.536,-0.520"]
I10 [pos="-0.509,-0.520"]
I11 [pos="-0.506,-0.520"]
I12 [pos="-0.503,-0.519"]
I13 [pos="-0.500,-0.519"]
I14 [pos="-0.497,-0.519"]
I15 [pos="-0.494,-0.519"]
I16 [pos="-0.492,-0.519"]
I17 [pos="-0.489,-0.519"]
I18 [pos="-0.486,-0.519"]
I19 [pos="-0.483,-0.519"]
I2 [pos="-0.533,-0.520"]
I20 [pos="-0.480,-0.519"]
I21 [pos="-0.477,-0.519"]
I22 [pos="-0.474,-0.519"]
I23 [pos="-0.471,-0.519"]
I24 [pos="-0.468,-0.519"]
I25 [pos="-0.465,-0.519"]
I26 [pos="-0.462,-0.519"]
I27 [pos="-0.459,-0.519"]
I28 [pos="-0.456,-0.519"]
I3 [pos="-0.530,-0.520"]
I4 [pos="-0.527,-0.520"]
I5 [pos="-0.524,-0.520"]
I6 [pos="-0.521,-0.520"]
I7 [pos="-0.518,-0.520"]
I8 [pos="-0.515,-0.520"]
I9 [pos="-0.512,-0.520"]
L1 [pos="-0.533,-0.490"]
L20 [pos="-0.494,-0.490"]
L21 [pos="-0.488,-0.490"]
L30 [pos="-0.460,-0.489"]
L31 [pos="-0.453,-0.489"]
Q [pos="-0.449,-0.503"]
TP [pos="-0.486,-0.536"]
Att1 -> D
Att2 -> D
Att3 -> D
FP -> I1
FP -> I10
FP -> I11
FP -> I12
FP -> I13
FP -> I14
FP -> I15
FP -> I16
FP -> I17
FP -> I18
FP -> I19
FP -> I2
FP -> I20
FP -> I21
FP -> I22
FP -> I23
FP -> I24
FP -> I25
FP -> I26
FP -> I27
FP -> I28
FP -> I3
FP -> I4
FP -> I5
FP -> I6
FP -> I7
FP -> I8
FP -> I9
I1 -> D
I10 -> D
I11 -> D
I12 -> D
I13 -> D
I14 -> D
I15 -> D
I16 -> D
I17 -> D
I18 -> D
I19 -> D
I2 -> D
I20 -> D
I21 -> D
I22 -> D
I23 -> D
I24 -> D
I25 -> D
I26 -> D
I27 -> D
I28 -> D
I3 -> D
I4 -> D
I5 -> D
I6 -> D
I7 -> D
I8 -> D
I9 -> D
L1 -> Att1
L20 -> Att2
L21 -> Att2
L30 -> Att3
L31 -> Att3
Q -> D
TP -> I1
TP -> I10
TP -> I11
TP -> I12
TP -> I13
TP -> I14
TP -> I15
TP -> I16
TP -> I17
TP -> I18
TP -> I19
TP -> I2
TP -> I20
TP -> I21
TP -> I22
TP -> I23
TP -> I24
TP -> I25
TP -> I26
TP -> I27
TP -> I28
TP -> I3
TP -> I4
TP -> I5
TP -> I6
TP -> I7
TP -> I8
TP -> I9
}
')

ggdag(dina_dag) + theme_dag()

Linear DINA

Code
linear_dina <- dagitty('
dag {
Att1 [latent,pos="-0.533,-0.495"]
Att2 [latent,pos="-0.491,-0.496"]
Att3 [latent,pos="-0.456,-0.495"]
D [pos="-0.490,-0.509"]
FP [pos="-0.497,-0.536"]
I1 [pos="-0.536,-0.520"]
I10 [pos="-0.509,-0.520"]
I11 [pos="-0.506,-0.520"]
I12 [pos="-0.503,-0.519"]
I13 [pos="-0.500,-0.519"]
I14 [pos="-0.497,-0.519"]
I15 [pos="-0.494,-0.519"]
I16 [pos="-0.492,-0.519"]
I17 [pos="-0.489,-0.519"]
I18 [pos="-0.486,-0.519"]
I19 [pos="-0.483,-0.519"]
I2 [pos="-0.533,-0.520"]
I20 [pos="-0.480,-0.519"]
I21 [pos="-0.477,-0.519"]
I22 [pos="-0.474,-0.519"]
I23 [pos="-0.471,-0.519"]
I24 [pos="-0.468,-0.519"]
I25 [pos="-0.465,-0.519"]
I26 [pos="-0.462,-0.519"]
I27 [pos="-0.459,-0.519"]
I28 [pos="-0.456,-0.519"]
I3 [pos="-0.530,-0.520"]
I4 [pos="-0.527,-0.520"]
I5 [pos="-0.524,-0.520"]
I6 [pos="-0.521,-0.520"]
I7 [pos="-0.518,-0.520"]
I8 [pos="-0.515,-0.520"]
I9 [pos="-0.512,-0.520"]
L1 [pos="-0.533,-0.490"]
L20 [pos="-0.494,-0.490"]
L21 [pos="-0.488,-0.490"]
L30 [pos="-0.460,-0.489"]
L31 [pos="-0.453,-0.489"]
Q [pos="-0.449,-0.503"]
TP [pos="-0.486,-0.536"]
Att1 -> Att2
Att1 -> D
Att2 -> Att3
Att2 -> D
Att3 -> D
FP -> I1
FP -> I10
FP -> I11
FP -> I12
FP -> I13
FP -> I14
FP -> I15
FP -> I16
FP -> I17
FP -> I18
FP -> I19
FP -> I2
FP -> I20
FP -> I21
FP -> I22
FP -> I23
FP -> I24
FP -> I25
FP -> I26
FP -> I27
FP -> I28
FP -> I3
FP -> I4
FP -> I5
FP -> I6
FP -> I7
FP -> I8
FP -> I9
I1 -> D
I10 -> D
I11 -> D
I12 -> D
I13 -> D
I14 -> D
I15 -> D
I16 -> D
I17 -> D
I18 -> D
I19 -> D
I2 -> D
I20 -> D
I21 -> D
I22 -> D
I23 -> D
I24 -> D
I25 -> D
I26 -> D
I27 -> D
I28 -> D
I3 -> D
I4 -> D
I5 -> D
I6 -> D
I7 -> D
I8 -> D
I9 -> D
L1 -> Att1
L20 -> Att2
L21 -> Att2
L30 -> Att3
L31 -> Att3
Q -> D
TP -> I1
TP -> I10
TP -> I11
TP -> I12
TP -> I13
TP -> I14
TP -> I15
TP -> I16
TP -> I17
TP -> I18
TP -> I19
TP -> I2
TP -> I20
TP -> I21
TP -> I22
TP -> I23
TP -> I24
TP -> I25
TP -> I26
TP -> I27
TP -> I28
TP -> I3
TP -> I4
TP -> I5
TP -> I6
TP -> I7
TP -> I8
TP -> I9
}
')

ggdag(linear_dina) + theme_dag()

Convergent DINA

Code
convergent_dina <- dagitty('
dag {
Att1 [latent,pos="-0.533,-0.495"]
Att2 [latent,pos="-0.491,-0.499"]
Att3 [latent,pos="-0.456,-0.495"]
D [pos="-0.490,-0.509"]
FP [pos="-0.497,-0.536"]
I1 [pos="-0.536,-0.520"]
I10 [pos="-0.509,-0.520"]
I11 [pos="-0.506,-0.520"]
I12 [pos="-0.503,-0.519"]
I13 [pos="-0.500,-0.519"]
I14 [pos="-0.497,-0.519"]
I15 [pos="-0.494,-0.519"]
I16 [pos="-0.492,-0.519"]
I17 [pos="-0.489,-0.519"]
I18 [pos="-0.486,-0.519"]
I19 [pos="-0.483,-0.519"]
I2 [pos="-0.533,-0.520"]
I20 [pos="-0.480,-0.519"]
I21 [pos="-0.477,-0.519"]
I22 [pos="-0.474,-0.519"]
I23 [pos="-0.471,-0.519"]
I24 [pos="-0.468,-0.519"]
I25 [pos="-0.465,-0.519"]
I26 [pos="-0.462,-0.519"]
I27 [pos="-0.459,-0.519"]
I28 [pos="-0.456,-0.519"]
I3 [pos="-0.530,-0.520"]
I4 [pos="-0.527,-0.520"]
I5 [pos="-0.524,-0.520"]
I6 [pos="-0.521,-0.520"]
I7 [pos="-0.518,-0.520"]
I8 [pos="-0.515,-0.520"]
I9 [pos="-0.512,-0.520"]
L1 [pos="-0.533,-0.490"]
L20 [pos="-0.494,-0.490"]
L21 [pos="-0.488,-0.490"]
L30 [pos="-0.460,-0.489"]
L31 [pos="-0.453,-0.489"]
Q [pos="-0.449,-0.503"]
TP [pos="-0.486,-0.536"]
Att1 -> Att3
Att1 -> D
Att2 -> Att3
Att2 -> D
Att3 -> D
FP -> I1
FP -> I10
FP -> I11
FP -> I12
FP -> I13
FP -> I14
FP -> I15
FP -> I16
FP -> I17
FP -> I18
FP -> I19
FP -> I2
FP -> I20
FP -> I21
FP -> I22
FP -> I23
FP -> I24
FP -> I25
FP -> I26
FP -> I27
FP -> I28
FP -> I3
FP -> I4
FP -> I5
FP -> I6
FP -> I7
FP -> I8
FP -> I9
I1 -> D
I10 -> D
I11 -> D
I12 -> D
I13 -> D
I14 -> D
I15 -> D
I16 -> D
I17 -> D
I18 -> D
I19 -> D
I2 -> D
I20 -> D
I21 -> D
I22 -> D
I23 -> D
I24 -> D
I25 -> D
I26 -> D
I27 -> D
I28 -> D
I3 -> D
I4 -> D
I5 -> D
I6 -> D
I7 -> D
I8 -> D
I9 -> D
L1 -> Att1
L20 -> Att2
L21 -> Att2
L30 -> Att3
L31 -> Att3
Q -> D
TP -> I1
TP -> I10
TP -> I11
TP -> I12
TP -> I13
TP -> I14
TP -> I15
TP -> I16
TP -> I17
TP -> I18
TP -> I19
TP -> I2
TP -> I20
TP -> I21
TP -> I22
TP -> I23
TP -> I24
TP -> I25
TP -> I26
TP -> I27
TP -> I28
TP -> I3
TP -> I4
TP -> I5
TP -> I6
TP -> I7
TP -> I8
TP -> I9
}
')

ggdag(convergent_dina) + theme_dag()

Divergent DINA

Code
divergent_dina <- dagitty('
dag {
Att1 [latent,pos="-0.533,-0.495"]
Att2 [latent,pos="-0.491,-0.499"]
Att3 [latent,pos="-0.456,-0.495"]
D [pos="-0.490,-0.509"]
FP [pos="-0.497,-0.536"]
I1 [pos="-0.536,-0.520"]
I10 [pos="-0.509,-0.520"]
I11 [pos="-0.506,-0.520"]
I12 [pos="-0.503,-0.519"]
I13 [pos="-0.500,-0.519"]
I14 [pos="-0.497,-0.519"]
I15 [pos="-0.494,-0.519"]
I16 [pos="-0.492,-0.519"]
I17 [pos="-0.489,-0.519"]
I18 [pos="-0.486,-0.519"]
I19 [pos="-0.483,-0.519"]
I2 [pos="-0.533,-0.520"]
I20 [pos="-0.480,-0.519"]
I21 [pos="-0.477,-0.519"]
I22 [pos="-0.474,-0.519"]
I23 [pos="-0.471,-0.519"]
I24 [pos="-0.468,-0.519"]
I25 [pos="-0.465,-0.519"]
I26 [pos="-0.462,-0.519"]
I27 [pos="-0.459,-0.519"]
I28 [pos="-0.456,-0.519"]
I3 [pos="-0.530,-0.520"]
I4 [pos="-0.527,-0.520"]
I5 [pos="-0.524,-0.520"]
I6 [pos="-0.521,-0.520"]
I7 [pos="-0.518,-0.520"]
I8 [pos="-0.515,-0.520"]
I9 [pos="-0.512,-0.520"]
L1 [pos="-0.533,-0.490"]
L20 [pos="-0.494,-0.490"]
L21 [pos="-0.488,-0.490"]
L30 [pos="-0.460,-0.489"]
L31 [pos="-0.453,-0.489"]
Q [pos="-0.449,-0.503"]
TP [pos="-0.486,-0.536"]
Att1 -> Att2
Att1 -> Att3
Att1 -> D
Att2 -> D
Att3 -> D
FP -> I1
FP -> I10
FP -> I11
FP -> I12
FP -> I13
FP -> I14
FP -> I15
FP -> I16
FP -> I17
FP -> I18
FP -> I19
FP -> I2
FP -> I20
FP -> I21
FP -> I22
FP -> I23
FP -> I24
FP -> I25
FP -> I26
FP -> I27
FP -> I28
FP -> I3
FP -> I4
FP -> I5
FP -> I6
FP -> I7
FP -> I8
FP -> I9
I1 -> D
I10 -> D
I11 -> D
I12 -> D
I13 -> D
I14 -> D
I15 -> D
I16 -> D
I17 -> D
I18 -> D
I19 -> D
I2 -> D
I20 -> D
I21 -> D
I22 -> D
I23 -> D
I24 -> D
I25 -> D
I26 -> D
I27 -> D
I28 -> D
I3 -> D
I4 -> D
I5 -> D
I6 -> D
I7 -> D
I8 -> D
I9 -> D
L1 -> Att1
L20 -> Att2
L21 -> Att2
L30 -> Att3
L31 -> Att3
Q -> D
TP -> I1
TP -> I10
TP -> I11
TP -> I12
TP -> I13
TP -> I14
TP -> I15
TP -> I16
TP -> I17
TP -> I18
TP -> I19
TP -> I2
TP -> I20
TP -> I21
TP -> I22
TP -> I23
TP -> I24
TP -> I25
TP -> I26
TP -> I27
TP -> I28
TP -> I3
TP -> I4
TP -> I5
TP -> I6
TP -> I7
TP -> I8
TP -> I9
}
')

ggdag(divergent_dina) + theme_dag()

  • TP = True Positive

  • FP = False Positive

  • Q = Q-matrix

  • I = Item

  • D = Delta

  • L = Lambda

  • Att = Latent Attribute