| Title: | p-Values of Multinomial Goodness-of-Fit Tests by Fourier Series Inversion |
|---|---|
| Description: | Computes p-values of multinomial goodness-of-fit tests based on an additive statistic: the log-likelihood ratio, Pearson's chi-squared, the Cressie-Read power divergence, and the exact multinomial test. The main function, pval_fourier(), writes the p-value as a Fourier series in the moment-generating function of the statistic and sums it until an adaptive stopping rule is met, which makes small p-values of large instances computationally feasible. The function pval_exact() computes the exact p-value by branch pruning with bounds from a dynamic program, and pval_flexible() chooses between the two according to the size of the instance. |
| Authors: | Charles Thraves [aut], Pablo Rivas [aut, cre], Felipe Subiabre [aut] |
| Maintainer: | Pablo Rivas <[email protected]> |
| License: | GPL (>= 2) |
| Version: | 1.0 |
| Built: | 2026-10-30 11:28:36 UTC |
| Source: | https://github.com/pabrvss/multinomfourier |
Computes p-values of multinomial goodness-of-fit tests based on an additive statistic: the log-likelihood ratio, Pearson's chi-squared, the Cressie-Read power divergence, and the exact multinomial test. The main function, pval_fourier(), writes the p-value as a Fourier series in the moment-generating function of the statistic and sums it until an adaptive stopping rule is met, which makes small p-values of large instances computationally feasible. The function pval_exact() computes the exact p-value by branch pruning with bounds from a dynamic program, and pval_flexible() chooses between the two according to the size of the instance.
MultinomFourier computes the p-value of a
multinomial goodness-of-fit test, where is the observation (the vector
of observed counts in the categories, with
trials), under the null hypothesis
with category probabilities , and is an additive statistic: the
log-likelihood ratio , Pearson's , the
Cressie–Read power divergence, or the probability mass function of the
multinomial (exact multinomial test). It is aimed at small p-values, far in
the tail, and at instances where the support of the multinomial is too large
to sum over.
The package provides three functions:
pval_fourier: writes the p-value as a Fourier series whose
terms are values of the moment-generating function of the statistic, and
sums it until an adaptive stopping rule is met. Its cost grows slowly with
and ; its accuracy depends on the statistic (fast convergence
for and the probability mass function, possibly none for
).
pval_exact: exact p-value by branch pruning with bounds
from a dynamic program, without enumerating the support. Feasible for a
moderate number of categories and trials.
pval_flexible: chooses between the two according to the
size of the support.
All three return an object of class "multinomfourier"; check its
status component before using the p-value of pval_fourier.
Maintainer: Pablo Rivas [email protected]
Authors:
Pablo Rivas [email protected]
Charles Thraves
Felipe Subiabre
Subiabre, F. and Thraves, C. Efficient computation of p-values for multinomial tests. Manuscript.
Report bugs at https://github.com/PabRvss/MultinomFourier/issues.
Prints the method, statistic, p-value, status and total runtime and, for the
Fourier method, also the tilt gamma, the time spent choosing it, the
period half-width W and the number of terms.
## S3 method for class 'multinomfourier' print(x, ...)## S3 method for class 'multinomfourier' print(x, ...)
x |
Object of class |
... |
Additional arguments (currently unused). |
Invisibly returns x.
# Note: Reported p-values in comments are illustrative and may vary slightly # across versions or due to changes in numerical criteria. p <- c(0.1, 0.2, 0.3, 0.4) x <- c(35, 30, 60, 75) # Fourier method: also shows gamma, W and the number of terms fit_fourier <- pval_fourier(x, p, stat = "llr") fit_fourier # same as print(fit_fourier) # Exact method fit_exact <- pval_exact(x, p, stat = "llr") fit_exact# Note: Reported p-values in comments are illustrative and may vary slightly # across versions or due to changes in numerical criteria. p <- c(0.1, 0.2, 0.3, 0.4) x <- c(35, 30, 60, 75) # Fourier method: also shows gamma, W and the number of terms fit_fourier <- pval_fourier(x, p, stat = "llr") fit_fourier # same as print(fit_fourier) # Exact method fit_exact <- pval_exact(x, p, stat = "llr") fit_exact
Computes the exact p-value ,
, of a multinomial goodness-of-fit
test, up to floating-point rounding. Instead of enumerating the support, it
prunes whole groups of outcomes using bounds on the statistic (see Details).
pval_exhaustive() is a deprecated alias.
pval_exact( x, p = rep(1/length(x), length(x)), stat = c("llr", "pd", "chi2", "pmf"), lambda = NULL, rescale_p = FALSE, n_threads = min(2L, parallel::detectCores()), max_time = NULL, verbose = FALSE, ... ) pval_exhaustive(...)pval_exact( x, p = rep(1/length(x), length(x)), stat = c("llr", "pd", "chi2", "pmf"), lambda = NULL, rescale_p = FALSE, n_threads = min(2L, parallel::detectCores()), max_time = NULL, verbose = FALSE, ... ) pval_exhaustive(...)
x |
Vector of observed counts, one per category (non-negative integers,
without missing values). Their sum is the number of trials |
p |
Vector of category probabilities under the null hypothesis, all
strictly positive. They must sum to 1 (up to a tolerance of
|
stat |
Test statistic: |
lambda |
Parameter |
rescale_p |
Logical; if |
n_threads |
Number of threads (positive integer, capped at the number of
available cores). Default |
max_time |
Optional time limit in seconds. |
verbose |
Logical; if |
... |
Advanced options passed to the C engine. Unknown names produce a warning. |
The statistics are those of pval_fourier. Because the statistic
is additive, a dynamic program over the states (category, trials
left) first computes the exact minimum and maximum that the remaining
categories can add to it, at cost . Counts are then assigned one
category at a time, in decreasing order of probability; whenever the bounds
show that every completion of a partial assignment is (or none is) at least as
extreme as the observed data, its whole probability is added (or it is
discarded) without visiting it. Only partial assignments near the boundary
are expanded.
The cost still grows quickly with the number of categories . As a rough
guide, in our tests with the log-likelihood ratio on 8 threads, instances with
and took under five minutes, while
with did not finish within an hour. For large instances use
pval_fourier.
An object of class "multinomfourier": a list with components
The input counts and null probabilities (p after
rescaling, if rescale_p = TRUE).
The observed value of the test statistic .
The exact p-value, up to floating-point rounding; NA if
max_time was reached or if the p-value is smaller than .
Natural logarithm of the exact p-value. Preserves the exact
magnitude when pval is smaller than and reported as NA.
Name of the test statistic, e.g. "Log-likelihood ratio"
or "power-divergence (lambda = 0.6667)".
"exact".
0: the computation finished; 1:
max_time was reached before it finished.
Human-readable version of status. For status = 0
it reads "Converged", a label shared with pval_fourier.
Total wall-clock time in seconds.
The components that describe the Fourier series are also present, so that
results of both methods have the same structure, but are always NA:
Poissonized, gamma, time_gamma, W, n_terms, Q, D_Q,
err_rel_ext, err_rel_est, n_eff and nu_max_ratio.
# Note: Reported p-values in comments are illustrative and may vary slightly # across versions or due to changes in numerical criteria. p <- c(0.1, 0.2, 0.3, 0.4) x <- c(35, 30, 60, 75) # N = 200 # Log-likelihood ratio fit <- pval_exact(x, p, stat = "llr") fit fit$pval # 0.00684 fit$status # 0: computation finished # Probability mass function (exact multinomial test) fit_pmf <- pval_exact(x, p, stat = "pmf") fit_pmf$pval # 0.00578 # Pearson's chi-square fit_chi2 <- pval_exact(x, p, stat = "chi2") fit_chi2$pval # 0.00294# Note: Reported p-values in comments are illustrative and may vary slightly # across versions or due to changes in numerical criteria. p <- c(0.1, 0.2, 0.3, 0.4) x <- c(35, 30, 60, 75) # N = 200 # Log-likelihood ratio fit <- pval_exact(x, p, stat = "llr") fit fit$pval # 0.00684 fit$status # 0: computation finished # Probability mass function (exact multinomial test) fit_pmf <- pval_exact(x, p, stat = "pmf") fit_pmf$pval # 0.00578 # Pearson's chi-square fit_chi2 <- pval_exact(x, p, stat = "chi2") fit_chi2$pval # 0.00294
Computes the p-value with pval_exact when of the
support size is strictly less than enum_cutoff, and with pval_fourier
when it is greater than or equal to enum_cutoff.
pval_flexible( x, p = rep(1/length(x), length(x)), stat = c("llr", "pd", "chi2", "pmf"), lambda = NULL, rescale_p = FALSE, enum_cutoff = 10, undersampling = 1, rel_eps = 0.001, B = 50, max_terms = 10000, max_time = NULL, exact_terms = FALSE, n_threads = min(2L, parallel::detectCores()), precompute = TRUE, verbose = FALSE, ... )pval_flexible( x, p = rep(1/length(x), length(x)), stat = c("llr", "pd", "chi2", "pmf"), lambda = NULL, rescale_p = FALSE, enum_cutoff = 10, undersampling = 1, rel_eps = 0.001, B = 50, max_terms = 10000, max_time = NULL, exact_terms = FALSE, n_threads = min(2L, parallel::detectCores()), precompute = TRUE, verbose = FALSE, ... )
x |
Vector of observed counts, one per category (non-negative integers,
without missing values). Their sum is the number of trials |
p |
Vector of category probabilities under the null hypothesis, all
strictly positive. They must sum to 1 (up to a tolerance of
|
stat |
Test statistic: |
lambda |
Parameter |
rescale_p |
Logical; if |
enum_cutoff |
Threshold on |
undersampling |
Positive factor dividing the half-width |
rel_eps |
Relative tolerance of the stopping rule ( |
B |
Non-negative integer used by the term-size stopping rule: the series
stops after |
max_terms |
Maximum number of series terms (positive integer). Default
10000. If it is reached before the stopping rule is met, |
max_time |
Optional time limit in seconds, passed to the chosen method. |
exact_terms |
Logical; if |
n_threads |
Number of threads, passed to whichever method is chosen.
Default |
precompute |
Logical; if |
verbose |
Logical; if |
... |
Advanced options passed to the C engine, for example
|
The cost of pval_exact depends strongly on the observed counts,
not only on the support size, and it is highest far in the tail, where small
p-values are computed. With counts drawn from the null hypothesis it takes
about one second on 2 threads at a support of to
outcomes (log-likelihood ratio, ); with counts far in the
tail it can take tens of seconds at a support of (for example
, , ). The default
enum_cutoff = 10 is a simple, conservative value that keeps the exact
method for instances where it is fast in both situations.
When the Fourier method is chosen and does not meet its stopping rule
(status != 0), the result is returned as is; there is no automatic
fallback to the exact method.
An object of class "multinomfourier", as described in
pval_fourier; its method component ("exact" or
"fourier") shows which method was used.
# Note: Reported p-values in comments are illustrative and may vary slightly # across versions or due to changes in numerical criteria. # Small instance (K = 4, N = 200, about 10^6 outcomes): exact method p <- c(0.1, 0.2, 0.3, 0.4) x <- c(35, 30, 60, 75) fit <- pval_flexible(x, p, stat = "llr") fit fit$method # "exact" fit$pval # 0.00684 fit$status # 0: computation finished # Large instance (K = 8, N = 2000, about 10^19 outcomes): Fourier method x_large <- c(240, 260, 250, 270, 230, 250, 245, 255) fit_large <- pval_flexible(x_large, stat = "llr") fit_large$method # "fourier" fit_large$pval # 0.761 fit_large$status # 0: stopping rule met# Note: Reported p-values in comments are illustrative and may vary slightly # across versions or due to changes in numerical criteria. # Small instance (K = 4, N = 200, about 10^6 outcomes): exact method p <- c(0.1, 0.2, 0.3, 0.4) x <- c(35, 30, 60, 75) fit <- pval_flexible(x, p, stat = "llr") fit fit$method # "exact" fit$pval # 0.00684 fit$status # 0: computation finished # Large instance (K = 8, N = 2000, about 10^19 outcomes): Fourier method x_large <- c(240, 260, 250, 270, 230, 250, 245, 255) fit_large <- pval_flexible(x_large, stat = "llr") fit_large$method # "fourier" fit_large$pval # 0.761 fit_large$status # 0: stopping rule met
Computes the p-value of a multinomial goodness-of-fit test,
for an additive test statistic , by summing a truncated Fourier series
whose terms are values of the moment-generating function of the statistic.
The series is truncated by an adaptive stopping rule, so the result is an
approximation whose accuracy depends on the statistic and the instance; always
check the returned status.
pval_fourier( x, p = rep(1/length(x), length(x)), stat = c("llr", "pd", "chi2", "pmf"), lambda = NULL, rescale_p = FALSE, undersampling = 1, rel_eps = 0.001, B = 50, max_terms = 10000, max_time = NULL, n_threads = min(2L, parallel::detectCores()), exact_terms = FALSE, precompute = TRUE, verbose = FALSE, ... )pval_fourier( x, p = rep(1/length(x), length(x)), stat = c("llr", "pd", "chi2", "pmf"), lambda = NULL, rescale_p = FALSE, undersampling = 1, rel_eps = 0.001, B = 50, max_terms = 10000, max_time = NULL, n_threads = min(2L, parallel::detectCores()), exact_terms = FALSE, precompute = TRUE, verbose = FALSE, ... )
x |
Vector of observed counts, one per category (non-negative integers,
without missing values). Their sum is the number of trials |
p |
Vector of category probabilities under the null hypothesis, all
strictly positive. They must sum to 1 (up to a tolerance of
|
stat |
Test statistic: |
lambda |
Parameter |
rescale_p |
Logical; if |
undersampling |
Positive factor dividing the half-width |
rel_eps |
Relative tolerance of the stopping rule ( |
B |
Non-negative integer used by the term-size stopping rule: the series
stops after |
max_terms |
Maximum number of series terms (positive integer). Default
10000. If it is reached before the stopping rule is met, |
max_time |
Optional time limit in seconds. |
n_threads |
Number of threads used to evaluate the series terms (positive
integer, capped at the number of available cores). Default
|
exact_terms |
Logical; if |
precompute |
Logical; if |
verbose |
Logical; if |
... |
Advanced options passed to the C engine, for example
|
Statistics. All supported statistics are additive,
, which is what the method requires. With
:
"llr": .
"chi2": .
"pd":
(Cressie and Read, 1984); gives and
gives .
"pmf": the probability mass function of the
multinomial under the null hypothesis (exact multinomial test). Small
values are the extreme ones, so the p-value is ,
the total probability of the outcomes that are at most as likely as the
observed one.
Which statistics converge. The p-value is written exactly as a Fourier
series for every additive statistic, but the number of terms needed for a given
accuracy depends on the statistic. For "llr" and "pmf" the series
typically converges in a few hundred to a few thousand terms. For "chi2",
and often for "pd" unless is close to 0 (
has failed on real data), the values of the statistic lie on or near a
lattice, the terms do not decay, and the series can fail to
converge within max_terms (status = 2) or even return a negative
value. In that case use pval_exact when it is feasible.
Stopping rule and reported value. The
partial sums are accelerated with Wynn's epsilon algorithm (in the safeguarded
form of QUADPACK's DQELG) and smoothed with a running median of five. The
series stops as soon as either (i) B consecutive terms are each smaller
than rel_eps / B relative to the partial sum, or (ii) the smoothed
extrapolated values vary by at most a relative rel_eps over a trailing
window covering the last half of the terms. The reported p-value is the last
smoothed extrapolated value.
Term evaluation. With exact_terms = FALSE (default), terms are
evaluated via a Poissonized Cauchy-integral evaluator, which is significantly
faster on large instances. With exact_terms = TRUE, terms are evaluated
via exact polynomial convolution.
Underflow below double precision. In standard IEEE 754 64-bit floating-point
arithmetic, positive numbers smaller than approximately
underflow to exact zero. Because multinomial p-values are mathematically strictly
positive, whenever the true p-value falls below , pval
is returned as NA and an informative console note is issued. The exact
magnitude is retained in log_pval, which can be exported or evaluated via
arbitrary-precision software (e.g. exp(log_pval)).
An object of class "multinomfourier": a list with components
The input counts and null probabilities (p after
rescaling, if rescale_p = TRUE).
The observed value of the test statistic .
The computed p-value, or NA if underflow occurs (when the
p-value is smaller than , below the 64-bit double
precision limit) or if the algorithm did not converge.
Natural logarithm of the p-value. When the p-value is smaller
than , pval is reported as NA to indicate
that it cannot be stored in 64-bit floating-point format without collapsing to
zero, but log_pval preserves the exact magnitude without underflow.
Name of the test statistic, e.g. "Log-likelihood ratio"
or "power-divergence (lambda = 0.6667)".
"fourier".
Logical; TRUE if the fast Poissonized Cauchy-integral evaluator was used (exact_terms = FALSE, the default), or FALSE if exact polynomial convolution was used (exact_terms = TRUE).
Total wall-clock time in seconds.
The exponential tilt applied to the statistic so
that the series terms stay within floating-point range (a saddlepoint
choice).
Time in seconds spent choosing gamma.
Half-width of the Fourier period,
over the range of the statistic minus its
observed value, divided by undersampling.
Number of series terms summed.
0: the stopping rule was met (the message reads
"Converged"); 1: max_time was reached; 2:
max_terms was reached without meeting the stopping rule, so
pval is unreliable; 3: the search for gamma failed;
4: numerical overflow in the evaluation of the terms. With a status
other than 0, pval is the last value reached or NA.
Human-readable version of status.
Relative spread of the smoothed extrapolated values over
the final window, i.e. the quantity that rule (ii) compares with
rel_eps. When the series stopped by rule (i) it can be larger
than rel_eps.
Estimate of the relative truncation error of the raw partial sum (not of the extrapolated value that is reported), based on a random-phase argument; in our tests it tends to underestimate the error.
Effective number of support points implied by the size of the
late terms; used by err_rel_est.
Largest size of the late transform values relative to the first one; values near 1 mean the terms are not decaying (a lattice-like statistic).
Number of equispaced points on the saddlepoint circle used to
extract the multinomial coefficient in each term of the series (a
-point trapezoid rule for Cauchy's integral). It is chosen
automatically, by doubling from a power of two until the aliasing
diagnostic D_Q falls below . NA with
exact_terms = TRUE.
Aliasing diagnostic . is a
computable lower bound on , which provides a
uniform upper bound on the aliasing error of each Fourier-series term.
For the probability-mass statistic ("pmf"),
approximates to first order. For the
log-likelihood-ratio statistic ("llr"), remains
a lower bound on , but no guarantee of closeness
is available. Stopping the doubling of when falls
below a threshold does not by itself guarantee that the aliasing error is
strictly below that threshold. D_Q measures an error separate from
the truncation of the series (reported by err_rel_ext and
err_rel_est). It is 0 when Q is large enough for the
Cauchy formula to be exact (no aliasing possible), and NA with
exact_terms = TRUE.
The last six components are diagnostics of the series. The four about
truncation (err_rel_ext, err_rel_est, n_eff and
nu_max_ratio) are heuristics, not error bounds. The names Gamma and Time_gamma used by earlier
versions still work, with a deprecation warning.
Subiabre, F. and Thraves, C. Efficient computation of p-values for multinomial tests. Manuscript.
Cressie, N. and Read, T. R. C. (1984). Multinomial goodness-of-fit tests. Journal of the Royal Statistical Society, Series B, 46(3), 440–464.
pval_exact for the exact p-value,
pval_flexible to choose between the two automatically.
# Note: Reported p-values and term counts in comments are illustrative and # may vary slightly across versions or due to changes in numerical criteria. p <- c(0.1, 0.2, 0.3, 0.4) x <- c(35, 30, 60, 75) # N = 200 # Log-likelihood ratio fit <- pval_fourier(x, p, stat = "llr") fit fit$pval # 0.00688 fit$status # 0: stopping rule met fit$n_terms # 1208 # Probability mass function (exact multinomial test) fit_pmf <- pval_fourier(x, p, stat = "pmf") fit_pmf$pval # 0.00577 fit_pmf$status # 0: stopping rule met # Power divergence: lambda is required fit_pd <- pval_fourier(x, p, stat = "pd", lambda = 2/3) fit_pd$pval # 0.00384 fit_pd$status # 0: stopping rule met# Note: Reported p-values and term counts in comments are illustrative and # may vary slightly across versions or due to changes in numerical criteria. p <- c(0.1, 0.2, 0.3, 0.4) x <- c(35, 30, 60, 75) # N = 200 # Log-likelihood ratio fit <- pval_fourier(x, p, stat = "llr") fit fit$pval # 0.00688 fit$status # 0: stopping rule met fit$n_terms # 1208 # Probability mass function (exact multinomial test) fit_pmf <- pval_fourier(x, p, stat = "pmf") fit_pmf$pval # 0.00577 fit_pmf$status # 0: stopping rule met # Power divergence: lambda is required fit_pd <- pval_fourier(x, p, stat = "pd", lambda = 2/3) fit_pd$pval # 0.00384 fit_pd$status # 0: stopping rule met