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.

Source facts

Repository
mims-harvard/ToolUniverse
Last source activity
July 21, 2026 at 05:31
Detected SKILL.md language
English
Stars
1,718
Forks
261

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
21 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
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")
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub