Edu Measures

setwd("/Users/isaiahmireles/Desktop/Misconceptions")
cohorts <- read.csv("cohorts.csv")

item difficulty (proportion correct)

library(tidyverse)
## Warning: package 'dplyr' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.0     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   4.0.0     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.1.0     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
items <- cohorts |>
  filter(term == "F23",
         exam == "Midterm") |>
  pivot_longer(
    cols = matches("^Q\\d+$"),
    names_to = "question",
    values_to = "correct"
  ) |>
  mutate(
    question = factor(
      question,
      levels = paste0("Q", 1:34)
    )
  )

items |>
  group_by(question) |>
  summarise(p_correct = mean(correct, na.rm = TRUE)) |>
  ggplot(aes(x = question, y = p_correct)) +
  geom_col(fill = "steelblue") +
  coord_cartesian(ylim = c(0, 1)) +
  labs(
    title = "Item Difficulty: F23 Midterm",
    x = "Question",
    y = "Proportion Correct"
  ) +
  theme_minimal()

Binary response heatmap

items |>
  ggplot(aes(x = question, y = student_id,
             fill = factor(correct))) +
  geom_tile() +
  scale_fill_manual(
    values = c("0" = "tomato", "1" = "seagreen"),
    na.value = "grey85"
  ) +
  labs(
    title = "Student Response Heatmap",
    x = "Question",
    y = "Student",
    fill = "Correct"
  ) +
  theme_minimal() +
  theme(
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank()
  )

Item-total correlation

cohorts |>
  filter(term == "F23",
         exam == "Midterm",
         version == "A") |>
  select(matches("^Q\\d+$")) |>
  summarise(
    across(
      everything(),
      ~ cor(
          .x,
          rowSums(pick(everything()), na.rm = TRUE) - .x,
          use = "complete.obs"
        )
    )
  ) |>
  pivot_longer(
    everything(),
    names_to = "question",
    values_to = "item_rest_cor"
  ) |>
  ggplot(aes(
    x = reorder(question, item_rest_cor),
    y = item_rest_cor
  )) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(
    title = "Corrected Item-Total Correlations",
    x = "Question",
    y = "Correlation"
  ) +
  theme_minimal()

Networks

Observed item-correlation network

library(tidyverse)
library(igraph)
## 
## Attaching package: 'igraph'
## The following objects are masked from 'package:lubridate':
## 
##     %--%, union
## The following objects are masked from 'package:dplyr':
## 
##     as_data_frame, groups, union
## The following objects are masked from 'package:purrr':
## 
##     compose, simplify
## The following object is masked from 'package:tidyr':
## 
##     crossing
## The following object is masked from 'package:tibble':
## 
##     as_data_frame
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## The following object is masked from 'package:base':
## 
##     union
library(ggraph)

X <- cohorts |>
  filter(
    term == "S24",
    exam == "Midterm",
    version == "AB"
  ) |>
  select(matches("^Q\\d+$"))

# Binary item correlation matrix
R <- cor(X, use = "pairwise.complete.obs")

# Convert matrix to network edges
edges <- as.data.frame(as.table(R)) |>
  rename(from = Var1, to = Var2, weight = Freq) |>
  filter(
    as.integer(from) < as.integer(to),
    abs(weight) >= 0.20
  )

nodes <- tibble(
  name = names(X),
  p_correct = colMeans(X, na.rm = TRUE)
)

g <- graph_from_data_frame(
  edges,
  directed = FALSE,
  vertices = nodes
)

ggraph(g, layout = "fr") +
  geom_edge_link(aes(width = abs(weight)),
                 alpha = 0.4) +
  geom_node_point(aes(size = p_correct),
                  color = "steelblue") +
  geom_node_text(aes(label = name), repel = TRUE) +
  theme_graph()
## Warning: The `trans` argument of `continuous_scale()` is deprecated as of ggplot2 3.5.0.
## ℹ Please use the `transform` argument instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

IRT residual network

library(tidyverse)
library(mirt)
## Warning: package 'mirt' was built under R version 4.4.3
## Loading required package: stats4
## Loading required package: lattice
# Fit Rasch model
model <- mirt(
  data = X,
  model = 1,
  itemtype = "Rasch",
  verbose = FALSE
)

