Test whether an in-silico model's predicted "impact" of perturbing ATAC-seq peaks is concordant with real CRISPR knockout essentiality, in B-cell acute lymphoblastic leukemia (B-ALL). There is no direct peak-to-gene assignment in the data, so the bridge between the two is transcription-factor identity: chromatin-factor binding annotations tell us which peaks each TF/factor is predicted to bind, and most of those factor names are themselves genes that were CRISPR-screened.
data/ # raw inputs (see Data files below)
BALL_CRISPRGeneEffect.tsv
BALL_Expression.tsv
BALL_Model.tsv
factor_overlap_matrix.tsv
impact_score_matrix.tsv
subtype_meta.tsv
scripts/
concordance_workflow.py # main pipeline: builds factor-level scores, runs concordance test
confounder_model.py # multivariate model: essentiality ~ impact + confounders
run_app.py # start FastAPI concordance UI (port 8765)
outputs/
factor_concordance_results.tsv # factor-level table from concordance_workflow.py
concordance_scatter.png
extended_factor_features.tsv # factor-level table from confounder_model.py (more features)
confounder_model_summary.txt # full OLS / VIF / CV output, plain text
partial_regression_plot.png
feature_importance.png
cache/ # disk cache for on-demand bundles (created by run_app.py)
src/isgs/
static/ # web UI (index.html, app.css, app.js)
... # installable Python package (logic shared by scripts)
pyproject.toml
cd /path/to/ISGS
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
python scripts/concordance_workflow.py
python scripts/confounder_model.pyThe web UI runs as a local server that loads peak-filter bundles on demand and caches them to disk (~20–30s the first time per (impact, variance, subtype) tuple; instant on repeat).
source .venv/bin/activate
pip install -e .
python scripts/run_app.py
# open http://127.0.0.1:8765Entry point: isgs-app (same server as scripts/run_app.py).
Frontend assets live in src/isgs/static/ and are served directly by the app.
Cache location: outputs/cache/ — analysis bundles, meta.json, peak summaries, and UI state (app_prefs.json, favorites.json). Cache entries are invalidated when any data/*.tsv file is newer than the cache file (UI prefs are not invalidated by data changes).
Both batch scripts accept --data-dir (default ./data) and --out-dir (default ./outputs).
Entry points: isgs-concordance, isgs-confounders, isgs-app.
| File | Shape | Rows | Columns | Notes |
|---|---|---|---|---|
BALL_CRISPRGeneEffect.tsv |
13 × 18,531 | cell line (ModelID) |
gene symbol | DepMap Chronos/CERES gene effect score. More negative = more essential. Has structural NaNs: 11 of 13 lines share one set of 744 unscreened genes, the other 2 lines share a different 372/373-gene set (different sgRNA library versions) — see "Gotchas" below. |
BALL_Expression.tsv |
26 × 19,215 | cell line (ModelID) |
gene symbol | Expression values (looks like log2(TPM+1)). 26 lines, a superset of the 13 CRISPR-screened lines. Has an extra IsDefaultEntryForModel column that scripts drop on load. |
BALL_Model.tsv |
33 × 48 | cell line (ModelID) |
metadata | Per-line metadata. PatientSubtypeFeatures has free-text fusion calls (e.g. KMT2A-AFF1, TCF3::PBX1) — this is the only way to approximate a subtype label for the CRISPR/expression lines, and its vocabulary does not match subtype_meta.tsv. Currently hand-curated into a dict (CELL_LINE_SUBTYPE in concordance_workflow.py) for the 13 CRISPR lines only. |
factor_overlap_matrix.tsv |
94,478 × 205 | ATAC peak (peak_id) |
chromatin factor name | Binary: 1 if that factor's binding site overlaps the peak. Row order is identical to impact_score_matrix.tsv (verified), but scripts still join on peak_id defensively rather than assume positional alignment. |
impact_score_matrix.tsv |
94,478 × 155 | ATAC peak (peak_id) |
chr, start, end, then 151 patient GEO IDs |
In-silico perturbation impact score per peak per patient. Raw TSV values are small and strictly positive (~0.0004–0.15). The app multiplies by IMPACT_SCORE_SCALE (1000) at load time for display (~0.4–150). Treated as an unsigned magnitude of predicted importance. Worth confirming the sign/direction convention with whoever produced this model before trusting downstream interpretation. |
subtype_meta.tsv |
153 × 2 | GEO ID | Subtype |
Patient subtype labels for the impact-score columns. 151 of 153 patients here are present in impact_score_matrix.tsv. Subtype sizes are very uneven (e.g. Unknown=25, BCR-ABL1=20 down to iAMP21=1) — anything below ~10 patients shouldn't be trusted for subtype-level aggregation. |
Of the 205 factor names in factor_overlap_matrix.tsv, 202 match a gene
symbol in the CRISPR data 1:1, and 201 also exist in the expression data. The
three that don't: MED (a complex placeholder, not a single gene), PBX1-2-3
(a paralog group), and TCF3-PBX1 (the fusion product itself, not a wild-type
gene). All three are dropped automatically (via .intersection() on column
names) — nothing hardcoded, this just falls out of the join.
- Loads all six files, aligns
factor_overlap_matrixandimpact_score_matrixonpeak_id. - Builds a Factor × Patient "predicted impact" matrix via one matrix multiply:
factor_overlap.T @ impact_scores, normalized by peak count per factor. This is the computational core — avoids looping over 205 factors × 94,478 peaks. - Collapses to one global value per factor (mean across all patients) and per-subtype values (mean within
subtype_meta.tsvgroups). - Computes CRISPR essentiality per gene as
-mean(gene_effect)across the 13 lines (so higher = more essential, matching the impact score's direction), and a mean-expression filter to drop factors not expressed in B-ALL (MIN_MEAN_EXPRESSION = 1.0). - Runs the primary concordance test: Spearman correlation + a 10,000-iteration label-permutation null (more robust than the parametric p-value at this n). Also reports median-aggregation and no-expression-filter robustness variants.
- Runs a subtype-specificity check: for subtypes with ≥2 matched CRISPR lines (currently only
KMT2A, n=4, andTCF3-PBX1, n=2, after excluding theUnknown/B-Othercatch-all bins), compares matched vs. mismatched subtype pairings. - Saves
factor_concordance_results.tsvandconcordance_scatter.png.
Key tunables at the top of the file: MIN_PEAKS_PER_FACTOR (10), MIN_MEAN_EXPRESSION (1.0), N_PERMUTATIONS (10,000), CELL_LINE_SUBTYPE (the hand-curated mapping).
Takes the question further: is the impact-score/essentiality relationship
confounded by other factor characteristics? Builds one extra engineered
feature beyond what's in factor_concordance_results.tsv:
mean_co_occupancy— average number of other factors co-bound at a factor's peaks (whether it mostly binds generically "busy" multi-TF hub regions vs. more selective sites).
(extended_factor_features.tsv also includes impact_sd_across_patients for reference, but it is not used in the OLS model.)
Fits an OLS regression (crispr_essentiality_global ~ predicted_impact_global + log10(n_bound_peaks) + mean_expression_BALL + mean_co_occupancy, all predictors z-scored), checks VIF for collinearity, compares nested models (impact-only vs. full), and cross-validates a linear model and a random forest (5-fold) to check out-of-sample predictive power. Saves extended_factor_features.tsv, confounder_model_summary.txt, partial_regression_plot.png, feature_importance.png.
- Global concordance is weak: Spearman rho = 0.092 (permutation p = 0.20, n=190 expressed factors). Holds up similarly under median aggregation / no expression filter.
- Restricting to the 20 most essential factors (essentiality > 1.0) strengthens it: Spearman rho = 0.347 (p=0.13), Pearson r = 0.415 (p=0.069) — but this is a non-random, outcome-selected subset, so it's descriptive, not a fair hypothesis test.
- The strongest single predictor of CRISPR essentiality in the multivariate model is
mean_expression_BALL(β=0.221, p<0.001) — a generic, chromatin-unrelated effect. The impact score's adjusted coefficient (β=0.075, p=0.052) barely changes from its unadjusted value (β=0.079, p=0.031), so it isn't confounded by the other covariates tested, but it's weak and right at the significance boundary. - 5-fold cross-validated R² is negative for both a linear model (-0.04) and a random forest (-0.08) — the in-sample fit (R²=0.24) does not generalize. This is the most important caveat on everything above: at n≈190 factors / 13 cell lines, don't treat the in-sample significance as confirmatory.
- Subtype-specificity check is inconclusive:
KMT2Ashows the expected matched > mismatched pattern,TCF3-PBX1doesn't, but both groups have only 2-4 cell lines. - Top concordant hits with good face validity (high impact AND high essentiality): MYC, CDK7, CDK9, CTCF, HCFC1, UBTF, MYB — all well-known pan-essential transcriptional/chromatin machinery genes. EBF1 (a core B-ALL lineage TF) also shows up in the high-essentiality subset.
- CRISPR NaN structure:
BALL_CRISPRGeneEffect.tsvis not fully dense. When subsetting to a small group of cell lines (e.g. for a subtype-specific essentiality mean),scipy.stats.spearmanr/pearsonrwill silently returnnanif even one paired value is missing — always drop NaNs pairwise first (seesafe_spearman()inconcordance_workflow.py) rather than calling the scipy functions directly on a small subset. subtype_meta.tsvvocabulary ≠BALL_Model.tsvvocabulary. The former has clean canonical subtype names; the latter only has raw fusion-call free text. The current cell-line-to-subtype mapping is manually curated and only covers the 13 CRISPR lines — re-derive it if you add more lines or want a more principled mapping.- Peak-to-gene linking is TF-mediated, not coordinate-based.
impact_score_matrix.tsvhaschr/start/endcolumns that are currently unused. If you get access to a gene annotation/TSS file, a nearest-gene assignment could be a complementary (and probably cleaner) linking strategy worth comparing against the current TF-binding-based approach. - Sign convention of the impact score is assumed, not confirmed. All observed values are small and positive; if the underlying model is actually signed (e.g., direction of expression change rather than magnitude), the aggregation logic (
meanof impact scores per factor) should be revisited.
pandas
numpy
scipy
matplotlib
statsmodels
scikit-learn
seaborn
plotly
fastapi
uvicorn[standard]
Optional for API tests: httpx (used by FastAPI's TestClient).
No GPU or special hardware needed; the heaviest step is the factor_overlap.T @ impact_scores matrix multiply (94,478 × 205 by 94,478 × 151), which runs in a few seconds with BLAS-backed numpy. impact_score_matrix.tsv is the large file (~270MB) — loading it is the main I/O cost (a few seconds with pandas.read_csv).
- Get clarity on the impact score's sign/direction convention from its source.
- If a gene-annotation file becomes available, add a coordinate-based peak→gene assignment as an alternative to the TF-binding bridge, and compare results.
- Expand/validate the
CELL_LINE_SUBTYPEmapping (currently hand-curated from free text) — possibly cross-check againstOncotreeSubtypeor external annotations of these specific cell lines. - Given the cross-validation results, consider whether a larger panel of CRISPR-screened B-ALL lines (if available from a broader DepMap pull) would give enough power for a more trustworthy multivariate fit.
- Consider restricting the concordance test a priori to known B-ALL lineage-defining TFs (PAX5, EBF1, IKZF1, TCF3, RUNX1, etc.) as a more targeted, biologically motivated hypothesis test rather than testing all 190+ factors.