Guided Statistics
Start with a question, see what the data can support, and keep the evidence visible. The exploration and task-first tools explain their choices instead of silently choosing an analysis for you.
Use stat.explore to see centre, spread, shape, missing values, and observations worth reviewing.
Use stat.scan to find data-quality, design, and association clues before fitting a model.
Use stat.decision_map to connect the scientific question and experimental unit to possible approaches.
A safe first pass
Import the statistics package as stat. The public functions below are readable wrappers around runtime builtins. They return ordinary records, so you can inspect the numbers and explanations rather than accepting a hidden decision.
import "statistics" as stat
let values = [12.1, 12.4, 12.8, 13.0, 13.2, 13.5, 29.0]
let result = stat.explore(values, {name: "protein concentration"})
println(stat.explain(result))
println(stat.distribution_ascii(values))
result.review_flags
The examples on this page are copyable package examples. The raw stats_* names shown in the tables are implementation-level builtins; application code should normally use stat.*.
A short analysis that still explains itself
When the study design is already known, use a task-first helper. The helper keeps the code short but returns the method, reason, assumptions, alternatives, effect estimates, uncertainty, and the equivalent low-level call. It does not guess whether observations are paired or independent.
import "statistics" as stat
let control = [12.1, 12.4, 12.8, 13.0, 13.2]
let treated = [13.0, 13.5, 13.9, 14.2, 14.8]
let result = stat.compare_groups(control, treated, {method: "welch"})
println(stat.explain_task(result))
# Keep these with the numerical result.
result.method
result.assumptions
result.alternatives
result.reproducible_call
| Question | Helper | Explicit choices |
|---|---|---|
| Do two groups differ? | stat.compare_groups | Welch, pooled t, Mann-Whitney, permutation |
| Did matched measurements change? | stat.paired_change | Paired t or paired Wilcoxon |
| Do several groups differ? | stat.compare_many | Welch/classical ANOVA or Kruskal-Wallis |
| Are categories associated? | stat.count_association | Chi-square or Fisher exact |
| Is one odds ratio plausible across strata? | stat.stratified_association | Tarone-adjusted Breslow-Day |
| How are numeric measurements related? | stat.numeric_relationship | Linear/Pearson, Spearman, or Kendall |
| What is survival over time? | stat.survival_summary | Kaplan-Meier; optional Cox model |
The same schema powers the CLI notebook generator. Run bl stats to see its questions, then create an editable notebook:
bl stats compare measurements.csv --columns control,treated --output comparison.bln
bl notebook comparison.bln
What the guidance does not do
It does not prove normality, independence, causation, randomization, or a missing-data mechanism. It never deletes observations, transforms values, or selects a model automatically. Treat every recommendation as a prompt to check against how the data were collected.
1. Understand one numeric variable
Begin here when you have one measurement such as expression, concentration, age, or read depth.
| Public function | Runtime builtin | Use it to… |
|---|---|---|
stat.explore(values, options?) | stats_explore | Summarize centre, spread, shape, missingness, and review flags. |
stat.means(values, options?) | stats_means | Compare arithmetic, geometric, harmonic, trimmed, RMS, median, and mode with compatible spread choices. |
stat.shape(values, options?) | stats_shape | Inspect skewness, kurtosis, peaks, and normal Q-Q alignment without declaring a diagnosis. |
stat.distribution_clues(values, options?) | stats_distribution_clues | Compare compatible normal, log-normal, Poisson, and negative-binomial clues using likelihood and AIC. |
stat.uncertainty(values, options?) | stats_uncertainty | Compute seeded bootstrap intervals for summaries, differences, or correlations. |
stat.preview_transform(values, method, options?) | stats_transform_preview | Compare before and after log, log1p, square-root, z-score, robust, or min-max transformation. |
stat.preprocessing(values, options?) | stats_preprocess | Find observable data-quality issues and see non-applied preprocessing alternatives. |
stat.distribution_plot(values, options?) | stats_distribution_plot | Draw observations, histogram, mean, median, IQR, SD bands, and outlier flags. |
stat.distribution_ascii(values, options?) | stats_distribution_ascii | Show the same distribution clues in a terminal-safe chart. |
stat.normal_qq_plot(values, options?) | stats_normal_qq_plot | Compare ordered values with normal-distribution quantiles in SVG or ASCII. |
stat.normal_diagram(values?, options?) | stats_normal_diagram | Draw the normal curve with a shaded tail, in SVG or ASCII. Given observations it adds their measured coverage beside the theoretical percentages, and says which to trust: below 20 observations, or with asymmetry or Tukey review flags, the measured coverage is the one to read. |
stat.visualize(report, options?) | stats_visualize | Pull the SVG or ASCII figure out of the visual_guide a stat.explore() report already carries, rather than recomputing it. |
Centre and spread belong together
| When values behave like… | Useful centre | Useful spread |
|---|---|---|
| Roughly symmetric, additive measurements | Arithmetic mean | SD |
| Skewed or heavy-tailed measurements | Median | IQR or MAD |
| Positive multiplicative changes | Geometric mean | Geometric SD or fold interval |
| Rates with a common numerator | Harmonic mean | Report the individual rates and design context |
| Category frequency | Mode | Counts or proportions |
Variance is the average squared distance from the mean; SD is its square root and is in the original unit. Neither is “left” or “right.” On a symmetric bell-shaped distribution, mean ± 1, 2, and 3 SD are useful visual landmarks. They are not universal outlier rules for skewed data.
2. Audit a whole table and its design
| Public function | Runtime builtin | Use it to… |
|---|---|---|
stat.profile(data, options?) | stats_profile | Inspect types, summaries, missingness, duplicates, ranges, and declared design roles. |
stat.scan(data, options?) | stats_scan | Run a bounded first pass with prioritized, evidence-linked next steps. |
stat.overview_ascii(data, options?) | stats_overview_ascii | Read a compact whole-table overview in the CLI. |
stat.missingness(data, options?) | stats_missingness | Inspect missing values by row, column, pair, and optional group. |
stat.missingness_plot(data, options?) | stats_missingness_plot | Visualize missingness in SVG or ASCII. |
stat.design_check(data, options?) | stats_design_check | Review repeated units, imbalance, duplicate subject/time rows, and batch/group confounding clues. |
stat.associations(data, options?) | stats_associations | Screen bounded numeric and categorical effect-size associations. |
stat.report(data, options?) | stats_report | Create a self-contained HTML or Markdown data-health report with provenance. |
stat.guide(report, context?) | stats_guide | Add the scientific question and experimental-unit context to a report. |
stat.explain(report, detail?) | stats_explain | Render quick, learning, or audit-level plain text. |
stat.decision_map(options?) | stats_decision_map | Browse question-led analysis alternatives without automatic selection. |
let audit = stat.scan(data, {
subject_column: "patient_id",
group_column: "treatment",
batch_column: "sequencing_batch"
})
println(stat.overview_ascii(data))
println(stat.explain(audit))
audit.recommendations
Column roles are declared by you; BioLang does not infer experimental units or randomization from convenient column names. Missingness patterns are investigation clues, not proof that data are MCAR, MAR, or MNAR.
3. Compare groups, relationships, and categories
| Public function | Runtime builtin | Use it to… |
|---|---|---|
stat.compare(values, groups, options?) | stats_compare | Compare per-group evidence and see compatible analysis alternatives. |
stat.relationship(x, y, options?) | stats_relationship | Inspect complete pairs, Pearson/Spearman association, and a regression line. |
stat.categorical(values, options?) | stats_categories | Count levels, proportions, modes, missingness, and rare-level clues. |
stat.group_plot(values, groups, options?) | stats_group_plot | Draw group observations and robust summaries in SVG or ASCII. |
stat.facet_plot(values, facets, options?) | stats_facet_plot | Draw one panel per level of a factor, sharing scales across panels. |
stat.relationship_plot(x, y, options?) | stats_relationship_plot | Draw a scatterplot and fitted line in SVG or ASCII. |
stat.categorical_plot(values, options?) | stats_categorical_plot | Draw frequency bars in SVG or ASCII. |
stat.compare_groups(a, b, options?) | task-first wrapper | Run a stated two-group method and preserve its reasoning and alternatives. |
stat.compare_many(groups, options?) | task-first wrapper | Run a stated multi-group mean or rank analysis. |
stat.count_association(counts, options?) | task-first wrapper | Run an explicit chi-square or Fisher analysis. |
stat.stratified_association(strata, options?) | task-first wrapper | Check common-odds-ratio homogeneity with Tarone-adjusted Breslow-Day. |
stat.numeric_relationship(x, y, options?) | task-first wrapper | Run an explicit linear, Pearson, Spearman, or Kendall analysis. |
Correlation describes association, not causation. A small p-value does not tell you whether an effect is important, whether groups were independent, or whether the study design was unbiased.
4. Omics and dependent data
| Public function | Runtime builtin | Use it to… |
|---|---|---|
stat.normalization_guide(matrix, options?) | stats_normalization_guide | Audit a dense or sparse matrix and see domain-aware normalization alternatives. |
stat.omics_profile(matrix, options?) | stats_omics_profile | Profile bulk RNA-seq, single-cell, proteomics, metabolomics, microbiome, or generic matrices without densifying sparse input. |
stat.weighted_summary(values, weights, options?) | stats_weighted_summary | Inspect weighted centre/spread, effective sample size, and weight concentration. |
stat.time_series_diagnostics(values, options?) | stats_time_series_diagnostics | Inspect trend, autocorrelation, Ljung-Box, and first differences in an ordered regular series. |
stat.cluster_diagnostics(values, clusters, options?) | stats_cluster_diagnostics | Estimate one-way ICC, cluster-size imbalance, and approximate information loss. |
5. Check fitted models
| Public function | Runtime builtin | Use it to… |
|---|---|---|
stat.linear_diagnostics(x, y, options?) | stats_linear_diagnostics | Inspect residual form, spread, Q-Q, order, and influence for a simple linear model. |
stat.linear_diagnostic_plot(x, y, options?) | stats_linear_diagnostic_plot | Draw residual-versus-fitted or residual Q-Q diagnostics. |
stat.multiple_linear_diagnostics(predictors, outcome, options?) | stats_multiple_linear_diagnostics | Check encodings, interactions, VIF, influence, intervals, and held-out error. |
stat.robust_linear_diagnostics(predictors, outcome, options?) | stats_robust_linear_diagnostics | Compare Huber and ordinary least-squares coefficients as a sensitivity analysis. |
stat.glm_diagnostics(predictors, outcome, options?) | stats_glm_diagnostics | Diagnose binomial or Poisson fits, including convergence, dispersion, influence, and calibration or zero-count clues. |
stat.random_intercept_model(predictors, outcome, clusters, options?) | stats_random_intercept_model | Fit fixed effects plus one declared random intercept and report pooling and variance components. |
stat.cox_diagnostics(time, event, predictors, options?) | stats_cox_diagnostics | Fit a Cox model and inspect intervals, likelihood, baseline hazard, concordance, and residual clues. |
Model checks that matter
- Check
convergedbefore interpreting a GLM. A non-converged logistic fit can still return misleading coefficients. random_intercept_modeldoes not fit random slopes, nested effects, or crossed effects.- The Cox Schoenfeld screen is descriptive; it is not a formal replacement for
cox.zph. - Diagnostics can reveal tension with a model, but cannot repair a weak study design.
Raw builtins versus the public API
The runtime performs deterministic full-data calculations under the stats_* names. The package wrappers keep user code short and stable. For example, stat.explore calls stats_explore, and stat.glm_diagnostics calls stats_glm_diagnostics. Use the raw name only when developing or testing the runtime itself.
Where to go next
- Use Math & Statistics for direct summaries, hypothesis tests, probability functions, and mathematical operations.
- Use the Practical Biostatistics book for visual, concept-first learning.
- Keep the returned record with your analysis so the evidence, options, backend, and limitations remain auditable.