# Extract Q3 residual correlation matrix
Q3 <- residuals(model, type = "Q3")
## Q3 summary statistics:
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  -0.251  -0.088  -0.022  -0.020   0.037   0.472 
## 
##         Q1     Q2     Q3     Q4     Q5     Q6     Q7     Q8     Q9    Q10
## Q1   1.000  0.252  0.009  0.016 -0.094 -0.136  0.248  0.006 -0.081  0.053
## Q2   0.252  1.000  0.305 -0.106  0.012 -0.057 -0.037  0.009  0.134  0.075
## Q3   0.009  0.305  1.000 -0.138 -0.063  0.051 -0.061  0.007  0.049  0.149
## Q4   0.016 -0.106 -0.138  1.000 -0.228 -0.244 -0.039  0.059 -0.001  0.101
## Q5  -0.094  0.012 -0.063 -0.228  1.000  0.018  0.069 -0.045 -0.027 -0.077
## Q6  -0.136 -0.057  0.051 -0.244  0.018  1.000  0.126 -0.188 -0.057 -0.179
## Q7   0.248 -0.037 -0.061 -0.039  0.069  0.126  1.000  0.031 -0.039 -0.025
## Q8   0.006  0.009  0.007  0.059 -0.045 -0.188  0.031  1.000 -0.007  0.069
## Q9  -0.081  0.134  0.049 -0.001 -0.027 -0.057 -0.039 -0.007  1.000  0.119
## Q10  0.053  0.075  0.149  0.101 -0.077 -0.179 -0.025  0.069  0.119  1.000
## Q11 -0.071 -0.079 -0.118 -0.063  0.097 -0.028 -0.034  0.121  0.192  0.047
## Q12  0.014  0.017 -0.113 -0.058 -0.080 -0.053 -0.096  0.028  0.087  0.042
## Q13 -0.141 -0.155  0.098  0.169 -0.099  0.085 -0.068  0.003 -0.145 -0.077
## Q14 -0.057  0.038  0.010 -0.089  0.012 -0.022 -0.071  0.013  0.096 -0.177
## Q15 -0.021 -0.134 -0.028 -0.007 -0.076  0.061  0.120 -0.105 -0.126 -0.228
## Q16 -0.002 -0.004 -0.204 -0.019 -0.128 -0.118 -0.059 -0.030  0.199 -0.045
## Q17  0.057  0.056 -0.022  0.117 -0.176 -0.124 -0.035  0.090 -0.090  0.036
## Q18 -0.131 -0.140  0.069  0.074 -0.051 -0.073 -0.108  0.054  0.029 -0.059
## Q19  0.030 -0.192 -0.001  0.029  0.012 -0.049 -0.084 -0.015 -0.038 -0.112
## Q20 -0.121 -0.128 -0.017 -0.008  0.055 -0.059 -0.060  0.094  0.132  0.099
## Q21 -0.134 -0.054 -0.033  0.076  0.140 -0.142 -0.104 -0.131 -0.097 -0.062
## Q22 -0.034  0.084 -0.069 -0.053 -0.041 -0.179  0.059 -0.036 -0.028 -0.146
## Q23 -0.020 -0.102  0.045  0.017  0.018  0.112 -0.127 -0.100 -0.133 -0.013
## Q24  0.121 -0.004  0.050 -0.217  0.062 -0.033  0.020 -0.114 -0.012 -0.122
## Q25 -0.054 -0.176  0.029 -0.076 -0.045  0.076  0.116  0.049 -0.022 -0.081
## Q26 -0.109 -0.010  0.000 -0.162  0.117  0.079 -0.049 -0.017  0.022 -0.073
## Q27 -0.126 -0.030  0.090 -0.118  0.067  0.242  0.067 -0.009  0.171  0.087
## Q28  0.102  0.011  0.053  0.062 -0.057 -0.178 -0.078 -0.123 -0.048  0.151
## Q29 -0.127 -0.048  0.000 -0.010 -0.179  0.172 -0.001 -0.161  0.049 -0.031
## Q30  0.103  0.235  0.097  0.002 -0.106 -0.071 -0.059  0.036  0.020  0.108
## Q31  0.083  0.004 -0.003 -0.194  0.047 -0.015  0.030  0.247 -0.013  0.024
## Q32  0.080  0.008 -0.195 -0.112 -0.129 -0.051  0.031 -0.138 -0.105 -0.029
## Q33 -0.003  0.027 -0.181 -0.144  0.053  0.063 -0.054 -0.218 -0.018  0.011
## Q34 -0.079  0.023 -0.048  0.032 -0.047 -0.053 -0.054 -0.070 -0.114  0.033
##        Q11    Q12    Q13    Q14    Q15    Q16    Q17    Q18    Q19    Q20
## Q1  -0.071  0.014 -0.141 -0.057 -0.021 -0.002  0.057 -0.131  0.030 -0.121
## Q2  -0.079  0.017 -0.155  0.038 -0.134 -0.004  0.056 -0.140 -0.192 -0.128
## Q3  -0.118 -0.113  0.098  0.010 -0.028 -0.204 -0.022  0.069 -0.001 -0.017
## Q4  -0.063 -0.058  0.169 -0.089 -0.007 -0.019  0.117  0.074  0.029 -0.008
## Q5   0.097 -0.080 -0.099  0.012 -0.076 -0.128 -0.176 -0.051  0.012  0.055
## Q6  -0.028 -0.053  0.085 -0.022  0.061 -0.118 -0.124 -0.073 -0.049 -0.059
## Q7  -0.034 -0.096 -0.068 -0.071  0.120 -0.059 -0.035 -0.108 -0.084 -0.060
## Q8   0.121  0.028  0.003  0.013 -0.105 -0.030  0.090  0.054 -0.015  0.094
## Q9   0.192  0.087 -0.145  0.096 -0.126  0.199 -0.090  0.029 -0.038  0.132
## Q10  0.047  0.042 -0.077 -0.177 -0.228 -0.045  0.036 -0.059 -0.112  0.099
## Q11  1.000  0.104  0.030  0.113 -0.109  0.210 -0.079  0.051 -0.020  0.139
## Q12  0.104  1.000  0.170 -0.101 -0.036  0.121  0.154 -0.060  0.011  0.123
## Q13  0.030  0.170  1.000 -0.039  0.012 -0.029 -0.158  0.067  0.104  0.095
## Q14  0.113 -0.101 -0.039  1.000  0.182 -0.090 -0.002 -0.242 -0.146 -0.104
## Q15 -0.109 -0.036  0.012  0.182  1.000 -0.042 -0.046 -0.056 -0.146 -0.184
## Q16  0.210  0.121 -0.029 -0.090 -0.042  1.000 -0.141  0.028  0.137  0.119
## Q17 -0.079  0.154 -0.158 -0.002 -0.046 -0.141  1.000 -0.088 -0.015 -0.135
## Q18  0.051 -0.060  0.067 -0.242 -0.056  0.028 -0.088  1.000  0.325  0.270
## Q19 -0.020  0.011  0.104 -0.146 -0.146  0.137 -0.015  0.325  1.000  0.390
## Q20  0.139  0.123  0.095 -0.104 -0.184  0.119 -0.135  0.270  0.390  1.000
## Q21  0.036  0.023  0.038  0.007 -0.073  0.002 -0.097  0.072 -0.020 -0.079
## Q22 -0.099  0.099 -0.053  0.066  0.021  0.034  0.064 -0.251 -0.054 -0.028
## Q23 -0.106 -0.020  0.093  0.115 -0.052 -0.066 -0.080  0.043 -0.011 -0.025
## Q24  0.013 -0.130 -0.156  0.148  0.017  0.067 -0.109 -0.071 -0.083 -0.085
## Q25 -0.005 -0.012  0.058 -0.041 -0.048 -0.084 -0.086  0.078  0.149  0.164
## Q26  0.176 -0.143 -0.039 -0.094 -0.109  0.070 -0.124 -0.015  0.020  0.086
## Q27 -0.065 -0.105  0.012  0.001 -0.044 -0.110 -0.080  0.054 -0.021  0.064
## Q28 -0.147 -0.218 -0.149 -0.048 -0.039  0.022 -0.110  0.174  0.044 -0.126
## Q29 -0.022  0.119 -0.075 -0.002 -0.011  0.014  0.019 -0.225 -0.178 -0.143
## Q30 -0.119 -0.082 -0.233  0.029 -0.205 -0.119  0.472 -0.021 -0.062 -0.191
## Q31 -0.100 -0.043 -0.077 -0.108 -0.079  0.024 -0.138  0.041 -0.088 -0.008
## Q32 -0.080 -0.035 -0.059 -0.090  0.078  0.028  0.018 -0.147 -0.027 -0.094
## Q33 -0.186 -0.003  0.037 -0.137  0.145  0.011  0.040 -0.011  0.043 -0.029
## Q34  0.010  0.092 -0.025 -0.093 -0.047 -0.110  0.097  0.026 -0.108 -0.028
##        Q21    Q22    Q23    Q24    Q25    Q26    Q27    Q28    Q29    Q30
## Q1  -0.134 -0.034 -0.020  0.121 -0.054 -0.109 -0.126  0.102 -0.127  0.103
## Q2  -0.054  0.084 -0.102 -0.004 -0.176 -0.010 -0.030  0.011 -0.048  0.235
## Q3  -0.033 -0.069  0.045  0.050  0.029  0.000  0.090  0.053  0.000  0.097
## Q4   0.076 -0.053  0.017 -0.217 -0.076 -0.162 -0.118  0.062 -0.010  0.002
## Q5   0.140 -0.041  0.018  0.062 -0.045  0.117  0.067 -0.057 -0.179 -0.106
## Q6  -0.142 -0.179  0.112 -0.033  0.076  0.079  0.242 -0.178  0.172 -0.071
## Q7  -0.104  0.059 -0.127  0.020  0.116 -0.049  0.067 -0.078 -0.001 -0.059
## Q8  -0.131 -0.036 -0.100 -0.114  0.049 -0.017 -0.009 -0.123 -0.161  0.036
## Q9  -0.097 -0.028 -0.133 -0.012 -0.022  0.022  0.171 -0.048  0.049  0.020
## Q10 -0.062 -0.146 -0.013 -0.122 -0.081 -0.073  0.087  0.151 -0.031  0.108
## Q11  0.036 -0.099 -0.106  0.013 -0.005  0.176 -0.065 -0.147 -0.022 -0.119
## Q12  0.023  0.099 -0.020 -0.130 -0.012 -0.143 -0.105 -0.218  0.119 -0.082
## Q13  0.038 -0.053  0.093 -0.156  0.058 -0.039  0.012 -0.149 -0.075 -0.233
## Q14  0.007  0.066  0.115  0.148 -0.041 -0.094  0.001 -0.048 -0.002  0.029
## Q15 -0.073  0.021 -0.052  0.017 -0.048 -0.109 -0.044 -0.039 -0.011 -0.205
## Q16  0.002  0.034 -0.066  0.067 -0.084  0.070 -0.110  0.022  0.014 -0.119
## Q17 -0.097  0.064 -0.080 -0.109 -0.086 -0.124 -0.080 -0.110  0.019  0.472
## Q18  0.072 -0.251  0.043 -0.071  0.078 -0.015  0.054  0.174 -0.225 -0.021
## Q19 -0.020 -0.054 -0.011 -0.083  0.149  0.020 -0.021  0.044 -0.178 -0.062
## Q20 -0.079 -0.028 -0.025 -0.085  0.164  0.086  0.064 -0.126 -0.143 -0.191
## Q21  1.000  0.010  0.106  0.021 -0.131 -0.094 -0.103 -0.026  0.003 -0.047
## Q22  0.010  1.000 -0.118  0.037 -0.064 -0.058 -0.225 -0.007  0.007 -0.011
## Q23  0.106 -0.118  1.000 -0.081 -0.050 -0.113 -0.018 -0.086 -0.009 -0.124
## Q24  0.021  0.037 -0.081  1.000 -0.158 -0.076 -0.246  0.053 -0.189 -0.134
## Q25 -0.131 -0.064 -0.050 -0.158  1.000 -0.018  0.038 -0.153  0.148 -0.108
## Q26 -0.094 -0.058 -0.113 -0.076 -0.018  1.000  0.067 -0.006  0.047 -0.116
## Q27 -0.103 -0.225 -0.018 -0.246  0.038  0.067  1.000  0.038 -0.009  0.143
## Q28 -0.026 -0.007 -0.086  0.053 -0.153 -0.006  0.038  1.000 -0.070  0.188
## Q29  0.003  0.007 -0.009 -0.189  0.148  0.047 -0.009 -0.070  1.000  0.000
## Q30 -0.047 -0.011 -0.124 -0.134 -0.108 -0.116  0.143  0.188  0.000  1.000
## Q31  0.022 -0.120  0.051 -0.105 -0.020  0.026  0.134 -0.050 -0.025  0.027
## Q32 -0.191 -0.028  0.024 -0.208 -0.014 -0.023  0.019 -0.003 -0.020  0.084
## Q33 -0.048 -0.084  0.040 -0.167  0.087 -0.003 -0.037  0.026 -0.035 -0.085
## Q34 -0.014 -0.194 -0.195 -0.053  0.028 -0.009 -0.039 -0.150  0.026 -0.032
##        Q31    Q32    Q33    Q34
## Q1   0.083  0.080 -0.003 -0.079
## Q2   0.004  0.008  0.027  0.023
## Q3  -0.003 -0.195 -0.181 -0.048
## Q4  -0.194 -0.112 -0.144  0.032
## Q5   0.047 -0.129  0.053 -0.047
## Q6  -0.015 -0.051  0.063 -0.053
## Q7   0.030  0.031 -0.054 -0.054
## Q8   0.247 -0.138 -0.218 -0.070
## Q9  -0.013 -0.105 -0.018 -0.114
## Q10  0.024 -0.029  0.011  0.033
## Q11 -0.100 -0.080 -0.186  0.010
## Q12 -0.043 -0.035 -0.003  0.092
## Q13 -0.077 -0.059  0.037 -0.025
## Q14 -0.108 -0.090 -0.137 -0.093
## Q15 -0.079  0.078  0.145 -0.047
## Q16  0.024  0.028  0.011 -0.110
## Q17 -0.138  0.018  0.040  0.097
## Q18  0.041 -0.147 -0.011  0.026
## Q19 -0.088 -0.027  0.043 -0.108
## Q20 -0.008 -0.094 -0.029 -0.028
## Q21  0.022 -0.191 -0.048 -0.014
## Q22 -0.120 -0.028 -0.084 -0.194
## Q23  0.051  0.024  0.040 -0.195
## Q24 -0.105 -0.208 -0.167 -0.053
## Q25 -0.020 -0.014  0.087  0.028
## Q26  0.026 -0.023 -0.003 -0.009
## Q27  0.134  0.019 -0.037 -0.039
## Q28 -0.050 -0.003  0.026 -0.150
## Q29 -0.025 -0.020 -0.035  0.026
## Q30  0.027  0.084 -0.085 -0.032
## Q31  1.000 -0.002 -0.146  0.092
## Q32 -0.002  1.000  0.262  0.030
## Q33 -0.146  0.262  1.000 -0.021
## Q34  0.092  0.030 -0.021  1.000
# Convert matrix to long format
Q3_long <- as.data.frame(Q3) |>
  rownames_to_column("Item1") |>
  pivot_longer(
    cols = -Item1,
    names_to = "Item2",
    values_to = "Q3"
  )

# Plot
Q3_long |>
  ggplot(aes(x = Item1, y = Item2, fill = Q3)) +
  geom_tile() +
  scale_fill_gradient2(
    low = "tomato",
    mid = "white",
    high = "steelblue",
    midpoint = 0
  ) +
  coord_equal() +
  labs(
    title = "IRT Residual Correlation Matrix",
    x = "Question",
    y = "Question",
    fill = "Q3"
  ) +
  theme_minimal()