Skip to main content

tooluniverse-statistical-modeling

Statistical modeling — linear/logistic/ordinal/Poisson regression, ANOVA, Kruskal-Wallis, chi-square, Mann-Whitney, Cox survival, spline fits (R `ns()`), odds ratios, Cohen's d, F-statistic, p-value computation. Specializes in clinical-trial AE analysis (SDTM DM/AE), severity ordinal regression, and per-feature stat workflows.

Informations de source

Dépôt
mims-harvard/ToolUniverse
Dernière activité de la source
21 juillet 2026 à 05:31
Langue détectée de SKILL.md
anglais
Étoiles
1 718
Forks
261

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
21 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
tooluniverse-statistical-modeling
description
Statistical modeling — linear/logistic/ordinal/Poisson regression, ANOVA, Kruskal-Wallis, chi-square, Mann-Whitney, Cox survival, spline fits (R `ns()`), odds ratios, Cohen's d, F-statistic, p-value computation. Specializes in clinical-trial AE analysis (SDTM DM/AE), severity ordinal regression, and per-feature stat workflows.
disable-model-invocation
true
# Statistical Modeling for Biomedical Data Analysis ## RULE ZERO — Check for pre-computed results FIRST Before following any instruction below, scan the data folder for: - `*_executed.ipynb` → read with `tu run read_executed_notebook '{"data_folder":"<path>","search":"<keyword>"}'` and cite its cell outputs as the authoritative answer - Pre-computed result files (CSV/TSV with names like `*results*`, `*deseq*`, `*enrich*`, `*stats*`, `*_simplified.csv`) → read directly and report the requested value - Canonical analysis scripts (`analysis.R`, `run_*.py`, `find_*.R`, `*.Rmd`) → execute as-is and read the output Only follow this skill's re-analysis recipe below if **none** of the above exist. Re-running from raw data produces different numbers than the published answer and is much slower (often 5-10× turn count). --- ## PRIMARY SCRIPTS — use these FIRST These scripts encode the question-specific gotchas in `scripts/` and emit labelled, parseable output. Prefer them over ad-hoc statsmodels / scipy code. | Script | When to use it | |--------|----------------| | `r_natural_spline_regression.py` | ANY question that mentions R syntax `lm(y ~ ns(x, df = K))`, "natural spline", or asks for spline R²/F/peak prediction CIs. Always shells out to Rscript so `splines::ns()` matches. | | `spline_model_compare.py` | "Best-fitting model among quadratic, cubic and natural spline" / "max colony area at the optimal x". Fits all three in R, ranks by adj-R²/AIC/BIC, and reports the BEST model's peak (x*, y*) with 95% CI. | | `logistic_regression_or.py` | Binary or ordinal logistic regression where the answer is an OR (or OR + 95% CI). Handles label encoding, explicit Placebo=0/BCG=1 maps, AND interaction terms (`--interaction A:B` -> creates `A_B = A*B`). Prints OR + CI for every coefficient and a SCALARS block for the requested `--coef-name`. | | `power_analysis.py` | "Minimum sample size per group", "TTestIndPower", "given Cohen's d, what N for power=0.8". Computes pooled-SD Cohen's d from a CSV (or accepts `--effect-size`), then `TTestIndPower.solve_power`. | | `expression_anova.py` | Per-gene ANOVA / median LFC across cell types or sample groups (NOT pooled across genes — see warnings below). | | `prepare_ae_cohort.py` | Clinical-trial AE severity tests (chi-square / ordinal) on SDTM DM/AE files (`encoding='latin1'`, `max(AESEV)` per subject across ALL AEs — no AEPT filter). | | `stat_tests.py` | Stdlib-only chi-square goodness-of-fit, Fisher's exact, simple OLS. Use when scipy/statsmodels aren't available. | ### Concrete invocations Natural-spline regression (R^2, overall F-test p, peak Y + 95% CI): ```bash python skills/tooluniverse-statistical-modeling/scripts/r_natural_spline_regression.py \ --csv data.csv --y-col Area \ --ratio-col Ratio --new-x-col Frequency_strain \ --filter "StrainNumber not in ['1', '98']" \ --df 4 --workdir /tmp/spline_run ``` Quadratic vs cubic vs natural-spline comparison + best-model peak: ```bash python skills/tooluniverse-statistical-modeling/scripts/spline_model_compare.py \ --csv data.csv --y-col Area \ --ratio-col Ratio --new-x-col Frequency_strain \ --filter "StrainNumber not in ['1', '98']" \ --ns-df 4 --workdir /tmp/spline_cmp ``` **Report the peak location (`x*`) in the units of the fitted x-variable, not a derived label.** When the model is fit on a frequency/proportion column (e.g. `Frequency_strain`, a 0–1 value), the answer to "at what ratio/frequency is the maximum" is that fraction (e.g. `0.909`), NOT the colon-ratio it was derived from (e.g. `10:1`). Convert a colon ratio `a:b` to the fraction `a/(a+b)` when the question expects a 0–1 value or the fitted x-column is a fraction. Ordinal logistic regression with interaction term (e.g. trial AE severity): ```bash python skills/tooluniverse-statistical-modeling/scripts/logistic_regression_or.py \ --csv merged.csv --outcome AESEV --outcome-type ordinal --outcome-order "1,2,3,4" \ --predictors TRTGRP,expect_interact,patients_seen,MHONGO \ --encode TRTGRP,expect_interact,patients_seen \ --encode-map "TRTGRP:Placebo=0,BCG=1" \ --interaction MHONGO:TRTGRP_cat \ --coef-name TRTGRP_cat ``` Two-sample power analysis from a pilot CSV: ```bash python skills/tooluniverse-statistical-modeling/scripts/power_analysis.py \ --csv pilot.csv --value-col MeasuredValue --group-col Group \ --group-a Treatment --group-b Control \ --power 0.8 --alpha 0.05 ``` --- ## Workspace isolation (CRITICAL) The input data folder for any analysis must remain untouched so re-runs are reproducible. Scripts that write intermediate files (R drivers, prepared CSVs, comparison tables) must write to `/tmp/` or to a `--workdir` you pass in. Both R-based scripts in this skill refuse to run if `--workdir` resolves to the input CSV's parent directory (or any ancestor of it). ```bash # OK --workdir /tmp/spline_run # Refused: --workdir <path-equal-to-or-containing-the-input-csv>/... ``` --- ## CRITICAL — Read before writing any code 1. **Clinical trial AE analysis** (regression, chi-square, ANY severity test): Use the bundled script (or the `clinical_trial_ae_severity_test` ToolUniverse tool which wraps it): ```bash tu run clinical_trial_ae_severity_test '{"dm_file":"DM.csv","ae_file":"AE.csv","test":"chi-square","group_col":"TRTGRP"}' # Or directly: python skills/tooluniverse-statistical-modeling/scripts/prepare_ae_cohort.py \ --dm DM.csv --ae AE.csv --test chi-square --group TRTGRP \ --subgroup "expect_interact=Yes" # optional ``` The script/tool handles: `encoding='latin1'` for SDTM CSVs, `max(AESEV)` per subject across ALL AEs (no AEPT filtering), inner join with DM, optional subgroup filter, optional ordinal-logistic with covariates. **Why no AEPT filter** — AESEV is a protocol-defined severity scale on the AE table. Filtering AE by AEPT (e.g. keeping only `AEPT == "COVID-19"`) drops subjects whose worst severity was recorded under a different AEPT label, drastically changes the contingency table, and can flip the test result. The phrase "COVID-19 severity" describes the OUTCOME, NOT a filter criterion. - ❌ WRONG: `ae[ae['AEPT'].str.contains('COVID-19')].groupby('USUBJID')['AESEV'].max()` — filters to COVID-19 events - ✅ RIGHT: `ae.groupby('USUBJID')['AESEV'].max()` — uses ALL AE records 2. **Expression ANOVA / fold change with multi-feature data** (gene × sample matrix): For "the F-statistic" or "a fold change" as a single value, run per-gene then summarize — NEVER pool `expr.values.ravel()` across all genes. - For **F-statistic**: derive a per-sample quantity (like DESeq2 LFC of each gene between two cell types, then ANOVA on those LFCs across groups) OR run on a single target gene. - For **median/mean log2 fold change** between two groups: run DESeq2 with `design=~<group>`, extract per-gene `log2FoldChange` (with shrinkage if the pipeline uses it), then take median/mean across genes. ❌ WRONG (aggregate): `log2(sum_counts_groupA / sum_counts_groupB)` per sample then summarize — gives ratio of totals, dominated by high-expression genes. ✅ RIGHT (per-gene): DESeq2 → `results_df['log2FoldChange'].median()`. **Sanity heuristics**: F > 50 for biological ANOVA across a few groups means you aggregated (typical biological F is 0.5–10). |median LFC| > 2 between similar groups means you aggregated (typical |median| < 1). Use the bundled script: `python skills/tooluniverse-statistical-modeling/scripts/expression_anova.py` (or the `expression_anova_per_gene` ToolUniverse tool). 3. **Spline models** — R `splines::ns(x, df=K)` ≠ Python `patsy.dmatrix("cr(x, df=K)")`. They produce different design matrices because of internal-knot placement, boundary-knot placement, and basis orthogonalization. For ANY question that references R syntax like `lm(y ~ ns(x, df = 4))`, run R via `Rscript`. Use the bundled wrapper: ```bash python skills/tooluniverse-statistical-modeling/scripts/r_natural_spline_regression.py \ --csv data.csv --y-col Y --x-col X --df 4 --workdir /tmp/spline_run ``` For "frequency of strain X" co-culture models, include pure focal strain (freq=1) but exclude non-focal pure strain (freq=0). 4. **CSV encoding**: Clinical trial CSVs often need `encoding='latin1'`. 5. **Pearson correlation between count-like and length-like variables**: when one variable spans orders of magnitude (raw read counts, TPM, gene length, transcript abundance), raw Pearson r is often near 0 even when log-transformed r is moderate. **ALWAYS compute and explicitly report ALL FOUR variants in your final answer body**: `r(x, y)`, `r(log10(x+1), y)`, `r(x, log10(y+1))`, `r(log10(x+1), log10(y+1))`. List as a table; mark one as your primary pick. The published answer can be ANY of the four, and the question text rarely disambiguates which transform combination was used. ``` ## Primary answer: r = X.XXX (transform: <name>) ## Sensitivity (all 4 transform combinations): - r(x, y) = ... - r(log10(x+1), y) = ... - r(x, log10(y+1)) = ... - r(log10(x+1), log10(y+1)) = ... ``` Background — for any single transform variant: ```python import numpy as np from scipy.stats import pearsonr r_raw, _ = pearsonr(x, y) r_log, _ = pearsonr(np.log10(x + 1), y) print(f"r_raw={r_raw:.4f} r_log10={r_log:.4f}") ``` Defaults: - Question says "log-transformed" / "log expression" → report **r_log10** - Question doesn't specify but the variable is gene expression / RNA count → also report **r_log10** as the canonical answer (most published correlations between gene length and expression are log-scale) - When `|r_raw| < 0.1` AND `|r_log10| > 0.2`, prefer **r_log10** ❌ WRONG: report only `r_raw ≈ 0.05` when log is `0.35` ✅ RIGHT: "r_raw = 0.05; r_log10 = 0.35 (canonical for log-distributed expression)" --- ## COMPUTE, DON'T DESCRIBE Write and run Python code (via Bash) for every statistical analysis. Never describe what you "would do" — do it. Use pandas for data wrangling, statsmodels for regression, scipy for tests, and matplotlib for plots. Execute the code and report actual numbers (β, p-value, CI, N). ## LOOK UP, DON'T GUESS When uncertain about any scientific fact, SEARCH databases first rather than reasoning from memory. ## Features - **Linear Regression** - OLS for continuous outcomes with diagnostic tests - **Logistic Regression** - Binary, ordinal, and multinomial models with odds ratios - **Survival Analysis** - Cox proportional hazards and Kaplan-Meier curves - **Mixed-Effects Models** - LMM/GLMM for hierarchical/repeated measures data - **ANOVA** - One-way/two-way ANOVA, per-feature ANOVA for omics data - **Model Diagnostics** - Assumption checking, fit statistics, residual analysis - **Statistical Tests** - t-tests, chi-square, Mann-Whitney, Kruskal-Wallis, etc. ## When to Use Apply this skill when user asks: - "What is the odds ratio of X associated with Y?" - "What is the hazard ratio for treatment?" - "Fit a linear regression of Y on X1, X2, X3" - "Perform ordinal logistic regression for severity outcome" - "What is the Kaplan-Meier survival estimate at time T?" - "What is the percentage reduction in odds ratio after adjusting for confounders?" - "Run a mixed-effects model with random intercepts" - "Compute the interaction term between A and B" - "What is the F-statistic from ANOVA comparing groups?" - "Test if gene/miRNA expression differs across cell types" ## Model Selection Decision Tree ``` START: What type of outcome variable? | +-- CONTINUOUS (height, blood pressure, score) | +-- Independent observations -> Linear Regression (OLS) | +-- Repeated measures -> Mixed-Effects Model (LMM) | +-- Count data -> Poisson/Negative Binomial | +-- BINARY (yes/no, disease/healthy) | +-- Independent observations -> Logistic Regression | +-- Repeated measures -> Logistic Mixed-Effects (GLMM/GEE) | +-- Rare events -> Firth logistic regression | +-- ORDINAL (mild/moderate/severe, stages I/II/III/IV) | +-- Ordinal Logistic Regression (Proportional Odds) | +-- MULTINOMIAL (>2 unordered categories) | +-- Multinomial Logistic Regression | +-- TIME-TO-EVENT (survival time + censoring) +-- Regression -> Cox Proportional Hazards +-- Survival curves -> Kaplan-Meier ``` ## Workflow ### Phase 0: Data Validation **Goal**: Load data, identify variable types, check for missing values. **CRITICAL: Identify the Outcome Variable First** Before any analysis, verify what you're actually predicting: 1. **Read the full question** - Look for "predict [outcome]", "model [outcome]", or "dependent variable" 2. **Examine available columns** - List all columns in the dataset 3. **Match question to data** - Find the column that matches the described outcome 4. **Verify outcome exists** - Don't create outcome variables from predictors **Common mistake**: Question mentions "obesity" -> Assumed outcome = BMI >= 30 (circular logic with BMI predictor). Always check data columns first: `print(df.columns.tolist())` ```python import pandas as pd import numpy as np df = pd.read_csv('data.csv') print(f"Observations: {len(df)}, Variables: {len(df.columns)}, Missing: {df.isnull().sum().sum()}") for col in df.columns: n_unique = df[col].nunique() if n_unique == 2: print(f"{col}: binary")
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub