Repository navigation
Combine p-values across studies and measure their replication - #58
Merged
Merged
Conversation
combine_p_values and partial_conjunction combine each gene's p-values across studies (Fisher, Stouffer, Tippett, Wilkinson, Pearson, logit, Simes; signed, so that changes in opposite directions do not add up), proportion_of_true_nulls estimates Storey's pi0 as qvalue's pi0est does, and differential_expression_replication reports, for the studies of a CubeCollection, the combined and replication p-values of every gene, sign agreement, heterogeneity, pi1 between every pair of studies and the genes found in at least k studies, with two figures. Checked against scipy, metap, qvalue, metafor and mpmath (validation/combined_p_values_and_replication.py: 54 checks, none failed).
parashardhapola
force-pushed
the
49-combined-p-values-and-replication
branch
from
October 26, 2026 20:18
9383780 to
12ddeba
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
One study's list of changed genes rarely replicates in the next, so a result of several studies should say how well they agree next to what they find together. This adds combined p-values of several studies (Fisher, Stouffer with weights, Tippett, Wilkinson, Pearson, Mudholkar and George, Simes), one-sided or two-sided with the directions of the effects; Benjamini and Heller's partial conjunction p-value that an effect is found in at least u studies; Storey's proportion of true nulls (pi0, and pi1) as qvalue's
pi0estcomputes it; and, for the studies of aCubeCollection, a replication analysis with sign agreement, heterogeneity, pi1 between studies and the genes found in k studies, with two figures. Each is checked against scipy, metap, qvalue, metafor and mpmath.Changes
cytepool.stats.combined_p_values.combine_p_values(p_values, *, method, weights, directions, rank): combines every feature's p-values (studies x features, NaN where a study did not test a feature) into one p-value of the global null hypothesis; returnsCombinedPValues.cytepool.stats.combined_p_values.partial_conjunction(p_values, at_least, *, method, directions): the p-value that at least u of the studies have an effect, by Simes's, Fisher's or Stouffer's combination of the n - u + 1 largest p-values; returnsCombinedPValues.cytepool.stats.combined_p_values.CombinedPValues: p-value, statistic, one-sided p-values up and down and the direction (with directions), and the number of studies per feature.cytepool.stats.multiple_testing.proportion_of_true_nulls(p, *, lambdas, method, smooth_df, smooth_log): pi0 as qvalue'spi0est, at one lambda, smoothed, or by the bootstrap; returnsTrueNullProportion(pi0,pi1,lambdas,pi0_lambda,pi0_smooth).cytepool.replication.differential_expression_replication(collection, results, group, contrast, *, alpha, method, at_least, conjunction_method, weights, meta_method, null_proportion): reads one cell type and contrast throughalign_study_resultsand returns aReplicationResultwith the tablesgenes,study_pairs,found_inandstudies, settings and provenance.cytepool.plotting.plot_study_agreement: a heatmap of pi1, or sign agreement, of every ordered pair of studies.cytepool.plotting.plot_replicated_genes: bars of the genes that exactly k studies find, split by whether every study finds them in the same direction.brent_minimum(private) takes an optional relative precision; a private smoothing spline incytepool.stats.smoothingreproduces R'ssmooth.spline(x, y, df = df).combined_p_valuesandreplication, three rows of the capability matrix, 14 references (DOIs checked against Crossref) cited on the Bibliography page, the Validation page, the README's status table, planned list and limits, and the Overview.Design notes
directions(the signs of the effects), each two-sided p becomes p / 2 for an effect above zero and 1 - p / 2 below, as metap'stwo2one; both directions are combined and the p-value is min(1, 2 min(p_up, p_down)), the two-sided p-value of Z for Stouffer's method, so that effects in opposite directions cancel. The one-sided value near 0 is kept exactly (Stouffer's z is the quantile of p / 2 with the effect's sign, and every log is read from whichever of q and 1 - q is near 0), so p-values near 1e-300 keep their digits.combine_p_valuesis the partial conjunction with u = 1. Wilkinson's maximum (r = k) tests the global null, not that every study has an effect; the docs say so.seq(0.05, 0.95, 0.05), whose last value is 0.95 itself, not 0.05 + 18 x 0.05 = 0.9500000000000001; pi0 is the smoother's value at the largest lambda, capped at 1; the bootstrap is in closed form, the binomial variance of the count plus the squared distance of pi0(lambda) from the 10% quantile (type 7) of the pi0(lambda), first minimum (60 of 60 random cases identical); it drops NA.smooth.spline(black box, documented outputs only): the penalty matrix (itskeep.stuffoutput) integrates products of B-spline second derivatives with the weight 0.333 for the product of slopes, where the exact integral has 1/3; the ratio r uses the 3rd to the (nk - 3)th diagonal entries; the spar search is Brent's method with tolerance 1e-4 and relative precision 2e-8 (itstraceoutput shows the steps and tol1); and the fit returned is that of the last spar evaluated, not of the best spar it reports. With these, pi0 agrees with qvalue to about 1e-12.sumzandlogitp, and refuses one study;two2onerounds 1 - p / 2 to a double. scipy'scombine_pvalues(BSD) was read for its conventions: Pearson's statistic is reported as -X, the logit's before scaling; no code was adapted.Deliberate differences:
proportion_of_true_nullsgives an estimate where qvalue stops: where no p-value reaches the largest lambda (common for pi1 between studies: qvalue stopped for all 12 pairs of the validation with its defaults) and where the estimate is at most 0 (it is 0). The log smoother refuses a count of zero, as qvalue does. With more than 49 lambdas every lambda is a knot, where R keeps some (2.6e-5 relative for 96 lambdas).Validation
validation/combined_p_values_and_replication.py, 54 checks: 47 pass, 7 deliberate differences, none failed (67.4 s, 444 MiB peak). Reference: scipy 1.18.1, mpmath 1.3.0; R 4.4.3, metap 1.1, qvalue 2.38.0, jsonlite 2.0.0; metafor 5.0.1.combine_pvaluestwo2onekeeps the digits)p.adjustp.adjustpi0estpi0estp.adjust,rma.unipi0est, Rpi0estTests
tests/test_combined_p_values_and_replication.py, 90 tests, 1.1 s warm (22 s cold, compiling). They compare with fixtures the validation script writes (tests/data/combined_p_values_and_replication/, 72 KB: scipy, metap, mpmath, qvalue, and R's replication of three small cohorts rebuilt from their counts), with closed forms derived by hand (two studies, identical studies at Phi-bar(2), opposite signs, p-values of 0 and 1, one study, a gene in no study, the count of p-values at least lambda, p-values near 1e-300), and with invariants: the order of studies and of features, every direction turned around, a direction as the one-sided p-value of its sign, weights scaled together, Simes's partial conjunction growing with u, and the replication's columns against the public array functions applied toalign_study_results. Messages of invalid arguments are tested; the masked-input inventory and the private-names list have the new callables.Checks: the full suite on top of master, 4,571 passed, 18 skipped, also with bounds checks and with the rounding changed (
OPENBLAS_CORETYPE=Sandybridge, 4 threads,NUMBA_CPU_NAME=generic); ruff check and ruff format clean; mypy clean;make -C docs apiandmake -C docs html(Node.js 24) build;tests/test_documentation.py74 passed.