The tables of critical values printed in the back of a statistics textbook, for the t, F, studentized range, studentized maximum modulus, Dunnett, and Bryant-Paulson distributions, are not data to be trusted on faith. Each one is the quantile of a known distribution, so each one can be recomputed. This vignette shows that DMAR’s critical-value family reproduces those tables, walks through the parameter conventions that make the reproduction line up, and closes with a case where recomputation caught a typographical error in a printed table.
DMAR’s critical-value functions share a common surface
(alpha, df, a count of groups or comparisons,
and a tidy one-row return):
| Function | Distribution | Appendix table it reproduces |
|---|---|---|
cv_t(), cv_z() |
Student’s t, standard normal | A.1 |
cv_tukey_hsd() |
studentized range | A.4 |
cv_smm() |
studentized maximum modulus | A.5 |
cv_dunnett() |
many-to-one (Dunnett) | A.6, A.7 |
cv_bryant_paulson() |
ANCOVA generalized studentized range | A.8 |
cv_scheffe() |
Scheffe (an F transformation) | derived from A.2 |
The base-R quantile functions qt(), qf(),
qtukey(), and qchisq() cover the t,
F, studentized range, and chi square tables directly; DMAR adds
the multiple-comparison distributions that base R does not ship.
Appendix Table A.1 is one-tailed t critical values at the
Bonferroni-spaced levels .05, .05/2, .05/4, .05/6, .05/8, .05/10. Each
cell is qt(1 - alpha, df):
alpha1 <- 0.05 / c(1, 2, 4, 6, 8, 10)
tab <- sapply(alpha1, function(a) round(qt(1 - a, df = c(5, 10, 20, 60)), 2))
dimnames(tab) <- list(df = c(5, 10, 20, 60), alpha1 = signif(alpha1, 3))
tab
#> alpha1
#> df 0.05 0.025 0.0125 0.00833 0.00625 0.005
#> 5 2.02 2.57 3.16 3.53 3.81 4.03
#> 10 1.81 2.23 2.63 2.87 3.04 3.17
#> 20 1.72 2.09 2.42 2.61 2.74 2.85
#> 60 1.67 2.00 2.30 2.46 2.58 2.66The printed value at df = 1, alpha = .05 is 6.31 = 6.31; the
infinite-df row is the normal quantile cv_z(). A subtlety
worth noting: the fourth column’s header prints “.0083”, but the exact
level is 1/6 of .05 = .008333…, and it is the exact fraction that
reproduces the printed cells. A literal .0083 would move a dozen cells
by 0.01.
Tukey’s studentized range is qtukey(), wrapped by
cv_tukey_hsd():
# Six groups, 14 error df, alpha = .05: Appendix A.4 prints 4.64.
qtukey(0.95, nmeans = 6, df = 14)
#> [1] 4.638538The studentized maximum modulus (cv_smm()) needs a word
about its index. Its argument is n_comparisons, the number
m of simultaneous statistics. When the maximum modulus is used
for all pairwise comparisons among a groups, that number is
m = a(a-1)/2, not a. So the “Number of Groups” column
of a printed SMM table maps to
n_comparisons = a * (a - 1) / 2:
# Appendix A.5, alpha = .05, "groups" a = 4, 5, 6 in the large-sample limit.
a <- c(4, 5, 6)
data.frame(groups = a, n_comparisons = a * (a - 1) / 2,
cv = round(sapply(a * (a - 1) / 2, function(m)
cv_smm(.05, df = Inf, n_comparisons = m, verbose = FALSE)$value), 2),
printed = c(2.63, 2.80, 2.93))
#> groups n_comparisons cv printed
#> 1 4 6 2.63 2.63
#> 2 5 10 2.80 2.80
#> 3 6 15 2.93 2.93(cv_smm() evaluates the one-dimensional integral that
defines the maximum modulus and inverts it deterministically, so the
value carries no Monte Carlo error and needs no seed.)
cv_dunnett() compares each of several treatments to a
single control. Its n_comparisons argument is the number of
treatments, that is, a - 1 where a counts all groups
including the control:
# Two-sided, a = 4 groups (3 treatments vs control), 10 error df: A.6 prints 2.76.
cv_dunnett(alpha_level = .05, df = 10, n_comparisons = 3, alternative = "not_equal")| term | value | area_less | area_greater |
|---|---|---|---|
| upper_cv | 2.76 | 0.95 | 0.05 |
With one treatment the many-to-one family collapses to a single two-sided t test, a useful check on the argument mapping:
c(dunnett = cv_dunnett(.05, df = 10, n_comparisons = 1,
alternative = "not_equal")$value,
t = qt(1 - .05/2, df = 10))
#> dunnett t
#> 2.228139 2.228139Appendix A.6 and A.7 reproduce Dunnett’s own tables. Recomputed with
the exact integral above, the printed values agree throughout except for
a scattering of cells at the .01 level that differ by 0.01 in the last
digit, a property of the historical tabulation rather than a
transcription error. (Reaching for a Monte Carlo multivariate- quantile
instead of the deterministic integral would introduce sampling error of
its own, several hundredths at the .01 level, which is why
cv_dunnett() integrates rather than simulates.)
The Bryant-Paulson generalized studentized range is the
analysis-of-covariance analogue of Tukey’s range, for comparing
covariate-adjusted means when the covariate is random.
It is the one distribution here that most software, including base R,
does not compute natively; cv_bryant_paulson() and the
underlying qbryant_paulson() supply it. (For its use in a
full ANCOVA analysis see the bryant_paulson_ancova
vignette.)
It reproduces the published anchor exactly:
# Bryant & Paulson (1976) Table 1a: q_{.05; p=1, a=6, nu=14} = 4.83.
cv_bryant_paulson(alpha_level = .05, df = 14, groups = 6, covariates = 1,
verbose = FALSE)$value
#> [1] 4.829856Recomputing an entire block of Appendix Table A.8 (error df = 20, three covariates, alpha = .01) against the printed values shows a single cell out of step:
a_vals <- c(2, 3, 4, 5, 6, 7, 8, 10, 12, 16, 20)
recomputed <- sapply(a_vals, function(k)
cv_bryant_paulson(.01, df = 20, groups = k, covariates = 3,
verbose = FALSE)$value)
printed <- c(4.35, 5.03, 5.45, 5.75, 6.09, 6.19, 6.36, 6.63, 6.85, 7.19, 7.45)
data.frame(a = a_vals, printed = printed, recomputed = round(recomputed, 2),
diff = round(recomputed, 2) - printed)
#> a printed recomputed diff
#> 1 2 4.35 4.35 0.0
#> 2 3 5.03 5.03 0.0
#> 3 4 5.45 5.45 0.0
#> 4 5 5.75 5.75 0.0
#> 5 6 6.09 5.99 -0.1
#> 6 7 6.19 6.19 0.0
#> 7 8 6.36 6.36 0.0
#> 8 10 6.63 6.63 0.0
#> 9 12 6.85 6.85 0.0
#> 10 16 7.19 7.19 0.0
#> 11 20 7.45 7.45 0.0Every cell agrees except a = 6, where the table prints 6.09 and the distribution gives 5.99. Two arguments settle which is right. First, the critical value is concave in the number of means, so the increments across the row must decrease; 5.99 keeps them decreasing while 6.09 forces a reversal:
rbind(printed_increments = diff(printed),
recomputed_increments = round(diff(recomputed), 2))
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> printed_increments 0.68 0.42 0.3 0.34 0.1 0.17 0.27 0.22 0.34 0.26
#> recomputed_increments 0.68 0.42 0.3 0.24 0.2 0.17 0.27 0.22 0.34 0.26Second, a direct Monte Carlo of the statistic, drawn from its definition as the range of a standard normals divided by an independent scale, agrees with the computed 5.99, not with 6.09:
G <- 2e5; a <- 6; nu <- 20; p <- 3
R <- apply(matrix(rnorm(G * a), ncol = a), 1, function(z) max(z) - min(z))
S <- sqrt(rchisq(G, nu) / nu)
delta <- rbeta(G, (nu + 1) / 2, p / 2) # covariate-shrinkage factor
Q <- R / (S * sqrt(delta)) # Bryant-Paulson statistic
c(simulated_99th_pctile = round(quantile(Q, 0.99, names = FALSE), 2),
cv_bryant_paulson = round(cv_bryant_paulson(.01, df = 20, groups = 6,
covariates = 3, verbose = FALSE)$value, 2))
#> simulated_99th_pctile cv_bryant_paulson
#> 6.01 5.99The printed 6.09 is a typographical error for 5.99. Recomputation is not just a way to avoid typing a table by hand; it is a way to check the tables one has.
Bryant, J. L., & Paulson, A. S. (1976). An extension of Tukey’s method of multiple comparisons to experimental designs with random concomitant variables. Biometrika, 63, 631–638.
Dunnett, C. W. (1964). New tables for multiple comparisons with a control. Biometrics, 20, 482–491.
Maxwell, S. E., Delaney, H. D., & Kelley, K. (2027). Designing experiments and analyzing data: A model comparison perspective (4th ed.). Routledge.