--- title: "Estimating District Means and SDs from Binned Test Scores, with the binest package" author: "Paul T. von Hippel" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Estimating District Means and SDs from Binned Test Scores, with the binest package} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` # Overview This vignette compares the package's three functions on real data: district-level bin counts and reported mean scores from the 2017-18 State of Texas Assessments of Academic Readiness (STAAR) Grade 6 mathematics test. Because the state publishes each district's mean, every estimate can be scored against a known truth, on accuracy and on runtime. All three functions fit the same heteroskedastic ordered probit (HETOP) model (Reardon, Shear, Castellano & Ho 2017), in which each district's scores are normally distributed around a mean and SD of its own. They differ only in how they fit it: one district at a time, jointly by maximum likelihood, or by MCMC. `fast_hetop()` is the recommended function; `mle_hetop()` and `fh_hetop()` are deprecated and retained only for comparison. See `?fast_hetop` for arguments and return value, and the accompanying paper for the fuller empirical case. # The Texas data ```{r} library(binest) data(tx_g6_math_2018) dim(tx_g6_math_2018) head(tx_g6_math_2018, 3) ``` The dataset has 1,151 districts, each with bin counts in four proficiency categories and a reported average score that serves as ground truth. The published cut scores defining the bin boundaries are 1536, 1653, and 1772: ```{r} ngk <- with(tx_g6_math_2018, cbind(unsatisfactory, approaches, meets, masters)) cuts <- c(1536, 1653, 1772) truth <- tx_g6_math_2018$reported_mean ``` # Method 1: `fast_hetop()` with known cutpoints This uses the published cut scores directly. Output `est_raw$mean` is on the test-score scale; `est_std$mean` is on a standardized scale (population-weighted state mean 0, total state SD 1). `scope` is required and has no default. It says whether the students behind each district's counts are a sample from some larger population or that district's entire population, which decides what the reported standard errors mean. Texas reports counts for every tested student, so a district's true mean is the actual mean of those students' scores — there is no sampling error, and the only uncertainty is that we see four bin counts instead of individual scores. That is `scope = "population"`, and the returned SEs reflect the binning alone. Data from a survey or a simulation would instead be `scope = "sample"`, whose SEs add the variability from having drawn these units rather than others. ```{r} t0 <- Sys.time() fit_bm_known <- fast_hetop(ngk, cutpoints_known = TRUE, cutpoints = cuts, scope = "population") t_bm_known <- as.numeric(Sys.time() - t0, units = "secs") cor(fit_bm_known$est_raw$mean, truth) ``` The two choices give the same point estimates and differ only in the reported uncertainty: ```{r} fit_bm_sample <- fast_hetop(ngk, cutpoints_known = TRUE, cutpoints = cuts, scope = "sample") head(cbind(population = fit_bm_known$est_raw$mean_se, sample = fit_bm_sample$est_raw$mean_se)) ``` ```{r, fig.width = 4.5, fig.height = 4.5} plot(truth, fit_bm_known$est_raw$mean, pch = 16, cex = 0.5, col = rgb(0, 0, 0, 0.3), xlab = "True district mean", ylab = "Estimated mean (test-score scale)", main = "fast_hetop (known cuts)") abline(0, 1, col = "red", lty = 2) ``` # Method 2: `fast_hetop()` with cutpoints estimated from data When cutpoints are not known, `fast_hetop()` derives them from pooled state bin proportions via `qnorm(cumsum(pooled_props))`. Output is on the standardized scale. ```{r} t0 <- Sys.time() fit_bm_null <- fast_hetop(ngk, scope = "population") t_bm_null <- as.numeric(Sys.time() - t0, units = "secs") ``` ```{r, fig.width = 4.5, fig.height = 4.5} plot(truth, fit_bm_null$est_std$mean, pch = 16, cex = 0.5, col = rgb(0, 0, 0, 0.3), xlab = "True district mean", ylab = "Estimated mean (standardized)", main = "fast_hetop (cuts from data)") ``` # Method 3: `fast_hetop()` with empirical-Bayes shrinkage The `estimator = "EB_shrunk"` option applies normal-normal empirical-Bayes shrinkage to the per-district means and log-SDs, using a moment estimator of the between-district variance and the Fisher-information sampling variance of each district's MLE. The shrunken estimates come back in the same `mean` and `sd` fields as the ML estimates, with `estimator` recording which was used. Shrinkage requires `scope = "sample"`, and `fast_hetop()` refuses it under `scope = "population"`. The Texas data cover the whole population, so the call below declares `scope = "sample"` purely to demonstrate the option; for a real population analysis, use the default `"ML"` estimator. ```{r} fit_bm_eb <- fast_hetop(ngk, cutpoints_known = TRUE, cutpoints = cuts, scope = "sample", estimator = "EB_shrunk") ``` ```{r, fig.width = 4.5, fig.height = 4.5} plot(truth, fit_bm_eb$est_raw$mean, pch = 16, cex = 0.5, col = rgb(0, 0, 0, 0.3), xlab = "True district mean", ylab = "EB-shrunk estimated mean", main = "fast_hetop (EB_shrunk, known cuts)") ``` # Method 4: `mle_hetop()` on a 50-district subsample `mle_hetop()` maximizes the likelihood over all districts at once, rather than one district at a time. On the full 1,151-district dataset it does not converge within a reasonable runtime, so we illustrate it on a representative random subsample of 50 districts. ```{r} set.seed(1) sub <- sample(nrow(ngk), 50) t0 <- Sys.time() fit_mle <- mle_hetop(ngk[sub, ], iterlim = 200) t_mle <- as.numeric(Sys.time() - t0, units = "secs") cor(fit_mle$est_star$mug, truth[sub]) ``` ```{r, fig.width = 4.5, fig.height = 4.5} plot(truth[sub], fit_mle$est_star$mug, pch = 16, cex = 0.5, col = rgb(0, 0, 0, 0.3), xlab = "True district mean", ylab = "Estimated mean (standardized)", main = "HETOP MLE (50-district subsample)") ``` # Method 5: `fh_hetop()` on the full dataset `fh_hetop()` fits the model by MCMC (Lockwood, Castellano & Shear 2018), placing a prior over the district parameters and reporting posterior means. Each Gibbs iteration is linear in the number of groups; on 1,151 districts the model fits in about nine minutes. The runtime in this vignette is deliberately short to keep the package build tractable; for production use, longer chains are recommended. ```{r, eval = FALSE} t0 <- Sys.time() fit_fh <- fh_hetop( ngk = ngk, p = c(10, 10), m = c(100, 100), gridL = c(-5.0, log(0.10)), gridU = c( 5.0, log(5.0)), n.iter = 2000, n.burnin = 1000, seed = 3142 ) t_fh <- as.numeric(Sys.time() - t0, units = "secs") cor(fit_fh$fh_hetop_extras$est_star_mug$theta_pm, truth) ``` Because this chunk takes about nine minutes, the package ships with the per-district HETOP-Bayes posterior means pre-computed and stored in `inst/extdata/fh_hetop_means_tx_g6_math_2018.rds`. The scatterplot below uses those cached values. ```{r, fig.width = 4.5, fig.height = 4.5} fh_means_file <- system.file("extdata", "fh_hetop_means_tx_g6_math_2018.rds", package = "binest") fh_means <- readRDS(fh_means_file) plot(truth, fh_means, pch = 16, cex = 0.5, col = rgb(0, 0, 0, 0.3), xlab = "True district mean", ylab = "Estimated mean (standardized)", main = "HETOP Bayes (full data, cached)") ``` # Summary | Method | Runtime | Districts | |--------|---------|-----------| | `fast_hetop()` (known cutpoints) | `r sprintf("%.1f s", t_bm_known)` | `r sum(!is.na(fit_bm_known$est_raw$mean))` / 1151 | | `fast_hetop()` (cutpoints from data) | `r sprintf("%.1f s", t_bm_null)` | `r sum(!is.na(fit_bm_null$est_std$mean))` / 1151 | | `mle_hetop()` (50-district subsample) | `r sprintf("%.1f s", t_mle)` | 50 / 50 | | fh_hetop (full data, not run above) | 450 s | 1151 / 1151 | Note: `fast_hetop()` ran on all 1,151 districts in a fraction of a second --- roughly 1,000 times faster than `fh_hetop()`, and roughly 10 times faster than `mle_hetop()`, even though `mle_hetop()` ran on a 50-district subsample rather than the full data. # References Lockwood, J. R., Castellano, K. E., & Shear, B. R. (2018). Flexible Bayesian models for inferences from coarsened, group-level achievement data. *Journal of Educational and Behavioral Statistics, 43*(6), 663–692. https://doi.org/10.3102/1076998618795124 Reardon, S. F., Shear, B. R., Castellano, K. E., & Ho, A. D. (2017). Using heteroskedastic ordered probit models to recover moments of continuous test score distributions from coarsened data. *Journal of Educational and Behavioral Statistics, 42*(1), 3–45. https://doi.org/10.3102/1076998616666279