Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Derived Variables — Transforms & Binning

Real measurements rarely arrive in the shape a control chart wants. A response can be heavily skewed, a rate lives on a bounded proportion scale, or the variable you’d most like to subgroup by is a continuous covariate with no natural categories. Derived variables let you reshape columns before you chart them:

  • Transforms (continuous → continuous): log, sqrt, arcsin(√x), inverse, square, power, zscore — tame skew, stabilize variance, or standardize.

  • Binning (continuous → ordered categorical): equal_freq, equal_width, breaks, sd — turn a continuous covariate into a rational-subgrouping factor, with labels you control.

Two ideas make this safe and reproducible:

  1. Derivations are specifications, not code. Each is a small serializable object you can inspect, edit, and round-trip to JSON — never a stored function.

  2. formulate() is the immutability boundary. Pending derivations ride on the ProcessBehavior; only when you formulate() do they materialize into the analytic dataset (freezing any data-dependent fit, like bin edges or a z-score’s μ/σ) and become referenceable by response= / factors= / time=.

We’ll work a fabricated craft-brewery fermentation log throughout.

The data

Twelve weeks of fermentation, four fermenters (A–D) each held near a different temperature setpoint, three replicate gravity readings per fermenter per week. Each reading records the fermentation temperature, the yeast cell count (cells/mL — a classically right-skewed measurement), and the attenuation (the fraction of sugar converted, a proportion in [0, 1]).

Transforms — taming a skewed response

cell_count is right-skewed (mean well above median). A natural log pulls it back toward symmetry, which is what a chart of individual values wants to see.

evaluate(spec, column) previews a derivation against a column without attaching it to a dataset — it’s the same primitive the application’s live preview uses. (To actually use a derivation, you attach it with pb.transform(...), which returns a new ProcessBehavior — derivations never mutate in place.)

arcsin for proportions

attenuation is a proportion in [0, 1], so its variance is largest in the middle and shrinks at the extremes. The variance-stabilizing arcsine-root transform (arcsin here means arcsin(√x)) is the classic remedy. Its domain is the closed interval [0, 1]; floating-point noise a hair past a boundary (e.g. 1.0000000002) is clamped to it rather than flagged, while a value genuinely outside is reported as a domain violation. (ln is an accepted alias for log.)

zscore standardization

zscore is data-dependent: it fits the mean and standard deviation (sample SD, ddof=1) of the column. Those fitted parameters are returned in fitted and are frozen when you formulate(), so the standardization stays reproducible afterward.

Binning — a continuous covariate becomes an ordered factor

We measured fermentation_temp but never labeled temperature zones. Binning creates them. The default equal_freq method cuts the column into n quantile bins; the result is an ordered categorical. The fitted bin edges are recorded, and the default range labels are derived from those real edges.

Changing the bin labels

The bins are fixed by the data, but what you call them is up to you. bin_labels accepts:

  • 'range' (default) — the fitted interval edges, e.g. [18.65, 21.3);

  • 'ordinal' — Low, Medium-Low, Medium-High, High;

  • 'number' — Bin 1, Bin 2, …;

  • an explicit list of names — here the brewer’s own vocabulary.

The same four bins, relabeled four ways:

An explicit list must have exactly one name per fitted bin — validate() (below) catches a miscount before anything is committed.

Other binning methods

  • equal_width — bins of equal temperature span rather than equal count.

  • breaks — your own cut points. Values outside the range fall into explicit below/above categories, so a brewer’s known optimal band (say 18–22 °C) becomes three honest groups.

  • sd — zones at ±1σ / ±2σ around the mean (the SPC “zones” idea), fitting μ and σ.

(With equal_freq, ties can collapse duplicate edges so the fitted bin count is below the requested n; when that happens it’s reported in EvalResult.message rather than silently relabeled.)

Putting it together — formulate with derived columns

Now we chain the verbs and formulate(). We bin the temperature into the brewer’s named zones, log-transform the cell count, and analyze cell_count_log subgrouped by fermentation_temp_bin over week. The derived names are referenceable because they were attached before formulate() — a derivation added afterward would need a fresh formulate() to take effect.

The binned factor charts in zone order (Cool → Hot), not alphabetically — the ordered categorical is preserved end to end.

Inspect, edit, and persist

The resolved derivations live on the study, fit-frozen. Each is a plain spec that round-trips to a dict (stable id and all), so saving a study preserves them exactly. And validate(spec, dataset) gives the application a structured pass/fail before committing — domain and label problems are returned as data, not raised as exceptions.

You can also revise the pending set on a ProcessBehavior before formulating — pb.remove_derived(id) and pb.replace_derived(id, new_spec) key off the stable id, never the (editable) label.

When to derive

  • Transform to stabilize. Log a skewed response, arcsine-root a proportion, z-score to put variables on a common scale — so the chart sees a well-behaved series.

  • Bin to subgroup. Turn a continuous covariate into an ordered factor when that is the rational way to group the process, and label the zones in the operator’s own language.

  • Trust the boundary. Data-dependent fits freeze at formulate(); the analytic dataset stays immutable; nothing is silently recomputed.

Box–Cox, derived-on-derived chaining, and free-form expressions are intentionally out of scope for this first version.