| Type: | Package |
| Title: | Exact Conditional Reference Distributions for Tables and Selected Linear Models |
| Version: | 0.2.0 |
| Description: | Reference distributions that are exact at every sample size, obtained by conditioning on a statistic sufficient for the nuisance parameter rather than by appealing to an asymptotic approximation. Two forms of conditioning are supplied. For a two-way table the row and column totals are sufficient for the marginal parameters, so conditioning on both margins leaves the Fisher-Yates law, sampled here exactly by random permutation of the column labels; statistics include Goodman's linear-by-linear association and, for nominal rows against an ordinal column, the dispersion of the row mean scores, whose components are the group contrasts themselves. A two-table form conditions further on the total score to cancel a common association parameter, giving a nuisance-free test that two tables share it. For a linear model under a null in which the tested structure is absent, a statistic invariant to the coefficients and the error scale depends on the data only through the residual direction, which is uniform on the sphere of the residual space; the reference stays exact when the statistic is maximised over candidate structures, and so remains valid after a spatial weight matrix has been chosen by fit. Every replicate draws from its own stream, so results do not depend on the thread count. The same C sources back the 'Python' package 'exactcond'. |
| URL: | https://github.com/mtloots/exactcond |
| BugReports: | https://github.com/mtloots/exactcond/issues |
| License: | GPL-3 |
| Encoding: | UTF-8 |
| Imports: | stats |
| NeedsCompilation: | yes |
| Config/testthat/edition: | 3 |
| Suggests: | testthat (≥ 3.0.0) |
| Config/roxygen2/version: | 8.0.0 |
| Packaged: | 2026-10-12 08:11:04 UTC; home |
| Author: | M. Theodor Loots |
| Maintainer: | M. Theodor Loots <theo.loots@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-10-21 16:30:14 UTC |
exactcond: exact conditional reference distributions
Description
Reference distributions exact at every sample size, obtained by conditioning on a statistic sufficient for the nuisance parameter rather than by an asymptotic approximation. Two conditionings are supplied: the Fisher-Yates law of a two-way table given both margins, and the uniform law of the residual direction on the sphere of the residual space.
What is here and why
ec_rc_testone table, both margins fixed. The dispersion statistic is for nominal rows against an ordinal column and is built from the row mean scores themselves, so where the column score is a signed difference the test is on the reported contrasts rather than on ranks of a variable that is almost all ties.
ec_rc_twotwo tables, conditioning further on the total score, which the common association parameter multiplies and which the conditioning has fixed, so that parameter leaves the density altogether.
ec_sphere_maxquada statistic maximised over candidate structures, whose exact null follows from the residual direction being uniform on the sphere. This is what makes inference valid AFTER a spatial weight matrix has been chosen by fit, where the usual chi-squared reference rejects a true null far too often.
What is deliberately absent
Nothing here is a bootstrap and nothing is asymptotic. A test that is exact by analytic means, such as one whose null is obtained by inverting a characteristic function, needs no simulation and has no place in this package.
Author(s)
Maintainer: M. Theodor Loots theo.loots@gmail.com (ORCID)
Authors:
M. Theodor Loots theo.loots@gmail.com (ORCID)
See Also
Useful links:
A differential network without a Gaussian assumption
Description
Which pairs of variables are associated differently in two or more groups, tested without
assuming the data are Gaussian. The edge statistic is the heterogeneity of the Fisher-transformed
correlations across groups, \sum_a (n_a - 3)(z_a - \bar z)^2, the same quantity the Gaussian
theory uses; what changes is the reference. Instead of a chi-square that holds only under
normality, the statistic is referred to the law obtained by permuting the group labels, which
conditions on the observed data and the group sizes and is therefore exact whatever the marginals
are. With rank = TRUE the correlations are Spearman's, so the whole procedure is
distribution-free.
Usage
ec_diffnet_test(x, group, cluster = NULL, rank = TRUE, B = 20000L, seed = 1L)
Arguments
x |
a matrix or data frame, variables in columns, respondents in rows. |
group |
a factor or vector giving each respondent's group. |
cluster |
an optional cluster per respondent, every cluster inside one group; when given the labels are permuted across clusters, each keeping its members. |
rank |
replace each variable by its ranks, average ties, before correlating. The default,
and what makes the test distribution-free; set |
B |
number of permutations. |
seed |
integer seed; each permutation has its own stream. |
Details
A network has p(p-1)/2 edges, so testing them one at a time needs a correction
that costs most of the power. The same permutations give ONE statistic for the whole network,
the sum of the edge statistics when many edges differ a little and the maximum when one edge
differs a lot, hence ONE p-value for "is the network different at all"; the per-edge p-values
are then a closed follow-up, valid without further correction once the omnibus has rejected.
Nothing is corrected because nothing is repeated.
Value
A list with sum and max over the p(p-1)/2 edges, each holding the
observed statistic, the exact p-value, its mid-p companion, a Wilson interval and the largest
null draw; and edges, a data frame of every edge with its statistic and the unadjusted
per-edge p-value from the same permutations.
Examples
set.seed(1)
n <- 120; p <- 5
g <- rep(c("a", "b"), each = n / 2)
z <- matrix(rnorm(n * p), n)
z[g == "b", 2] <- 0.8 * z[g == "b", 1] + 0.6 * z[g == "b", 2] # one edge differs
ec_diffnet_test(z, g, B = 999, seed = 1)
One exact test for a battery of items across groups
Description
Several categorical items are recorded on the same respondents, who fall into groups, and the
question is whether ANY item differs across the groups. Rather than test each item and pay a
multiplicity correction, one statistic is formed, the sum or the maximum over items of a
per-item statistic, and referred to the law obtained by permuting the group labels. That law
conditions on every item's margin and on the group sizes, so it is exact conditional in the
sense of ec_rc_test, and it gives ONE p-value for the whole battery. The per-item
statistics are referred to the same permutations, which supplies the unadjusted per-item
p-values a closed procedure uses once the omnibus has rejected.
Usage
ec_multi_test(
x,
group,
cluster = NULL,
stat = c("pearson", "dispersion"),
B = 20000L,
seed = 1L
)
Arguments
x |
a matrix or data frame of items, one column per item, each a factor or integer coded from zero (or from one, which is shifted), with respondents in rows. |
group |
a factor or vector giving each respondent's group. |
cluster |
an optional factor or vector giving each respondent's cluster, every cluster lying inside one group. When supplied the labels are permuted across clusters, each cluster keeping its members, which is the exact treatment of intra-cluster correlation such as an interviewer or fieldworker effect. |
stat |
|
B |
number of permutations. |
seed |
integer seed; each permutation uses its own stream, so the result does not depend on the thread count. |
Details
The sum has power against many items moving a little; the maximum against one item
moving a lot, and it is the one that matches or beats a Bonferroni-corrected battery while
holding its size. With one binary item and no clusters, "pearson" is the exact test of
the two-way table and agrees with ec_rc_test in distribution.
Value
A list with sum and max, each holding the observed statistic, the exact
p-value, its mid-p companion, a Wilson interval for the Monte Carlo error and the largest
null draw; items, a data frame of the per-item statistics and unadjusted per-item
p-values from the same permutations; and the sizes of the groups and, if given, the number
of clusters.
Examples
set.seed(1)
g <- rep(c("a", "b", "c"), c(30, 40, 30))
x <- cbind(rbinom(100, 1, 0.9), rbinom(100, 1, 0.95), rbinom(100, 1, ifelse(g == "c", 0.7, 0.9)))
ec_multi_test(x, g, B = 2000, seed = 1)
Tail probabilities from an observed value and a null sample
Description
Tail probabilities from an observed value and a null sample
Usage
ec_pvalue(obs, null)
Arguments
obs |
the observed statistic. |
null |
a vector of draws from the null. |
Value
A list with the p-value, its mid-p companion, and a Wilson interval reflecting the Monte Carlo error in the number of draws supplied.
Examples
ec_pvalue(2, rnorm(1000))
Null draws of a table statistic given both margins
Description
Null draws of a table statistic given both margins
Usage
ec_rc_null(
rows,
cols,
stat = c("dispersion", "linlin", "pearson", "lr"),
u = NULL,
v = NULL,
B = 20000L,
seed = 1L
)
Arguments
rows, cols |
the fixed row and column totals. |
stat |
one of |
u, v |
scores for the rows and columns; defaults |
B |
number of draws from the conditional law. |
seed |
integer seed. Each replicate uses its own stream, so the result does not depend on the number of threads. |
Value
A numeric vector of B draws.
Examples
ec_rc_null(c(30, 40), c(35, 35), B = 100, seed = 2)
Exact conditional test in a two-way table
Description
Conditions on BOTH margins, which are sufficient for the marginal parameters, and so leaves a null distribution carrying no nuisance parameter at all. That distribution is sampled exactly, by random permutation of the column labels; nothing here is asymptotic.
Usage
ec_rc_test(
x,
stat = c("dispersion", "linlin", "pearson", "lr"),
u = NULL,
v = NULL,
B = 20000L,
seed = 1L
)
Arguments
x |
an r by c table or integer matrix of counts. |
stat |
one of |
u, v |
scores for the rows and columns; defaults |
B |
number of draws from the conditional law. |
seed |
integer seed. Each replicate uses its own stream, so the result does not depend on the number of threads. |
Details
"dispersion" is for NOMINAL rows against an ORDINAL column and is
\sum_i n_i (\bar v_i - \bar v)^2. Its components are the row mean scores themselves, so
in an application where the column score is a signed difference the statistic is built out of
the very contrasts being reported, rather than out of ranks. "linlin" is Goodman's
\sum_{ij} u_i v_j x_{ij}, sufficient for the linear-by-linear association parameter given
both margins.
When p.value is at 1/(B+1) the observed statistic exceeded every draw, so the
test has hit its resolution floor and the honest report is an upper bound together with
max_null, not a p-value.
Value
A list with the observed statistic, the exact conditional p-value and its mid-p
companion, a Wilson interval for the p-value reflecting the Monte Carlo error in B
draws, the largest null draw seen, and the components of the statistic.
Examples
tab <- matrix(c(3, 228, 72, 10, 548, 67, 7, 276, 131), nrow = 3, byrow = TRUE)
ec_rc_test(tab, v = c(-1, 0, 1), B = 2000, seed = 1)
Exact conditional test that two tables share an association parameter
Description
Conditions on both margins WITHIN each table and then on the total score across them. Under the null of a common linear-by-linear parameter the joint density carries that parameter only through the total score, which the conditioning has fixed, so it leaves the density altogether: what remains carries no unknown parameter at all. Nothing is exchanged between the groups, so the test stays valid when the two tables have very different margins, which is exactly where permuting a group label fails.
Usage
ec_rc_two(
t1,
t2,
u = NULL,
v = NULL,
B = 20000L,
burn = 20000L,
thin = 20L,
alpha = 0.05,
seed = 1L
)
Arguments
t1, t2 |
the two tables, of the same dimension. |
u, v |
integer scores; required to be integer valued so the total score lies on a lattice. |
B |
number of recorded draws. |
burn |
moves discarded before recording begins. |
thin |
moves between recorded draws. |
alpha |
level at which the attainable size is reported. |
seed |
integer seed. |
Details
The law is sampled by moves that make a two by two swap in EACH table with equal and opposite score changes, so both sets of margins and the total score are preserved, and the chain starts at the observed pair.
That last point is what makes this usable. The obvious construction instead estimates each
table's null distribution separately and convolves them, which requires the observed score to
be reachable under NO association; for a strongly associated table it is not, and no increase
in B repairs it. Worse, the failure arrives precisely when BOTH tables are strongly
associated, which is the case where the correct answer is a large p-value, so the convolution
fails in the direction a reader would take for evidence of a difference.
Successive draws are dependent, so accuracy is governed by the chain rather than by
independent sampling. Check accept and raise burn or thin if it is low.
Value
A list with the conditional mid-p value, the attainable size at alpha, the two
conditioning statistics, and the acceptance rate of the sampler.
Examples
a <- matrix(c(20, 8, 3, 9, 22, 7, 4, 8, 19), 3, byrow = TRUE)
b <- matrix(c(18, 9, 4, 8, 20, 8, 5, 9, 18), 3, byrow = TRUE)
ec_rc_two(a, b, B = 2000, seed = 3)
Integer scores for an ordinal margin
Description
Integer scores for an ordinal margin
Usage
ec_scores(k, centre = FALSE)
Arguments
k |
number of categories. |
centre |
if TRUE, centre the scores on zero. |
Details
The dispersion and linear-by-linear statistics are unchanged in distribution by an
affine transformation of the scores, so 0:(k-1) and a centred version give identical
p-values; centre exists only to make output easier to read.
Value
A numeric vector of length k.
Examples
ec_scores(3)
Exact null for a statistic maximised over candidate structures
Description
Under a null in which the tested structure does not enter the data-generating process, a statistic invariant to the regression coefficients and to the error scale depends on the data only through the residual direction, which is uniform on the unit sphere of the residual space whatever those nuisance parameters are. Maximising over candidates inside each draw gives the exact null of the statistic a practitioner actually reports after selecting a structure by fit, and it is valid for ANY selection rule that is a function of that direction.
Usage
ec_sphere_maxquad(M, A, B = 20000L, seed = 1L)
Arguments
M |
the residual maker, a symmetric idempotent n by n matrix. |
A |
a single n by n symmetric matrix, or a list of them, one per candidate. |
B |
number of draws from the conditional law. |
seed |
integer seed. Each replicate uses its own stream, so the result does not depend on the number of threads. |
Value
A numeric vector of B draws of the maximised quadratic form.
Examples
n <- 12; X <- cbind(1, seq_len(n))
M <- diag(n) - X %*% solve(crossprod(X), t(X))
A <- diag(n)[, c(n, seq_len(n - 1))]; A <- (A + t(A)) / 2
quantile(ec_sphere_maxquad(M, A, B = 500, seed = 4), 0.95)