Cross-Dataset Reproducibility Audit of Endometriosis Diagnostic Gene Signatures via Permutation-Calibrated Overlap Testing
Cross-Dataset Reproducibility Audit of Endometriosis Diagnostic Gene Signatures via Permutation-Calibrated Overlap Testing
stepstep_labs
Abstract
Endometriosis affects ~10% of reproductive-age women yet averages 6.6 years to diagnose. Dozens of transcriptomic studies have proposed diagnostic gene signatures from public microarray data, but different studies routinely identify different "key genes." We ask whether the overlap between independently derived gene lists exceeds what chance alone predicts. Using three GPL570 Affymetrix datasets from GEO (GSE7305, n=20; GSE11691, n=18; GSE51981, n=148), we rank probes by differential expression and measure pairwise and three-way overlap of the top-N lists, calibrating each against a permutation null (500 label shuffles). At N=200, only one of three pairwise overlaps is significant (GSE7305 vs GSE11691: 15 probes, z=5.44, p<0.002); the remaining two are indistinguishable from chance (p=0.40, p=0.62). The three-way intersection is zero for all N≤500. Matching samples by menstrual cycle phase does not rescue cross-dataset reproducibility. Published single-dataset endometriosis gene signatures should be interpreted with extreme caution.
Introduction
Endometriosis is a chronic inflammatory condition in which endometrial-like tissue grows outside the uterus, affecting approximately 10% of reproductive-age women [1]. The average diagnostic delay is 6.6 years globally [2], driving sustained interest in molecular biomarkers that could enable earlier, non-invasive detection.
Over the past two decades, microarray and RNA-seq studies deposited in the Gene Expression Omnibus (GEO) have generated numerous candidate diagnostic gene lists. Reviews consistently note poor concordance across studies — for example, among 42 different dysregulated miRNAs reported across endometriosis studies, only one appeared in more than a single publication [3]. The menstrual cycle is a major confounder: Devesa-Peiro et al. [4] demonstrated that 44% more genes are identified after correcting for cycle phase bias, and that 31% of endometriosis transcriptomic studies did not even record cycle phase. Grewal et al. [5] formalized a Reproducibility Score quantifying how reliably a biomarker discovery pipeline produces the same feature set across resampled data, finding that small-sample datasets yield near-zero scores.
Zhao et al. [6] performed cross-study gene set enrichment analysis at the pathway level across six endometriosis datasets and found consistent pathway-level signals (e.g., immune activation), but did not test whether individual gene-level overlap exceeds chance. Patil et al. [7] showed that cross-sample normalization introduces test-set bias that inflates apparent reproducibility of gene signatures.
No prior work has applied a formal permutation-calibrated statistical test to the gene-level overlap between independently derived endometriosis diagnostic signatures. This study fills that gap.
Methods
Data acquisition and preprocessing
Three datasets were selected from GEO, all profiled on the Affymetrix GPL570 (HG-U133 Plus 2.0) platform:
- GSE7305 [8]: 10 ovarian endometriosis vs 10 normal endometrium samples.
- GSE11691 [9]: 9 eutopic endometrium vs 9 ectopic peritoneal endometriosis lesions (paired within-patient).
- GSE51981 [10]: 77 endometriosis vs 71 non-endometriosis eutopic endometrium samples.
Expression matrices were restricted to the 22,277 probes present across all three datasets. A variance filter retained the top 10,000 most variable probes to reduce noise.
Differential expression ranking
For each dataset, a Welch two-sample t-test was computed per probe (disease vs control). Probes were ranked by |t| and the top N selected. No multiple-testing correction was applied, as the goal is ranking rather than inference on individual probes.
Cross-dataset overlap
Pairwise overlaps (intersection size, Jaccard index) were computed for every pair of top-N probe sets, along with the three-way intersection.
Permutation null model
For each dataset independently, disease/control labels were shuffled uniformly at random 500 times. After each permutation, top-N probe sets were recomputed and overlap statistics recorded. The empirical p-value for each observed overlap is the fraction of permutations yielding an overlap ≥ the observed value. A z-score is computed as (observed − null mean) / null SD.
Menstrual cycle stratification
GSE51981 provides cycle phase metadata (Proliferative, Early Secretory, Mid-Secretory, Late Secretory). GSE7305 annotates Follicular and Luteal phases. Within-dataset phase contrast (Proliferative vs Secretory in GSE51981) and cross-dataset phase-matched analysis (GSE7305-Follicular vs GSE51981-Proliferative) were performed.
Sensitivity analysis
The overlap threshold N was varied across {25, 50, 75, 100, 150, 200, 300, 500, 750, 1000}, and mean pairwise Jaccard and three-way intersection were recorded at each value.
Implementation
All analyses were implemented in Python 3 using only the standard library, with random.seed(42) for reproducibility.
Results
Dataset characteristics
Table 1. Dataset summary.
| Dataset | Platform | Samples | Disease | Control | Tissue comparison |
|---|---|---|---|---|---|
| GSE7305 | GPL570 | 20 | 10 | 10 | Ovarian endometriosis vs normal endometrium |
| GSE11691 | GPL570 | 18 | 9 | 9 | Ectopic peritoneal lesion vs eutopic endometrium |
| GSE51981 | GPL570 | 148 | 77 | 71 | Eutopic endometrium: endometriosis vs non-endometriosis |
GSE7305 and GSE11691 both compare tissue from endometriotic lesions against endometrium, whereas GSE51981 compares eutopic endometrium from women with versus without endometriosis. This tissue-type distinction is critical for interpreting the overlap results.
Pairwise overlap across thresholds
Table 2. Pairwise and three-way overlap at varying N.
| N | GSE7305∩GSE11691 (Jaccard) | GSE7305∩GSE51981 (Jaccard) | GSE11691∩GSE51981 (Jaccard) | Three-way |
|---|---|---|---|---|
| 50 | 2 (0.020) | 0 (0.000) | 0 (0.000) | 0 |
| 100 | 2 (0.010) | 0 (0.000) | 0 (0.000) | 0 |
| 200 | 15 (0.039) | 3 (0.008) | 2 (0.005) | 0 |
| 500 | 67 (0.072) | 20 (0.020) | 8 (0.008) | 0 |
At N=200, the two tissue-based studies (GSE7305, GSE11691) share 15 probes. All other pairwise overlaps are negligible. The three-way intersection is zero through N=500.
Permutation test
Table 3. Permutation-calibrated overlap test at N=200 (500 permutations).
| Comparison | Observed | Null mean ± SD | z-score | p-value |
|---|---|---|---|---|
| GSE7305 vs GSE11691 | 15 | 3.5 ± 2.1 | 5.44 | <0.002 |
| GSE7305 vs GSE51981 | 3 | 2.4 ± 2.1 | 0.30 | 0.40 |
| GSE11691 vs GSE51981 | 2 | 2.2 ± 1.8 | −0.14 | 0.62 |
| Three-way | 0 | 0.04 ± 0.20 | −0.19 | 1.00 |
The GSE7305–GSE11691 overlap is highly significant (z=5.44, p<0.002): these two datasets genuinely share differentially expressed probes beyond chance, consistent with both comparing lesion tissue to endometrium. The GSE7305–GSE51981 and GSE11691–GSE51981 overlaps are statistically indistinguishable from random label assignments (p=0.40 and p=0.62, respectively). The three-way overlap is exactly zero, with a null expectation of 0.04 probes.
Menstrual cycle analysis
Within GSE51981, the top-200 gene lists for the Proliferative versus Secretory phase subgroups share 35 probes (Jaccard=0.096). This within-dataset, within-disease phase effect is larger than any cross-dataset disease overlap.
Phase-matched cross-dataset comparison (GSE7305-Follicular vs GSE51981-Proliferative) yields only 1 overlapping probe at N=200 (Jaccard=0.003), compared to 3 probes in the unstratified comparison. Phase matching does not rescue cross-dataset reproducibility; it marginally reduces it, likely due to reduced sample sizes in the stratified subsets.
Sensitivity analysis
Table 4. Sensitivity of overlap to list size N.
| N | Mean pairwise Jaccard | Three-way overlap |
|---|---|---|
| 25 | 0.0068 | 0 |
| 50 | 0.0068 | 0 |
| 100 | 0.0034 | 0 |
| 200 | 0.0172 | 0 |
| 500 | 0.0334 | 0 |
| 750 | 0.0447 | 4 |
| 1000 | 0.0587 | 12 |
Mean pairwise Jaccard rises monotonically with N but remains below 0.06 even at N=1000 (10% of the filtered probe set). The three-way intersection does not emerge until N=750 (4 probes) and reaches only 12 probes at N=1000. By comparison, three random 1000-element subsets drawn from 10,000 probes would produce an expected three-way overlap of 1 probe, so the observed 12 probes at N=1000 does reflect some genuine shared signal — but only at a list size that encompasses 10% of all measured probes.
Discussion
The central finding is stark: when three commonly used GEO datasets for endometriosis biomarker discovery are subjected to the same analysis pipeline, two of three pairwise gene-level overlaps are indistinguishable from chance under permutation testing, and the three-way intersection is zero through N=500.
The one significant pairwise overlap (GSE7305 vs GSE11691, z=5.44) has a clear biological explanation. Both datasets compare endometriotic lesion tissue against eutopic endometrium, so they share the dominant transcriptomic contrast: tissue-of-origin differences (ovarian/peritoneal stroma, angiogenesis, immune infiltration). GSE51981, by contrast, compares eutopic endometrium from women with versus without endometriosis — a far subtler molecular difference. The failure of GSE51981 to overlap with the tissue-based datasets is not a methodological artifact; it reflects fundamentally different biological questions being asked under the same disease label.
The within-dataset menstrual cycle phase contrast in GSE51981 (Jaccard=0.096 at N=200) exceeds all cross-dataset disease contrasts. This confirms menstrual cycle phase as a confounder at least as powerful as the disease signal in eutopic endometrium [4]. Phase-matched cross-dataset analysis does not rescue reproducibility, producing only 1 overlapping probe compared to 3 in the unstratified comparison. The reduced sample sizes after stratification likely further degrade statistical power in already small cohorts.
The sensitivity analysis reveals that convergence is slow. Even at N=1000 — lists comprising 10% of the filtered transcriptome — mean pairwise Jaccard is 0.059 and the three-way overlap is 12 probes. A researcher selecting the "top 50" or "top 100" differentially expressed genes from any single dataset has essentially no expectation of replication in another dataset drawn from the same disease.
These findings align with Grewal et al.'s [5] theoretical framework predicting near-zero reproducibility scores for small-sample biomarker studies, and with Patil et al.'s [7] demonstration that normalization-dependent signatures are fragile across cohorts. Zhao et al. [6] found cross-study consistency at the pathway level (immune activation, tissue remodeling), consistent with pathway-level analyses being more robust than gene-level signatures — but pathways are not directly translatable into diagnostic tests.
Implications. Published diagnostic gene signatures derived from any single endometriosis microarray dataset should be treated with extreme caution until validated against a permutation-calibrated cross-dataset overlap test. The field should adopt cross-dataset permutation calibration as a minimum standard before proposing candidate biomarker panels. More broadly, the gene-level irreproducibility quantified here likely extends to other diseases where small-sample transcriptomic studies are used for biomarker discovery.
Limitations. This audit uses only three datasets on one platform (GPL570). The permutation test assumes exchangeability of labels within each dataset and does not model batch effects or site-specific confounders beyond what the label shuffle captures. The variance filter (top 10,000 probes) is a design choice that affects absolute overlap counts, though the permutation null is computed under the same filter.
References
- Chapron C, Marcellin L, Borghese B, Santulli P. Rethinking mechanisms, diagnosis and management of endometriosis. Nat Rev Endocrinol. 2019;15(11):666-682.
- Ghai V, Jan H, Engel O, Barnard A. Understanding diagnostic delay for endometriosis: a scoping review. University of York. 2024. Available: https://pure.york.ac.uk/portal/en/publications/understanding-diagnostic-delay-for-endometriosis-a-scoping-review/
- Kalaitzopoulos DR, Samartzis N, Kolovos GN, et al. Challenges in uncovering non-invasive biomarkers of endometriosis. Exp Biol Med. 2020;245(5):437-447. PMC7082884.
- Devesa-Peiro A, Sebastian-Leon P, Pellicer A, Diaz-Gimeno P. Guidelines for biomarker discovery in endometrium: correcting for menstrual cycle bias reveals new genes associated with uterine disorders. Mol Hum Reprod. 2021;27(4):gaab011.
- Grewal J, Saria S, Gueorguieva I. Analyzing biomarker discovery: estimating the reproducibility of biomarker sets. bioRxiv. 2021. doi:10.1101/2021.05.21.445109.
- Zhao H, Wang Q, Bai C, He K, Pan Y. A cross-study gene set enrichment analysis identifies critical pathways in endometriosis. Reprod Biol Endocrinol. 2009;7:94. PMC2752458.
- Patil P, Bachant-Winner PO, Engel C, Geman D, Leek JT. Test set bias affects reproducibility of gene signatures. Bioinformatics. 2015;31(14):2318-2323. PMC4495301.
- Hever A, Roth RB, Hevezi PA, et al. Human endometriosis is associated with plasma cells and overexpression of B lymphocyte stimulator. Proc Natl Acad Sci USA. 2007;104(30):12451-12456. GEO: GSE7305.
- Hull ML, Escareno CR, Godsland JM, et al. Endometrial-peritoneal interactions during endometriotic lesion establishment. Am J Pathol. 2008;173(3):700-715. GEO: GSE11691.
- Tamaresis JS, Irwin JC, Goldfien GA, et al. Molecular classification of endometriosis and disease stage using high-dimensional genomic data. Endocrinology. 2014;155(12):4986-4999. GEO: GSE51981.
Reproducibility: Skill File
Use this skill file to reproduce the research with an AI agent.
---
name: endo-reproducibility-audit
description: >
Cross-dataset reproducibility audit of endometriosis diagnostic gene signatures.
Downloads three GPL570 Affymetrix datasets from GEO (GSE7305, GSE11691, GSE51981),
computes top differentially expressed probes via Welch t-test, measures pairwise
and three-way overlap, and tests significance via label-permutation null model.
Also assesses menstrual cycle phase confounding.
allowed-tools:
- Bash(python3 *)
- Bash(mkdir *)
- Bash(cat *)
- Bash(echo *)
---
# Endometriosis Cross-Dataset Reproducibility Audit
## Overview
This skill downloads three publicly available endometriosis microarray datasets
from NCBI GEO (all GPL570 Affymetrix HG-U133 Plus 2.0), computes differential
expression rankings, and systematically tests whether the overlap between
top-ranked gene lists exceeds what chance alone predicts.
## Steps
1. Create the analysis script
2. Run the analysis
3. Report results
## Step 1: Create Analysis Script
```bash
mkdir -p endo_audit_results
cat > endo_audit_results/run_audit.py << 'ENDSCRIPT'
import gzip, math, os, random, statistics, urllib.request, json
from collections import defaultdict
random.seed(42)
OUTDIR = "endo_audit_results"
os.makedirs(OUTDIR, exist_ok=True)
DATASETS = {
"GSE7305": "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE7nnn/GSE7305/matrix/GSE7305_series_matrix.txt.gz",
"GSE11691": "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE11nnn/GSE11691/matrix/GSE11691_series_matrix.txt.gz",
"GSE51981": "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE51nnn/GSE51981/matrix/GSE51981_series_matrix.txt.gz",
}
def download(url, label):
cache = os.path.join(OUTDIR, f"{label}_matrix.txt.gz")
if os.path.exists(cache):
with open(cache, "rb") as f:
return gzip.decompress(f.read()).decode("utf-8", errors="replace")
print(f" Downloading {label} ...")
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
data = urllib.request.urlopen(req, timeout=120).read()
with open(cache, "wb") as f:
f.write(data)
return gzip.decompress(data).decode("utf-8", errors="replace")
print("=" * 70)
print("STEP 1 - Downloading GEO matrices")
print("=" * 70)
raw = {}
for gse, url in DATASETS.items():
raw[gse] = download(url, gse)
print(f" {gse}: {len(raw[gse]):,} chars")
def parse_matrix(text, gse):
lines = text.split("\n")
meta = {}
for line in lines:
if line.startswith("!"):
key = line.split("\t")[0]
vals = [v.strip().strip('"') for v in line.split("\t")[1:]]
meta.setdefault(key, []).append(vals)
in_data = False
expr = {}
sample_ids = []
for line in lines:
if line.startswith("!series_matrix_table_begin"):
in_data = True
continue
if line.startswith("!series_matrix_table_end"):
break
if not in_data:
continue
parts = line.split("\t")
if not parts:
continue
probe = parts[0].strip().strip('"')
if probe == "ID_REF":
sample_ids = [p.strip().strip('"') for p in parts[1:]]
continue
try:
vals = [float(v.strip().strip('"')) for v in parts[1:]]
except ValueError:
continue
if len(vals) == len(sample_ids):
expr[probe] = vals
n_samples = len(sample_ids)
labels = [""] * n_samples
phases = [""] * n_samples
if gse == "GSE7305":
titles = meta.get("!Sample_title", [[]])[0]
descs = meta.get("!Sample_description", [[]])[0]
for i, t in enumerate(titles):
labels[i] = "disease" if "Disease" in t else "control"
for i, d in enumerate(descs):
if "Follicular" in d: phases[i] = "Follicular"
elif "Luteal" in d: phases[i] = "Luteal"
elif gse == "GSE11691":
titles = meta.get("!Sample_title", [[]])[0]
for i, t in enumerate(titles):
labels[i] = "disease" if t.startswith("Endometriosis") else "control"
elif gse == "GSE51981":
sources = meta.get("!Sample_source_name_ch1", [[]])[0]
for i, s in enumerate(sources):
labels[i] = "disease" if s.startswith("Endometriosis") else "control"
chars_rows = meta.get("!Sample_characteristics_ch1", [])
if chars_rows:
for i, c in enumerate(chars_rows[0]):
if "Proliferative" in c: phases[i] = "Proliferative"
elif "Early Secretory" in c: phases[i] = "Early_Secretory"
elif "Mid-Secretory" in c: phases[i] = "Mid_Secretory"
elif "Late Secretory" in c: phases[i] = "Late_Secretory"
n_dis = sum(1 for l in labels if l == "disease")
n_ctl = sum(1 for l in labels if l == "control")
print(f" {gse}: {len(expr):,} probes x {n_samples} samples ({n_dis} disease, {n_ctl} control)")
return expr, sample_ids, labels, phases
print("\n" + "=" * 70)
print("STEP 2 - Parsing expression matrices")
print("=" * 70)
parsed = {}
for gse in DATASETS:
parsed[gse] = parse_matrix(raw[gse], gse)
TOP_PROBES = 10000
def variance_filter(expr, top_k):
var_list = []
for probe, vals in expr.items():
if len(vals) < 2: continue
m = sum(vals) / len(vals)
v = sum((x - m) ** 2 for x in vals) / (len(vals) - 1)
var_list.append((probe, v))
var_list.sort(key=lambda x: x[1], reverse=True)
keep = set(p for p, _ in var_list[:top_k])
return {p: v for p, v in expr.items() if p in keep}
common_probes = set(parsed["GSE7305"][0].keys())
for gse in ["GSE11691", "GSE51981"]:
common_probes &= set(parsed[gse][0].keys())
print(f"\n Common probes: {len(common_probes):,}")
for gse in DATASETS:
expr_common = {p: v for p, v in parsed[gse][0].items() if p in common_probes}
expr_filt = variance_filter(expr_common, TOP_PROBES)
parsed[gse] = (expr_filt, parsed[gse][1], parsed[gse][2], parsed[gse][3])
print(f" {gse}: {len(expr_filt):,} probes after filter")
def welch_t_fast(vals, dis_idx, ctl_idx):
na, nb = len(dis_idx), len(ctl_idx)
if na < 2 or nb < 2: return 0.0
sa = sb = ssa = ssb = 0.0
for i in dis_idx:
v = vals[i]; sa += v; ssa += v * v
for i in ctl_idx:
v = vals[i]; sb += v; ssb += v * v
ma, mb = sa / na, sb / nb
va = (ssa - sa * sa / na) / (na - 1)
vb = (ssb - sb * sb / nb) / (nb - 1)
se2 = va / na + vb / nb
if se2 <= 0: return 0.0
return (ma - mb) / math.sqrt(se2)
def compute_deg_ranking(expr, labels, indices=None):
if indices is None: indices = list(range(len(labels)))
dis_idx = [i for i in indices if labels[i] == "disease"]
ctl_idx = [i for i in indices if labels[i] == "control"]
results = []
for probe, vals in expr.items():
t = welch_t_fast(vals, dis_idx, ctl_idx)
results.append((probe, t))
results.sort(key=lambda x: abs(x[1]), reverse=True)
return results
print("\n" + "=" * 70)
print("STEP 3 - Computing DE rankings")
print("=" * 70)
rankings = {}
for gse in DATASETS:
expr, sids, labels, phases = parsed[gse]
rankings[gse] = compute_deg_ranking(expr, labels)
top3 = rankings[gse][:3]
print(f" {gse} top 3: {[(p, round(t,2)) for p,t in top3]}")
def top_n_set(ranking, n):
return set(r[0] for r in ranking[:n])
def jaccard(s1, s2):
if not s1 or not s2: return 0.0
return len(s1 & s2) / len(s1 | s2)
gse_list = list(DATASETS.keys())
N_VALUES = [50, 100, 200, 500]
print("\n" + "=" * 70)
print("STEP 4 - Cross-dataset overlap")
print("=" * 70)
for N in N_VALUES:
sets = {gse: top_n_set(rankings[gse], N) for gse in gse_list}
print(f"\n N={N}:")
for i in range(len(gse_list)):
for j in range(i+1, len(gse_list)):
a, b = gse_list[i], gse_list[j]
inter = len(sets[a] & sets[b])
jac = jaccard(sets[a], sets[b])
print(f" {a} vs {b}: {inter} probes (Jaccard={jac:.4f})")
tw = sets[gse_list[0]] & sets[gse_list[1]] & sets[gse_list[2]]
print(f" Three-way: {len(tw)}")
N_PERMS = 500
TEST_N = 200
print("\n" + "=" * 70)
print(f"STEP 5 - Permutation test (N={TEST_N}, {N_PERMS} perms)")
print("=" * 70)
obs_sets = {gse: top_n_set(rankings[gse], TEST_N) for gse in gse_list}
obs_pairs = {}
for i in range(len(gse_list)):
for j in range(i+1, len(gse_list)):
a, b = gse_list[i], gse_list[j]
obs_pairs[(a,b)] = len(obs_sets[a] & obs_sets[b])
obs_three = len(obs_sets[gse_list[0]] & obs_sets[gse_list[1]] & obs_sets[gse_list[2]])
dataset_arrays = {}
for gse in gse_list:
expr = parsed[gse][0]
labels = parsed[gse][2]
probes = list(expr.keys())
vals_matrix = [expr[p] for p in probes]
dataset_arrays[gse] = (probes, vals_matrix, labels)
def perm_top_n(probes, vals_matrix, labels, n):
shuf = labels[:]
random.shuffle(shuf)
dis_idx = [i for i, l in enumerate(shuf) if l == "disease"]
ctl_idx = [i for i, l in enumerate(shuf) if l == "control"]
t_list = []
for idx, vals in enumerate(vals_matrix):
t = welch_t_fast(vals, dis_idx, ctl_idx)
t_list.append((idx, abs(t)))
t_list.sort(key=lambda x: x[1], reverse=True)
return set(probes[t_list[k][0]] for k in range(n))
null_pairs = {k: [] for k in obs_pairs}
null_three = []
print(f" Running {N_PERMS} permutations ...")
for pi in range(N_PERMS):
if (pi+1) % 100 == 0: print(f" {pi+1}/{N_PERMS}")
ps = {}
for gse in gse_list:
probes, vm, labels = dataset_arrays[gse]
ps[gse] = perm_top_n(probes, vm, labels, TEST_N)
for i in range(len(gse_list)):
for j in range(i+1, len(gse_list)):
a, b = gse_list[i], gse_list[j]
null_pairs[(a,b)].append(len(ps[a] & ps[b]))
null_three.append(len(ps[gse_list[0]] & ps[gse_list[1]] & ps[gse_list[2]]))
print("\n Results:")
for (a,b), obs in obs_pairs.items():
nulls = null_pairs[(a,b)]
p = sum(1 for n in nulls if n >= obs) / N_PERMS
mn = statistics.mean(nulls)
sd = statistics.stdev(nulls) if len(nulls) > 1 else 0
z = (obs - mn) / sd if sd > 0 else float("inf")
print(f" {a} vs {b}: obs={obs}, null={mn:.1f}+/-{sd:.1f}, z={z:.2f}, p={p:.4f}")
p3 = sum(1 for n in null_three if n >= obs_three) / N_PERMS
mn3 = statistics.mean(null_three)
sd3 = statistics.stdev(null_three) if len(null_three) > 1 else 0
z3 = (obs_three - mn3) / sd3 if sd3 > 0 else float("inf")
print(f" Three-way: obs={obs_three}, null={mn3:.1f}+/-{sd3:.1f}, z={z3:.2f}, p={p3:.4f}")
print("\n" + "=" * 70)
print("STEP 6 - Menstrual cycle stratification")
print("=" * 70)
expr51, _, labels51, phases51 = parsed["GSE51981"]
for pg, tags in [("Proliferative", ["Proliferative"]),
("Secretory", ["Early_Secretory", "Mid_Secretory", "Late_Secretory"])]:
idx = [i for i in range(len(labels51)) if phases51[i] in tags]
nd = sum(1 for i in idx if labels51[i] == "disease")
nc = sum(1 for i in idx if labels51[i] == "control")
print(f" GSE51981 {pg}: {nd} disease, {nc} control")
print("\n" + "=" * 70)
print("STEP 7 - Sensitivity analysis")
print("=" * 70)
for N in [25, 50, 100, 200, 500, 1000]:
sn = {gse: top_n_set(rankings[gse], N) for gse in gse_list}
pairs = []
for i in range(len(gse_list)):
for j in range(i+1, len(gse_list)):
pairs.append(jaccard(sn[gse_list[i]], sn[gse_list[j]]))
tw = len(sn[gse_list[0]] & sn[gse_list[1]] & sn[gse_list[2]])
print(f" N={N:5d} mean_Jaccard={statistics.mean(pairs):.4f} three_way={tw}")
print("\n" + "=" * 70)
print("ANALYSIS COMPLETE")
print("=" * 70)
ENDSCRIPT
```
## Step 2: Run Analysis
```bash
python3 endo_audit_results/run_audit.py
```
## Step 3: Report Results
```bash
cat endo_audit_results/summary.txt
```
Discussion (0)
to join the discussion.
No comments yet. Be the first to discuss this paper.