Technical reference: adult algorithm

2026-11-08

Purpose. This is a technical reference with an extremely detailed description of the adult algorithm. It is primarily intended for developers, but may be of interest to people who want to know more about how growthcleanr works.

This narrative documents the adult algorithm cleaning steps (Adult Steps 1, 2W, 3, 4W, 9Wa/9H/9Wb, 10H/10W, 11H/11Wa/11Wa2/11Wb, 13, 14).

Wrapper-level material lives in vignette("wrapper"): the cleangrowth() public interface, input format, unit conversion, batching, dispatch, output reassembly, and cross-algorithm working-dataframe differences (the adult algorithm’s copies-into-shrinking-dataframes pattern is contrasted there with the child algorithm’s single-data.df pattern).

The adult algorithm works with raw measurements (and BMI computed internally for some thresholds); it does not use the recentered WHO/CDC z-scores that drive the child algorithm. The one exception is Evil Twins (Adult Step 9Wa), which uses a small fixed-NHANES conditional-SD (CSD) reference for the unit-error-range flag and the median-band |z| tiebreak (see the Support Functions cross-reference to vignette("algorithm-functions")). The Z-Score Infrastructure section in the wrapper narrative is otherwise not load-bearing for the adult algorithm but may be useful context for cross-algorithm work.


Key concepts

EWMA: Exponentially Weighted Moving Average — a weighted average of a subject’s other weight measurements, where measurements closer in time receive exponentially more weight. The exponent is e=−5, making the nearest neighbor almost entirely dominant; this is closer to a near-neighbor comparison than a traditional moving average. Three variants are computed for each observation: ewma_all (all other values), ewma_before (excluding the immediately preceding value), and ewma_after (excluding the immediately following value). The before/after variants are used as confirmation — a value is only excluded when the overall and both directional deviations agree. Edge values (first/last in subject) have only one neighbor, so before/after falls back to ewma_all. Window parameter ewma_window (default 15) limits how many observations on each side contribute.

Ordered steps: Removing more extreme problems first, getting more refined at the end

Iterations: Many exclusion steps use a dynamic loop: one value is excluded per round (the worst candidate by a defined scoring rule), then the EWMA and thresholds are recomputed from scratch on the remaining values, and the process repeats. The loop runs until no candidates remain or fewer than 3 values are left. This single-worst-out-per-round approach prevents a cluster of outliers from masking each other — removing the most deviant value first often exposes the next, which would have appeared plausible if evaluated simultaneously.

Exclusion rescue: The adult algorithm does not rescue exclusions the way the child algorithm rescues carried-forward values. The one exception is the moderate EWMA step (11Wb): when 4 or more consecutive candidates are found in a single pass, they are all excluded at once with an -Error-Load code rather than one per round (in linked mode, if any of them has RV copies, all of the subject’s remaining weights are excluded). This treats a long run of EWMA failures as evidence of overall data unreliability rather than a sequence of isolated errors.

Flexibility: Defaults to looser (moderate permissiveness). Can be made more permissive (loosest) or more restrictive (tighter, tightest).

Missing measurements: A row whose measurement is missing (NA/NaN — a parameter row present in the data but carrying no value, including a 0 converted to NaN upstream) is set aside (reported as Exclude-Missing-Info) before any cleaning step runs and does not participate in any step. This matters because the algorithm derives quantities from the measurements (BMI from a same-day height+weight pair, EWMA deviations, medians) and compares them against thresholds; a missing value left in play would propagate as NA into those comparisons. Such rows are removed from the per-subject height and weight working sets at entry, so they are never a same-day BMI partner and never enter a trajectory. They are reported as Exclude-Missing-Info in the final output.

Parameters: The adult algorithm cleans height and weight only. cleangrowth() relabels adult LENGTHCM/LENGTHIN rows to HEIGHTCM/HEIGHTIN before dispatch, so adult length is cleaned as height (the submitted label is still returned). Adult head circumference is not cleaned: cleangrowth() marks every HEADCM row at or above adult_cutpoint Exclude-HC-Out-of-Range, as for child HC past 5 years. HC rows play no part in any adult height or weight step.


Rounding Tolerance

All threshold comparisons in the algorithm apply a 0.12 unit tolerance (cm for height, kg for weight). This accommodates rounding that may occur during data extraction, transformation, and loading — specifically, rounding to the nearest 0.1 cm or 0.1 kg, plus a small margin for floating-point precision.

The tolerance makes all threshold comparisons slightly more permissive: a value must exceed a limit by more than 0.12 to be excluded. For example, if the PIV minimum height is 50 cm, a recorded value of 49.9 cm is not excluded (it is within 0.12 of the threshold). When comparing two measured values (e.g., height difference in a band check), the tolerance is applied once (0.12), not doubled.

Where tolerance is applied:

Where tolerance is NOT applied:


Permissiveness Framework

The algorithm’s aggressiveness is controlled by a permissiveness argument with four levels. Each level sets defaults for multiple settings. Individual settings can be overridden; explicit values always take precedence over the preset.

Level Goal
loosest Retain all plausible values. Allows extreme weight changes (bariatric surgery, rapid fluid status change), significant height measurement error, height gain in young adults, and height loss.
looser (default) Retain most plausible values. Slightly less extreme weights allowed. Height gain allowed; height loss beyond the 3-inch band is not.
tighter Exclude plausible values likely reflecting illness with large effect on body size. Less permissive height limits. Height gain allowed; height loss beyond the 2-inch band is not. Repeated values treated as less informative.
tightest Exclude extreme measurements even if plausible. Tightest weight and height limits. No height gain or loss allowance. Repeated values treated as less informative.

For the intended use of each level and the preset values, see Section 1D of vignette("configuration").

The allofus15 weight-allowance formula used at the tightest level was adapted from the algorithm developed by Guide et al. for the All of Us Research Program:

> Guide A, Sulieman L, Garbett S, Cronin RM, Spotnitz M, Natarajan K, Carroll RJ, Harris P, Chen Q. Identifying erroneous height and weight values from adult electronic health records in the All of Us research program. J Biomed Inform. 2024 Jul;155:104660. doi: 10.1016/j.jbi.2024.104660. Epub 2024 May 23. PMID: 38788889; PMCID: PMC11973958.

Parameters controlled by permissiveness

Setting loosest looser tighter tightest
Overall (PIV) HT limits 50–244 cm 120–230 cm 142–213 cm 147–208 cm
Overall (PIV) WT limits 20–500 kg 30–270 kg 36–159 kg 39–136 kg
Overall (PIV) BMI limits¹ 5–300 12–65 16–45 18–40
Single (1D) limits wider BMI-dependent split (see Adult Step 13) same as PIV same as PIV same as PIV
wtallow_formula PW-H ("piecewise") PW-H ("piecewise") PW-L ("piecewise-lower") allofus15 ("allofus15")
UW scaling UW-based (see the wtallow formulas section) UW-based UW-based cap limited by PW-L
ET caps wtallow cap + 20 wtallow cap + 20 wtallow cap + 20 allofus15-cap-12m
perclimit_low (wt ≤45 kg) 0.5 0.5 0.7 0.7
perclimit_mid (45<wt≤80 kg) 0.4 0.4 0.4 0.4
perclimit_high (wt >80 kg) 0 (disabled) 0 (disabled) 0.4 0.4
error_load_threshold 0.41 0.41 0.29 0.29
mod_ewma_f 0.75 0.75 0.60 0.60
ht_band 3” 3” 2” 2”
allow_ht_loss TRUE FALSE FALSE FALSE
allow_ht_gain TRUE TRUE TRUE FALSE
repval_handling independent independent linked linked

¹ Adult Step 1 applies these limits only when they are wider than the single-value BMI limits (at the presets, loosest only). At the other levels they equal the single-value limits, which are applied at Adult Step 13.

Single source of truth for these preset values: vignette("configuration") §1D (which also lists the exact parameter names and per-step roles). The table above is a reading convenience; if the two ever disagree, §1D and the code (permissiveness_presets()) are canonical.

Implementation

The permissiveness argument defaults to "looser". All settings default to NULL in the function signature. At the start of cleanadult(), resolve_permissiveness() fills NULLs from the preset table and passes through any non-NULL (user-specified) values. This means:

The parameter names in the step tables below are the names used in adult_overrides, except max_rounds (fixed, not overridable), scale_max_lbs (set with adult_scale_max_lbs), and ewma_window (set with ewma_window).

Parameters NOT controlled by permissiveness: scale_max_lbs (default Inf), ewma_window (default 15), quietly (default FALSE).


Exclusion codes and output levels

Throughout this narrative, each step’s “Exclusion Code(s)” row and inline references use the algorithm’s internal literal codes — the strings cleanadult() actually writes to its result column (Exclude-A-PIV, Exclude-A-Scale-Max, Exclude-A-Traj-Moderate, the RV / firstRV / allRV / -All / -RV-Propagated / -Error-Load variants, etc.). These internal literals are not renamed in the code.

At the end of cleangrowth(), a mapping layer translates each internal code into the two reported output levels:

Adult internal → Detailed → Summary mapping (canonical catalog + child side: vignette("exclusion-codes")):

Code (internal) Adult Step Detailed (exclude_detailed) Summary (exclude)
Exclude-A-PIV 1H/1W Exclude-A-Hard-Limit Exclude-Hard-Limit
Exclude-A-Scale-Max (+ -Identical, -RV-Propagated) 4W Exclude-A-Scale-Max Exclude-Scale-Max
Exclude-A-Evil-Twins 9Wa Exclude-A-Evil-Twins Exclude-Extreme
Exclude-A-Identical 9H/10W Exclude-Identical Exclude-Identical
Exclude-A-Extraneous 9H/10W Exclude-Extraneous Exclude-Extraneous
Exclude-A-Traj-Extreme (+ -firstRV, -allRV, -firstRV-RV-Propagated) 9Wb Exclude-A-Traj-Extreme Exclude-Extreme
Exclude-A-Ord-Pair (+ -All) 10Ha Exclude-A-Ord-Pair Exclude-Pattern
Exclude-A-Window (+ -All) 10Hb Exclude-A-Window Exclude-Pattern
Exclude-A-2D-Ordered 11Wa Exclude-A-2D-Ordered Exclude-Pattern
Exclude-A-2D-Non-Ordered 11Wa2 Exclude-A-2D-Non-Ordered Exclude-Pattern
Exclude-A-Traj-Moderate (+ -allRV, -RV-Propagated) 11Wb Exclude-A-Traj-Moderate Exclude-Pattern
Exclude-A-Traj-Moderate-Error-Load (+ -RV) 11Wb Exclude-A-Traj-Moderate-Error-Load Exclude-Pattern
Exclude-A-Single 13 Exclude-A-Single Exclude-Single
Exclude-A-Unevaluable-Trajectory 14 Exclude-A-Unevaluable-Trajectory Exclude-Traj-Uneval

Adult has no Exclude-Pair at the Summary level — the adult 2-distinct-value HT pair/window checks are trajectory disagreement and fold into Exclude-Pattern (the child Exclude-Pair means a sparse 2-measurement subject, a different concept).


List of steps

For the most part, HT and WT are evaluated separately, although there are some shared steps. See separate tables for HT and WT steps below.

Adult Step numbering is not sequential. Adult Steps 5–8 and 12W are not present in the algorithm; numbering is preserved for consistency with prior documentation.

Height steps (include steps shared by HT and WT):

Adult Step # Adult Step title Brief description
1H PIV Exclude population implausible heights
3H Temp SDE Temporarily flag same-day height duplicates
9H Final SDE Height Resolve same-day height duplicates: exclude identical, choose keeper by category
10H Height Distinct Evaluate 2D pairs (band, loss/gain rescue, frequency) and 3+D windows (w2, loss/gain groups)
11H Mean Height Calculate mean height per subject (group-aware for loss/gain groups)

Weight steps (include steps shared by HT and WT):

Adult Step # Adult Step title Brief description
1W PIV Exclude population implausible weights
2W Repeated Values Identify groups of identical weight values; mark first vs. subsequent
3W Temp SDE Temporarily flag same-day weight duplicates
4W Scale Max Exclude weights at physical scale maximum unless EWMA confirms
9Wa Evil Twins Exclude inner members of an over-the-limit weight pair flanked by stable anchors (anchor rule)
9Wb Extreme EWMA Exclude weight outliers with large EWMA deviations (interval-specific caps)
10W Final SDE Weight Resolve same-day weight duplicates: exclude identical, choose keeper by category
11Wa 2D Ordered Weight Exclude 2D ordered weight pairs outside wtallow or perclimit
11Wa2 2D Non-Ordered Weight Exclude 2D interleaved weight pairs using wtallow + dominance rules
11Wb Moderate EWMA Exclude weight outliers using 7-step EWMA flow with trajectory rescue and error load
13 Single Distinct (1D) Exclude 1D height/weight values outside BMI-based or no-BMI limits
14 Error Load Exclude all remaining values when error ratio exceeds threshold

Configurable Arguments

For wrapper-level arguments (id, adult_cutpoint, sd.recenter, ref.data.path, batch_size, parallel, cores, log.path, ref_tables, tri_exclude, quietly), see vignette("wrapper") → Configurable Arguments (wrapper-level).

For the full cross-algorithm parameter and threshold index (including all hardcoded adult thresholds by step), see vignette("configuration").

This table covers adult-specific arguments exposed via cleangrowth(). All permissiveness settings (PIV limits, 1D limits, wtallow_formula, mod_ewma_f, perclimit_*, ht_band, allow_ht_loss, allow_ht_gain, repval_handling, error_load_threshold) are set by adult_permissiveness and documented in full in vignette("configuration") Section 1D. Individual settings can also be overridden without switching preset via the adult_overrides named list, e.g. cleangrowth(df, adult_overrides = list(overall_ht_min = 120)).

Argument Default Used in Description
adult_permissiveness "looser" All adult steps (via preset resolver) Permissiveness level: "loosest", "looser", "tighter", "tightest". Sets all settings unless individually overridden. See vignette("configuration") Section 1D for the full preset table.
adult_scale_max_lbs Inf Adult Step 4W Physical scale upper limit in pounds. Weights within ±0.1 kg of the cap are excluded if they are more than 50 kg different from their expected value, or if all of the subject’s weights (at least two) are the same value at the cap.
ewma_window 15 Adult Steps 9Wb, 11Wb Maximum observations on each side contributing to EWMA weighting. Shared with child algorithm — a single cleangrowth() argument affects both.

Support Functions

Algorithm-internal helpers defined in R/adult_support.R. Each entry lists purpose, callers, input/return contract, key invariants, and approximate code location. For step-implementing functions already fully documented in their step sections, this section provides a brief cross-reference only. For wrapper-level helpers shared across algorithms, see vignette("wrapper") → Shared Helpers.


Adult CSD reference + unit-error-range helpers (.adult_csd_ref(), .adult_csd_lookup(), .adult_z3(), .adult_csd_z(), .unit_error_range_flag())

Cross-reference. These cross-algorithm helpers — the small fixed sex-specific NHANES CSD reference (.adult_csd_ref() and its lookup/z3/per-value-z wrappers) and the shared unit-error-range band logic (.unit_error_range_flag()) — are documented in full in vignette("algorithm-functions"). They are defined in R/adult_support.R so both the adult algorithm and the standalone adult test harness can reach them. In the adult algorithm they are consumed inside Evil Twins (Adult Step 9Wa): .adult_z3() + .unit_error_range_flag() build the per-value sp_uer flag, and .adult_csd_z() provides the UER3 plausible-group split and the median-band |z| tiebreak (see the Step 9Wa walkthrough and the “Adult CSD z” / “UER3” definitions there). The constants’ provenance (NHANES 2017–2020 + 2021–2023) is recorded in that reference and in the .adult_csd_ref() roxygen.


Permissiveness configuration: permissiveness_presets() / resolve_permissiveness()

Purpose. permissiveness_presets() returns a named list of four complete parameter sets (loosest, looser, tighter, tightest), each containing the full set of adult algorithm thresholds and flags. resolve_permissiveness() merges a preset with user-supplied parameter overrides: any non-NULL override replaces the preset value; NULL values fall through to the preset default.

Callers. permissiveness_presets() is exported and may be called directly by users. It is also called from within resolve_permissiveness(). resolve_permissiveness() is called once at the top of cleanadult() before any algorithm steps run.

Inputs and return contract.

Key invariants. All four preset lists share the same parameter names. The resolved list keys match the named parameters that cleanadult() unpacks and passes to each step.

Code location. R/adult_support.R.


Utility helpers: check_between() / round_pt()

Purpose. Small one-liners used throughout the algorithm. check_between(vect, num_low, num_high, incl = TRUE) returns a logical vector indicating whether elements of vect fall within the given range (inclusive by default; incl = FALSE for strict). round_pt(val, pt) rounds val to the nearest multiple of pt (e.g. round_pt(78.14, 0.2) → 78.2).

Callers. check_between() is called from cleanadult() at Adult Step 4W (weight cap range check at two sites) and Adult Step 10H (height w2-window inclusion/exclusion). round_pt() has no active callers (it is listed in var_for_par for parallel export only); compute_trajectory_fails() performs the equivalent 0.2 kg rounding inline (round(x / 0.2) * 0.2), not via round_pt().

Inputs and return contract. Both functions are fully vectorized. check_between() returns a logical vector the same length as vect. round_pt() returns a numeric vector the same length as val. No input validation — callers are responsible for non-NA numeric inputs.

Code location. R/adult_support.R.


Adult EWMA: as.matrix.delta_dn() / ewma_dn()

Purpose. as.matrix.delta_dn(agedays) builds an n × n matrix of pairwise absolute age differences — the weight matrix used by adult EWMA calculations. ewma_dn() is the original adult EWMA function using |delta|^(–5) weighting (the adult exponent; the child algorithm uses (5 + |delta|)^(–1.5) instead). It returns three variants: EWMA-all, EWMA-before (excluding predecessor), EWMA-after (excluding successor).

Callers. as.matrix.delta_dn() is called from adult_ewma_cache_init() (and was also used by ewma_dn()). ewma_dn() has no active callers in the current algorithm — it was superseded by adult_ewma_cache_init() when the cache-based approach was introduced. Both functions are listed in var_for_par (the parallel-worker export list in child_clean.R) but ewma_dn() is not invoked from any algorithm step or support function.

Inputs and return contract.

Key invariants. Input should be sorted by age. The window parameter zeroes out weight-matrix entries more than ewma_window positions from the diagonal (position-based, not age-based). For n ≤ 2, all three EWMA variants equal ewma.all.

Code location. R/adult_support.R.


Adult EWMA cache: adult_ewma_cache_init() / adult_ewma_cache_update()

Purpose. adult_ewma_cache_init() builds the full weight matrix once (O(n²)) and returns pre-computed EWMA-all, EWMA-before, and EWMA-after vectors as a plain named list. adult_ewma_cache_update() was designed to remove one observation from the cache in O(n) arithmetic — a performance path that avoids rebuilding the full matrix each round.

Callers. adult_ewma_cache_init() is called from remove_ewma_wt() (Adult Step 9Wb) and remove_mod_ewma_wt() (Adult Step 11Wb) — once per iteration round in each. adult_ewma_cache_update() has no active callers: both step functions rebuild the full cache from scratch each round rather than using the incremental update.

Inputs and return contract.

Key invariants. Input must be sorted by age. For n ≤ 2, EWMA-before and EWMA-after equal EWMA-all (no distinct predecessor/successor to remove). The return from adult_ewma_cache_update() has the same structure as adult_ewma_cache_init() and can be passed back to adult_ewma_cache_update() iteratively.

Code location. R/adult_support.R.


PIV detection: remove_piv() / remove_piv_low() / remove_piv_high()

Purpose. remove_piv() is the PIV dispatcher: returns a logical vector indicating which rows in subj_df are population implausible — too low OR too high. remove_piv_low() and remove_piv_high() are one-line sub-helpers that check the low and high bounds independently, each applying the 0.12 cm/kg rounding tolerance.

Callers. remove_piv() is called from cleanadult() at Adult Steps 1H and 1W, once per parameter per subject.

Inputs and return contract.

Key invariants. The 0.12 rounding tolerance is applied at every PIV check in the algorithm. piv_df must have a row named type and columns named “low” and “high”.

Code location. R/adult_support.R.


Repeated value detection: identify_rv() / redo_identify_rv()

Purpose. identify_rv() scans a single-subject weight data frame for repeated (identical) measurement values and adds two boolean columns: is_rv (TRUE for all occurrences after the first) and is_first_rv (TRUE for the first occurrence of any value that has at least one duplicate). Uses exact numeric equality. redo_identify_rv() handles the case where temp_sde() marked an is_first_rv row as extraneous — it subsets to non-extraneous rows, re-runs identify_rv(), and maps results back to the full data frame so the RV group still has a valid is_first_rv.

Callers. identify_rv() is called from cleanadult() at Adult Step 2W (initial RV scan after PIV) and again after non-SDE exclusions (weight cap, evil twins). redo_identify_rv() is called from cleanadult() immediately after each temp_sde() call that could have displaced an is_first_rv.

Inputs and return contract.

Key invariants. Exact numeric equality only — floating-point near-duplicates (e.g. 78.1 vs 78.101) are not treated as repeated values. Does not filter by extraneous status; the identify_rv → temp_sde → redo_identify_rv cycle is the caller’s responsibility to sequence correctly.

Code location. R/adult_support.R.


Temporary SDE flagging: temp_sde()

Purpose. Selects the keeper on each same-day group, marking all other rows extraneous = TRUE. The keeper is the row with the smallest absolute deviation from the subject median; ties keep the highest internal_id (consistent with final SDE resolution). For weight (ptype = "weight"), the subject median is computed over non-RV values only; for height (ptype = "height"), over all values.

Callers. Called from cleanadult() at Adult Steps 3H and 3W (initial temp SDE), and again within the Adult Step 9H and 10W final-SDE resolution sequences.

Inputs and return contract.

Key invariants. For weight, the median excludes is_rv == TRUE rows. Duplicate-day detection is character-key-based (table(subj_df$age_days) → names(...)[count > 1]), so the result is stable across integer and double age_days representations.

Code location. R/adult_support.R.


Weight allowance: compute_wtallow() / compute_et_limit() / compute_perc_limit()

Purpose. Three functions for interval- and weight-based thresholds used across multiple steps. compute_wtallow() computes the maximum plausible weight change for a given interval and, optionally, upper weight (UW). compute_et_limit() computes the Evil Twins / extreme EWMA cap (wtallow-cap + 20 kg for PW-H/PW-L; allofus15 12-month cap for allofus15; fixed 70/100 for custom CSV). compute_perc_limit() returns an observation-level percentage ratio threshold tiered by weight.

See the wtallow section below for full formula derivations and design rationale.

Callers. compute_wtallow() is called from cleanadult() (Adult Steps 11Wa and 11Wa2) and from evil_twins(), remove_ewma_wt(), and remove_mod_ewma_wt(). compute_et_limit() is called from evil_twins() and remove_ewma_wt(). compute_perc_limit() is called from remove_mod_ewma_wt().

Inputs and return contract.

Key mechanics not in the wtallow formulas section.

Code location. R/adult_support.R (compute_et_limit, compute_perc_limit, compute_wtallow).


Height evaluation: ht_allow() / ht_change_groups() / ht_3d_growth_compare()

Purpose. Three helpers supporting Adult Step 10H (Height Distinct Values). ht_allow(velocity, ageyears1, ageyears2) computes the maximum expected height gain over an age range using a log-based growth model. ht_change_groups(h_subj_df, cutoff, type) groups consecutive height measurements within 2 inches (5.08 + 0.12 cm rounding tolerance) of each other; exits early if a measurement diverges by more than 2 inches from the current group in the specified direction. ht_3d_growth_compare(mean_ht, min_age, glist, compare) checks whether adjacent height groups have plausible height differences, applying velocity tiers based on the inter-group age gap, with a 25-year age cap.

Callers. All three are called exclusively from cleanadult() in the Adult Step 10H block. ht_allow() is called only from within ht_3d_growth_compare().

Inputs and return contract.

Key invariants. ht_3d_growth_compare() skips groups indexed beyond length(glist) via next, so short group lists are safe. The 0.12 cm rounding tolerance is applied to the 5.08 cm (2-inch) threshold in ht_change_groups(). The height loss/gain check uses mh2 – mh1 relative to zero and hta, with the 0.12 tolerance applied at both bounds.

Code location. R/adult_support.R.


Trajectory helpers: detect_runs() / compute_trajectory_fails()

Purpose. detect_runs(flagged) detects consecutive runs of TRUE in a logical vector and returns run metadata for each element: run_id (which run), run_len (length of that run), and run_pos (position within the run). compute_trajectory_fails(meas, age_days, err = 5) pre-computes which observations fail all three trajectory rescue methods — returning TRUE for “not rescued” (candidate eligible for exclusion). The three rescue methods: (1) within ±err kg of the linear interpolation between the two adjacent observations; (2) within ±err kg of the extrapolation from the prior pair (p2 → p1 → current); (3) within ±err kg of the extrapolation from the next pair (n2 → n1 → current). Extrapolation is not trusted when the extrapolation distance exceeds 2× the source interval.

Callers. detect_runs() is called from remove_mod_ewma_wt() twice per round (once for the standard-pathway candidates, once for the alternate-pathway candidates). compute_trajectory_fails() is called from remove_mod_ewma_wt() once per round.

Inputs and return contract.

Key invariants. Extrapolated values in compute_trajectory_fails() are rounded to the nearest 0.2 kg inline (round(x / 0.2) * 0.2, equivalent to round_pt(x, 0.2)) before the ±err window is applied — matching the 0.2 kg rounding precision used elsewhere. The distance guard compares raw absolute age differences without rounding tolerance.

Code location. R/adult_support.R.


Evil Twins: evil_twins()

See Evil Twins (Adult Step 9Wa) in this narrative for full documentation.

Brief. Applies the anchor rule — excludes an inner member of an over-the-limit (OTL) weight pair (inner B–C jump > ET cap) only when both outer pairs (A–B, C–D) anchor within 60% of their cap, ceding single spikes to the EWMA steps. One exclusion per iteration, re-anchoring on the remaining values, until no OTL pair remains or fewer than 4 eligible values are left. Selection uses a windowed-median deviation (absd_med) with a UER3 unit-error check and a median band; the |z| tiebreak and UER3 plausible group use adult CSD z-scores. RVs participate as ordinary values. Returns a character vector of internal_id values to exclude.

Code location. R/adult_support.R.


Extreme EWMA + RV propagation: remove_ewma_wt() / propagate_to_rv()

See Extreme EWMA (Adult Step 9Wb) in this narrative for the remove_ewma_wt() algorithm description.

Brief — remove_ewma_wt(). Iteratively excludes weight outliers whose EWMA deviation exceeds the interval-specific ET cap (with 90% directional rule on before/after dewma). Rebuilds the EWMA cache from scratch each round via adult_ewma_cache_init(). Returns a named character vector (internal_id → exclusion code).

propagate_to_rv(). Extends firstRV exclusions to matching RV copies and extraneous values in linked-mode processing. For each excluded internal_id, finds all still-Include rows in the subject’s weight data with the same meas_m value and marks them with the source exclusion code + "-RV-Propagated". Propagation is forward-only (firstRV → later copies); this differs from the bidirectional Scale-Max propagation in Adult Step 4W. Called from cleanadult() in both the Extreme EWMA (Adult Step 9Wb) and Moderate EWMA (Adult Step 11Wb) linked-mode firstRV passes.

Inputs and return contract (propagate_to_rv).

Code location. R/adult_support.R (remove_ewma_wt, propagate_to_rv).


Moderate EWMA: remove_mod_ewma_wt()

See Moderate EWMA (Adult Step 11Wb) in this narrative for full documentation of the 7-step flow.

Brief. Iteratively excludes weight outliers using wtallow-based thresholds, trajectory rescue, alternate pathway (short-gap neighbors), percentage criterion, and 4+-consecutive error-load detection. Rebuilds the EWMA cache from scratch each round. Returns a named character vector (internal_id → exclusion code).

Code location. R/adult_support.R.


2D non-ordered pairs, 1D evaluation, error load: eval_2d_nonord() / eval_1d() / eval_error_load()

See 2D Non-Ordered Weight Pairs (Adult Step 11Wa2), Single Distinct Value: 1D Evaluation (Adult Step 13), and Error Load (Adult Step 14) in this narrative for full documentation.

Brief summaries:

Code locations. eval_2d_nonord(), eval_1d(), and eval_error_load() (in file order; eval_error_load() is at the end of the file).


PIV: Population Implausible Values (Adult Step 1)

Exclude values outside absolute biological limits.

Scope Height and Weight (individually), then same-day ht+wt pairs (BMI check)
Prior Adult Step None (first step)
Next Adult Step 2W: Repeated Values (Weight); 3H: Temp SDE (Height)
Distinct/RVs N/A — runs before RV identification
Exclusion Code Exclude-A-PIV
Configurable parameter names overall_ht_min, overall_ht_max, overall_wt_min, overall_wt_max, overall_bmi_min, overall_bmi_max

Overview:

The first step removes values that are biologically impossible for an adult human. These absolute outer limits are deliberately very wide — they are meant to catch only values that could not possibly be correct (e.g., data entry errors, unit errors that produce extreme values). More refined exclusion of implausible-but-not-impossible values happens in later steps.

Key terms and variable names:

Configurable setting defaults and options:

Setting loosest Unit Notes
overall_ht_min 50 cm
overall_ht_max 244 cm
overall_wt_min 20 kg Not pounds — specified because scale_max_lbs uses pounds
overall_wt_max 500 kg Not pounds — specified because scale_max_lbs uses pounds
overall_bmi_min 5 kg/m² Only meaningfully different from single_bmi_min at loosest (5 vs 10); same at other levels
overall_bmi_max 300 kg/m² Only meaningfully different from single_bmi_max at loosest (300 vs 250); same at other levels

Values shown are for loosest; see vignette("configuration") §1D for the other levels.

These thresholds use strict inequalities with 0.12 rounding tolerance (see Rounding Tolerance section): a value must be more than 0.12 below the minimum or above the maximum to be excluded. The permissiveness parameter provides preset less-permissive defaults that tighten these (and other) limits; explicit parameter values override permissiveness presets.

Logic and implementation:

  1. For each subject, evaluate all height values against the height PIV limits
  2. Exclude any height outside [overall_ht_min, overall_ht_max] (with 0.12 tolerance)
  3. For each subject, evaluate all weight values against the weight PIV limits
  4. Exclude any weight outside [overall_wt_min, overall_wt_max] (with 0.12 tolerance)
  5. BMI PIV check (Adult Step 1B): Runs only when overall_bmi_min < single_bmi_min or overall_bmi_max > single_bmi_max — i.e., only when the PIV BMI limits are wider than the Adult Step 13 single-value limits. In practice this means loosest only (overall BMI [5,300] vs single BMI [10,250]). At other levels the two sets of limits are equal, so skipping the check avoids applying thresholds earlier than the algorithm intends.
  6. For each ageday where both a surviving height and a surviving weight exist, pair the first height and first weight (by internal_id order) and compute BMI = weight_kg / (height_cm / 100)²
  7. Exclude that ht and wt pair if BMI < overall_bmi_min or BMI > overall_bmi_max. No rounding tolerance is added to the BMI comparison: tolerance is already embedded in the measurement-level checks above, and BMI is a derived value
  8. Excluded values are removed from the working dataframes so they do not participate in subsequent steps
  9. There is no need to check for same-day duplicates before this step because all values outside these limits are excluded regardless

Rationale for selected decisions:


Repeated Value Markers (Adult Step 2W, weight only)

Identify groups of identical weight values and distinguish first occurrence from subsequent repeats.

Scope Weight only
Prior Adult Step 1W: PIV
Next Adult Step 3W: Temp SDE
Distinct/RVs Creates RV markers (this is where RVs are defined)
Exclusion Code None — this step marks but does not exclude
Configurable parameter names repval_handling (affects later EWMA steps, not this step)

Overview:

This step identifies repeated values (RVs) — weight measurements that appear more than once for a subject. The first occurrence is distinguished from subsequent identical values. RVs are not excluded at this point; instead they are flagged so that later steps (particularly the EWMA steps) can handle them differently depending on the repval_handling mode.

Height does not use RV tracking. While height does identify same-day duplicates (Adult Step 3H), it does not distinguish first vs. subsequent occurrences of the same height value across different days.

Key terms and variable names:

Configurable setting defaults and options:

Setting Default Options Notes
repval_handling "independent" "independent", "linked" Does not affect this step directly; controls how RVs are handled in EWMA steps (9Wb, 11Wb)

Logic and implementation:

  1. For each included weight value (in meas_m), count how many times it appears for the subject
  2. Values appearing only once: is_first_rv = FALSE, is_rv = FALSE
  3. Values appearing more than once:
    • The first occurrence (earliest age, internal_id tiebreaker): is_first_rv = TRUE
    • All subsequent occurrences: is_rv = TRUE
  4. RV identification depends on the dataframe being pre-sorted by age and internal_id
  5. After later exclusion steps (weight cap, evil twins), RVs are re-identified on the remaining values:
    • identify_rv() is called fresh on the remaining rows
    • temp_sde() is re-run
    • redo_identify_rv() cleans up if SDE marked a first_rv as extraneous

Rationale for selected decisions:


Temp Same-Day Extraneous: SDE (Adult Step 3)

Temporarily flag same-day values that deviate most from patient median.

Scope Height (3H) and Weight (3W), evaluated separately
Prior Adult Step 1H: PIV (Height); 2W: Repeated Values (Weight)
Next Adult Step 4W: Weight Cap (Weight); 9H: Final SDE Height Resolution (Height)
Distinct/RVs WT median uses non-RV values only; HT median uses all values
Exclusion Code None — values are flagged as extraneous, not excluded yet
Configurable parameter names None

Overview:

When a subject has multiple measurements on the same day, this step temporarily identifies which value to keep and which to flag as extraneous. For each same-day group, the value closest to the patient’s overall median survives; the others are flagged extraneous = TRUE. This is a temporary designation — flagged values remain in the working dataframe but are skipped by most subsequent steps. Final SDE resolution (where extraneous values are actually excluded or rescued) happens later (Adult Step 9H for height, Adult Step 10W for weight).

Key terms and variable names:

Configurable setting defaults and options:

None. This step has no configurable settings.

Logic and implementation:

  1. Identify days with multiple measurements for the subject (same age_days)
  2. Calculate the patient’s overall median:
    • Weight: Median of all included non-RV weight values
    • Height: Median of all included height values
  3. For each value on a same-day group, compute diff = abs(measurement - median)
  4. Within each same-day group, keep the value with the smallest diff (highest internal_id tiebreaker); mark all others as extraneous = TRUE
  5. Weight only: After flagging, call redo_identify_rv() to update RV identification in case a first_rv was flagged extraneous
  6. Store extraneous flags in the subject-level tracking vector (h_extraneous or w_extraneous)

Rationale for selected decisions:


Scale Max: Weight Cap (Adult Step 4W)

Exclude weights at a physical scale maximum unless EWMA and adjacency checks confirm the value fits the patient’s pattern.

Scope Weight only
Prior Adult Step 3W: Temp SDE
Next Adult Step 9Wa: Evil Twins
Distinct/RVs EWMA and adjacency use firstRV (non-RV) values; RVs of excluded cap values are also excluded
Exclusion Codes Exclude-A-Scale-Max, Exclude-A-Scale-Max-Identical, Exclude-A-Scale-Max-RV-Propagated
Configurable parameter names scale_max_lbs

Overview:

Many clinical scales have a physical upper limit (commonly 400 lbs). When a weight exactly equals this limit, it is likely a truncated reading rather than a true weight. This step identifies weights near the scale maximum and excludes them unless they are consistent with the patient’s other weights (as determined by EWMA and adjacency checks). This step is optional — it only runs when scale_max_lbs is set to a finite value.

There are two cases:

  1. All distinct weights at cap: If a subject’s only distinct non-RV weight is the cap value, all of the subject’s weights are excluded (code: Scale-Max-Identical).
  2. Mixed cap and non-cap values: Individual cap values are evaluated using EWMA delta and adjacency difference criteria. Values must be >50 kg away from expected by both measures to be excluded (code: Scale-Max). RVs of excluded values are also excluded (code: Scale-Max-RV-propagated in linked mode).

Key terms and variable names:

Configurable setting defaults and options:

Setting Default Unit Notes
scale_max_lbs Inf pounds Inf disables this step entirely. Common value: 400 (lbs).

The cap detection range is scale_max_lbs converted to kg and rounded to 0.1 kg (round(scale_max_lbs / 2.2046226, 1)), ± 0.1 kg, using inclusive bounds.

Logic and implementation:

  1. Skip if scale_max_lbs == Inf or fewer than 2 included non-extraneous values
  2. Convert scale_max_lbs to kg; define detection range as kg rounded to 0.1 kg (round(kg, 1)) ± 0.1 kg
  3. Identify non-RV values at the cap and count distinct non-RV weights
  4. If only 1 distinct non-RV weight and it’s the cap: Exclude all of the subject’s weights with Scale-Max-Identical
  5. If mixed cap and non-cap, and >1 distinct non-RV weight: a. Calculate EWMA on non-RV values (exponent = -5), including ewma_before (EWMA excluding the immediately preceding observation) and ewma_after (EWMA excluding the immediately following observation). At edges, before/after EWMA equals the full EWMA. b. Calculate adjacency diffs (difference from previous and next non-RV values) c. For positive outliers (dewma50p): dewma_all > 50 + 0.12 AND dewma_before > 45 + 0.12 AND dewma_after > 45 + 0.12 AND dewma_all is not NA d. For negative outliers (dewma50m): dewma_all < −(50 + 0.12) AND dewma_before < −(45 + 0.12) AND dewma_after < −(45 + 0.12) AND dewma_all is not NA e. For positive adjacency (d50p): d_prev > 50 + 0.12 AND d_next > 50 + 0.12. Missing neighbors (first/last in series): treated as +Inf (confirms exclusion). f. For negative adjacency (d50m): d_prev < −(50 + 0.12) AND d_next < −(50 + 0.12). Missing neighbors: treated as −Inf (confirms exclusion). g. Exclude cap values where (dewma50p AND d50p) OR (dewma50m AND d50m)
  6. Propagate exclusion to RVs of excluded cap values
  7. After exclusions: re-run identify_rv(), temp_sde(), redo_identify_rv() on remaining values

Rationale for selected decisions:


Evil Twins (Adult Step 9Wa)

Exclude the inner members of an over-the-limit weight pair flanked by stable values (the anchor rule).

Scope Weight only
Prior Adult Step 4W: Scale Max
Next Adult Step 9H: Final SDE Height Resolution; 9Wb: Extreme EWMA
Distinct/RVs RVs participate as ordinary values (no special handling)
Exclusion Code Exclude-A-Evil-Twins
Configurable parameter names wtallow_formula (shared with EWMA steps)

Overview:

Evil twins are runs of consecutive extreme weights that distort each other’s EWMA, so that neither looks out of line on a neighbor-weighted basis and the EWMA steps alone would miss them. This step catches them up front using the anchor rule: of four age-ordered eligible weights A, B, C, D, the inner B–C jump must exceed the ET cap and both outer pairs (A–B and C–D) must anchor within 60% of their own cap. Only the inner members (B and C) are excludable; A and D anchor but are not flagged.

A missing A or D disqualifies the pair, so single spikes — at the record ends and embedded between normal values — are ceded to the Extreme/Moderate EWMA steps, which have the better-informed directional 90% rule, per-observation thresholds, and missing-neighbor handling that the non-overrideable ET cap lacks. ET runs before Extreme EWMA (9Wb) so that mutually-anchoring outlier runs are removed before they can pull the EWMA toward themselves.

The step excludes one value per iteration and re-anchors on the remaining values until no over-the-limit (OTL) pair remains or fewer than 4 eligible values are left.

Key terms and variable names:

Configurable setting defaults and options:

The ET cap is derived from the wtallow formula and UW (upper weight of the pair). For PW-H and PW-L formulas, ET cap = wtallow cap at 6m or 12m + 20. For allofus15, ET cap = allofus15-cap-12m (flat). For custom CSV, ET caps are fixed at 70/100.

Formula ET cap ≤6m (UW=120) ET cap >6m (UW=120) UW scaling
PW-H (loosest/looser) 70 100 Yes — caps scale with UW; see the wtallow section below
PW-L (tighter) 53.33 73.33 Yes for UW<120 (scales down); No for UW>120
allofus15 (tightest) allofus15-cap-12m allofus15-cap-12m Cap limited by PW-L effective cap at 12m
Custom CSV 70 100 No

All thresholds include the standard 0.12 kg rounding tolerance (see Rounding Tolerance section). See the wtallow section below for full formula details including UW adjustment for high and low UW.

Logic and implementation:

  1. Pre-check: Skip if <4 non-extraneous values, or if max−min range ≤ minimum possible ET cap + 0.12 (computed via compute_et_limit(1, formula, uw = min(weights)); no pair could exceed any interval-specific cap).
  2. UER3 (once, on the initial Include set): flag each weight’s unit_error_range; if the SP splits cleanly into a unit-error group (all one CSD-z sign) and a plausible group (|CSD z| < 3) whose mean kg differ by a factor in [1.7, 2.7], set uer3 = TRUE.
  3. Repeat until no OTL pair remains or <4 eligible values: a. Remove previously excluded values; break if <4 remain. b. Compute adjacent weight differences, age intervals (months, 30.4375 days/month), and per-pair ET caps (UW = larger of the pair). c. Anchor rule: mark both inner members of every pair satisfying the inner-jump and both outer-anchor conditions above. d. If no OTL value, break. e. Select one to exclude: if a single OTL candidate, exclude it; otherwise compute absd_med (windowed median) and route — if uer3, exclude the highest |CSD z|; else form a band of candidates within 0.20·etcap(B,C) of the max absd_med — if ≥2 in band exclude the highest |CSD z|, if exactly 1 exclude the highest absd_med. Tiebreak: lowest internal_id.
  4. After all exclusions: re-run identify_rv(), temp_sde(), redo_identify_rv() on remaining values.

Rationale for selected decisions:


Final SDE Height Resolution (Adult Step 9H)

Resolve same-day height duplicates: exclude identical values, then choose keepers using category-based median selection.

Scope Height only
Prior Adult Step 3H: Temp SDE (temporary flagging)
Next Adult Step 10H: Height Distinct Pairs
Distinct/RVs Height does not use RV tracking
Exclusion Codes Exclude-A-Identical, Exclude-A-Extraneous
Configurable parameter names None

Overview:

This step does the final resolution of same-day height duplicates that were temporarily flagged in Adult Step 3H. It has two parts:

The keeper retains its original measurement value — no averaging or mean replacement is performed.

Key terms and variable names:

Configurable setting defaults and options:

None. This step has no configurable settings.

Logic and implementation:

Part A — Identical values:

  1. All values (including those temporarily marked extraneous by Adult Step 3H) remain in h_subj_df; extraneous flags identify which days have duplicates
  2. On each day with extraneous values, identify measurements that appear more than once (exact match on meas_m)
  3. Keep the first occurrence (lowest internal_id); exclude the rest with Exclude-A-Identical
  4. Re-run temp_sde() to update extraneous flags on remaining values

Part B — Non-identical SDEs:

  1. Identify remaining days with SDEs
  2. Calculate three reference medians:
    • daymed: median within each day
    • nonsdemed: median of values on non-SDE days (NA if no non-SDE days exist)
    • medmed: median of per-day medians
  3. Categorize subject:
    • Category 1 (one day): daystot == 1
    • Category 2 (high non-SDE): daystot >= 4 AND sderatio < 0.5
    • Category 3 (low non-SDE): daystot == 2 or 3 OR sderatio >= 0.5
  4. For each SDE day, choose keeper by sorting:
    • Category 1: closest to day-median, tiebreaker highest internal_id
    • Category 2: closest to non-SDE median, then day-median, then highest internal_id
    • Category 3: closest to median-of-medians, then non-SDE median, then day-median, then highest internal_id
  5. Keep the first value after sorting; exclude the rest with Exclude-A-Extraneous

Rationale for selected decisions:


Extreme EWMA (Adult Step 9Wb)

Exclude weight outliers whose EWMA deviation exceeds interval-specific caps.

Scope Weight only
Prior Adult Step 9Wa: Evil Twins
Next Adult Step 10W: Final SDE Weight Resolution
Distinct/RVs Independent: all values participate. Linked: firstRV pass (non-RV only), then propagate, then allRV pass.
Exclusion Codes Exclude-A-Traj-Extreme (independent), Exclude-A-Traj-Extreme-firstRV (linked), Exclude-A-Traj-Extreme-allRV (linked)
Configurable parameter names wtallow_formula, repval_handling

Overview:

This step uses Exponentially Weighted Moving Averages to identify weight values that deviate far from the patient’s expected trajectory. The EWMA gives exponentially more weight to measurements closest in time — with the exponent e=-5, the nearest neighbor almost completely dominates, making this a near-neighbor comparison rather than a traditional moving average.

A value is excluded when its EWMA deviation exceeds an interval-specific ET cap AND both directional deviations (before/after) confirm. The step iterates, removing one value per round (the worst outlier), until no candidates remain or fewer than 3 values are left.

Key terms and variable names:

Configurable setting defaults and options:

Setting loosest/looser tighter tightest Unit Notes
wtallow_formula "piecewise" "piecewise-lower" "allofus15" — Determines ET cap structure (ET cap = wtallow cap + 20 for PW-H/PW-L; see the wtallow formulas section)
repval_handling "independent" "linked" "linked" — "independent" or "linked" (controls single vs two-pass)
ewma_window 15 15 15 observations Max observations on each side for EWMA (not permissiveness-controlled)

The threshold is computed dynamically per observation: compute_et_limit(min_gap_months, formula, uw) where uw = max(wt, ewma). ET caps scale with UW — for higher UW they increase (PW-H only, capped at UW=180), for lower UW they decrease (PW-H and PW-L, with 2/3×UW ceiling). See the wtallow section below for complete formula details.

Logic and implementation:

Guards:

  1. Skip if no weight values remain
  2. Skip if <3 non-extraneous values
  3. Range gate: Skip if max-min of included values ≤ minimum possible ET cap + 0.12 (computed via compute_et_limit(1, formula, uw = min(weights))). If the entire range is within the smallest possible cap plus rounding tolerance, no value can exceed any cap.

Independent mode (single pass):

  1. Call remove_ewma_wt() with all non-extraneous values (including RVs)
  2. Each round: a. Compute minimum neighbor gap in months for each value. Missing gaps (edge values) set to Inf. b. Compute per-observation threshold: compute_et_limit(min_gap_months, formula, uw) where uw = max(wt, ewma_all) c. Compute EWMA (exponent e=-5, anchor a=0, window = ewma_window observations on each side) d. Positive outlier: dewma_all > threshold + 0.12 AND dewma_before > 0.9×threshold + 0.12 AND dewma_after > 0.9×threshold + 0.12 e. Negative outlier: dewma_all < -(threshold + 0.12) AND dewma_before < -(0.9×threshold + 0.12) AND dewma_after < -(0.9×threshold + 0.12) f. Edge values: ewma_before/ewma_after fall back to ewma_all, so the directional checks reduce to the dewma_all check and do not block exclusion g. Among candidates, select the one with largest |dewma_all|, then earliest age, then lowest internal_id h. Exclude with code "Exclude-A-Traj-Extreme" i. Rebuild EWMA for next round
  3. Iterate until no candidates or <3 values remain (no artificial round limit)
  4. After exclusions: re-identify RVs on remaining values

Linked mode (two-pass):

  1. firstRV pass: Run remove_ewma_wt() on non-RV values only, with label "Exclude-A-Traj-Extreme-firstRV". Apply same range gate (cap_short + 0.12) to firstRV subset.
  2. Propagate firstRV exclusions to their RV copies via propagate_to_rv()
  3. Re-identify RVs on remaining values
  4. allRV pass: Run remove_ewma_wt() on all remaining non-extraneous values (RVs now participate), with label "Exclude-A-Traj-Extreme-allRV". Apply range gate (minimum ET cap + 0.12) to remaining subset.
  5. Re-identify RVs after allRV exclusions

Rationale for selected decisions:


Height Distinct Values (Adult Step 10H)

Evaluate subjects with 2 or 3+ distinct height values: exclude pairs outside the height band (with loss/gain and frequency rescue), or values outside the best height window.

Scope Height only
Prior Adult Step 9H: Final SDE Height Resolution
Next Adult Step 10W: Final SDE Weight Resolution
Distinct/RVs Height does not use RV tracking
Exclusion Codes Exclude-A-Ord-Pair-All, Exclude-A-Ord-Pair, Exclude-A-Window-All, Exclude-A-Window
Configurable parameter names ht_band, allow_ht_loss, allow_ht_gain

Overview:

This step evaluates subjects whose remaining included heights have 2 or more distinct values. It routes to one of two substeps:

Subjects with only 1 distinct height skip this step entirely.

Key terms and variable names:

Configurable setting defaults and options:

Setting loosest looser tighter tightest Unit Notes
ht_band 3 3 2 2 inches Effective threshold in cm: ht_band * 2.54 + 0.12
allow_ht_loss TRUE FALSE FALSE FALSE — When FALSE, disables loss rescue in both 2D and 3D
allow_ht_gain TRUE TRUE TRUE FALSE — When FALSE, disables gain rescue in both 2D and 3D

Logic and implementation:

Adult Step 10Ha: 2 Distinct Heights

  1. Extract the two distinct heights (ht_1, ht_2) in age order (both in cm)
  2. Band check: abs(ht_1 - ht_2) > (ht_band * 2.54 + 0.12) — if FALSE, values are within band; skip (all remain included)
  3. If band exceeded: a. Gain rescue (pairhtgain): Only evaluated if ageyears1 < 25 (young adult). Checks:
    • Height increased: (ht_2 - ht_1) > -0.12 (in cm, with rounding tolerance)
    • Within allowance: (ht_2 - ht_1) ≤ (htallow + 2) * 2.54 + 0.12 (htallow in inches, converted to cm with tolerance; 2-inch buffer)
    • Temporal ordering: all ht_2 measurements occur after all ht_1 measurements
    • htallow coefficient by age gap: <2yr → 15.5, 2–3yr (inclusive) → 13, >3yr → 12
    • Age capped at 25 for htallow formula b. Loss rescue (pairhtloss): Only evaluated if allow_ht_loss = TRUE. Checks:
    • Height decreased: ht_1 > ht_2 - 0.12 (in cm, with rounding tolerance)
    • Within loss limit: (ht_1 - ht_2) ≤ 5 * 2.54 + 0.12 for age at ht_2 < 50, or ≤ 7 * 2.54 + 0.12 for age at ht_2 ≥ 50
    • Temporal ordering: all ht_2 measurements occur after all ht_1 measurements c. If neither rescue applies: mark all values for exclusion d. Frequency rescue: If keepht1 (ht_1 count ≥ ht_2 count × 4/3), rescue ht_1 values. Same for keepht2.
  4. Exclusion code:
    • Exclude-A-Ord-Pair if frequency rescue saved some values
    • Exclude-A-Ord-Pair-All if no frequency rescue (all excluded)

Adult Step 10Hb: 3+ Distinct Heights

Part 1 — Window (w2) evaluation:

  1. Define w2 window for each distinct height: [ht, ht + ht_band * 2.54 + 0.12]
  2. Count observations and distinct values inside each window (w2) and outside (o2)
  3. Require w2/o2 ratio ≥ 3/2 for a window to be viable
  4. If multiple viable windows: select by highest score (tot_w2 + 0.5 * numdistinct_w2); ties broken by largest mean absolute distance from w2 mean to o2 values
  5. If one viable window: use it
  6. If no viable window: exclude all values (Exclude-A-Window-All)
  7. Exclude values outside the best window (Exclude-A-Window)

Part 2 — Loss group rescue (if allow_ht_loss = TRUE):

  1. Only evaluated if any values are still marked for exclusion after w2 evaluation
  2. Form sequential height groups by iterating through values in age order:
    • Current group range within 2 inches (5.08 + 0.12 cm): extend group
    • New value > 2 inches below group minimum: start new loss group
    • New value > 2 inches above group maximum: abort (wrong direction for loss)
    • Maximum 3 groups; abort if exceeded
  3. Validate loss pattern (all comparisons in cm with 0.12 tolerance):
    • G2-G1 direction: must decrease by at least 0.12 cm
    • G2-G1 magnitude: loss ≤ 5 inches (age < 50) or ≤ 7 inches (age ≥ 50)
    • G3-G2: same direction and magnitude checks
    • G3-G1 cumulative: ≤ 6/8/9 inches depending on age pattern
  4. If all checks pass: rescue all values (clear all exclusion criteria)

Part 3 — Gain group rescue:

  1. Only evaluated if any values are still marked for exclusion AND min age < 25 AND not already rescued by loss
  2. Form sequential height groups (same as loss, but maximum 6 groups; abort if height decreases)
  3. Validate gain pattern using ht_3d_growth_compare():
    • Adjacent check (compare = “before”): each group vs. previous group
    • Cumulative check (compare = “first”): each group vs. group 1
    • Direction: gain must not drop more than 0.12 cm
    • Magnitude: gain must not exceed htallow + 0.12 cm
    • htallow coefficient by age gap: <1yr → 20, 1-3yr → 15, >3yr → 12
  4. If all checks pass: rescue all values

Rationale for selected decisions:


Final SDE Weight Resolution (Adult Step 10W)

Resolve same-day weight duplicates: exclude identical values, then choose keepers using category-based median selection.

Scope Weight only
Prior Adult Step 9Wb: Extreme EWMA
Next Adult Step 11H: Mean Height; 11Wa: Distinct Ordered Pairs (Weight)
Distinct/RVs Non-SDE median uses firstRV (non-RV) values only; re-identifies RVs after exclusions
Exclusion Codes Exclude-A-Identical, Exclude-A-Extraneous
Configurable parameter names None

Overview:

Same logic as Adult Step 9H (Final SDE Height Resolution), applied to weight. Resolves same-day weight duplicates that were temporarily flagged in Adult Step 3W. Two parts:

The keeper retains its original measurement value — no averaging is performed. After exclusions, RVs are re-identified since a first_rv may have been excluded.

Key terms and variable names:

Same as Adult Step 9H, with one weight-specific difference:

Configurable setting defaults and options:

None.

Logic and implementation:

Identical to Adult Step 9H with these weight-specific differences:

  1. After identical removal, temp_sde() is re-run with ptype = "weight" (non-RV median) and redo_identify_rv() updates RV flags
  2. Non-SDE median (nonsdemed) is calculated from non-RV values on non-SDE days only
  3. After all SDE exclusions, RVs are re-identified via identify_rv()

See Adult Step 9H for full details on categories, sorting, and tiebreakers.

Rationale for selected decisions:


Mean Height (Adult Step 11H)

Calculate the mean height for each subject, respecting loss/gain group boundaries from Adult Step 10H.

Scope Height only
Prior Adult Step 10H: Height Distinct Values
Next Adult Step 11Wa: 2D Ordered Weight Pairs
Distinct/RVs N/A — height does not use RV tracking
Exclusion Codes None — this step calculates a value, not exclusions
Configurable parameter names None

Overview:

This step computes a representative mean height for each subject. The mean_ht column can be used by researchers who want a consistent height for evaluation of body mass index. The mean is calculated from meas_m (metric measurement values) of remaining included heights. How the mean is computed depends on the outcome of Adult Step 10H:

Key terms and variable names:

Logic and implementation:

  1. Initialize meanht as NA for all height observations
  2. If no included heights remain, leave as NA
  3. Otherwise, compute mean height based on Adult Step 10H outcome:
    • pairhtloss or pairhtgain: Use each observation’s meas_m directly (no averaging)
    • Loss groups validated: For each group in glist_loss, compute mean(meas_m) for observations in that group; all observations in the group receive the same mean
    • Gain groups validated: Same logic using glist_gain
    • Default: Compute mean(meas_m) across all included heights; all observations receive the same value
  4. Store in the mean_ht column (returned by cleanadult(); returned in cleangrowth() output by default)
  5. Finalize after Adult Steps 13/14. Steps 13 and 14 can exclude a subject’s remaining heights (always all of them). After Step 14, mean_ht is set so it reflects only finally-included heights and is populated on every height row: if the subject has no included heights left, every height row is NA; an included height keeps its Step 11H value; every other height row (excluded at any step, or with a missing measurement) takes the mean_ht of the included height nearest in age (ties go to the earlier one).

Rationale for selected decisions:


2D Ordered Weight Pairs (Adult Step 11Wa)

Exclude subjects with exactly 2 distinct weight values in time order when the weight difference exceeds the allowed amount or the weight ratio is too extreme.

Scope Weight only
Prior Adult Step 10W: Final SDE Weight Resolution
Next Adult Step 11Wa2: 2D Non-Ordered Weight Pairs
Distinct/RVs Routing uses firstRV (non-RV) numdistinct and ordering; allRV override for non-ordered detection; evaluation uses firstRV values
Exclusion Codes Exclude-A-2D-Ordered
Configurable parameter names wtallow_formula

Overview:

This step handles subjects whose remaining included weights have exactly 2 distinct values that are time-ordered (all instances of value 1 occur before all instances of value 2). It applies two independent tests: (1) whether the absolute weight difference exceeds a time-dependent allowance (wtallow), and (2) whether the ratio of the smaller weight to the larger weight falls below a threshold (perclimit). If either test fails, all weight observations for the subject are excluded.

This step also performs the routing that determines whether subjects go to 2D ordered (this step), 2D non-ordered (Adult Step 11Wa2), or 3+D/EWMA (Adult Step 11Wb). Subjects with 1 distinct value go to Adult Step 13 (Distinct Single).

Key terms and variable names:

Configurable setting defaults and options:

See the wtallow section below for the complete specification of all formulas, UW adjustments, ET caps, and ceilings.

Logic and implementation:

Routing (numdistinct and ordering):

  1. Filter to firstRV (non-RV) included weight observations (w_nonrv)
  2. Count distinct values among firstRV observations
  3. If firstRV has exactly 2 distinct values, check ordering: max(ages of value 1) < min(ages of value 2) (strict less-than)
  4. allRV override: If allRV (all included values, including RVs) also has exactly 2 distinct values but they are NOT time-ordered, override the firstRV result — route to 2D non-ordered (Adult Step 11Wa2) instead
  5. Routing outcome:
    • 1 distinct value → Adult Step 13 (Distinct Single)
    • 2 distinct, ordered → this step (11Wa)
    • 2 distinct, not ordered → Adult Step 11Wa2
    • 3+ distinct → Adult Step 11Wb (Moderate EWMA)

Evaluation (if 2D ordered):

  1. Compute wt_first (earliest weight) and wt_last (latest weight) from firstRV observations
  2. Compute ageyears_diff = min age of value 2 - max age of value 1 (gap between the two groups)
  3. Compute wtallow using compute_wtallow(ageyears_diff * 12, formula, uw = max(wt_first, wt_last))
  4. Test 1 (wtallow): abs(wt_diff) > wtallow + 0.12 (with rounding tolerance)
  5. Compute wt_perc = min(wt_first, wt_last) / max(wt_first, wt_last)
  6. Determine perclimit: subject-level, max of compute_perc_limit() across all observations. At loosest/looser: ≤45 kg → 0.5, 45–80 kg → 0.4, >80 kg → 0 (disabled). At tighter/tightest: ≤45 kg → 0.7, >45 kg → 0.4.
  7. Test 2 (perclimit): wt_perc < perclimit (strict less-than)
  8. If either test fails, exclude ALL weight observations for the subject (including RVs)

Rationale for selected decisions:


2D Non-Ordered Weight Pairs (Adult Step 11Wa2)

Exclude subjects with exactly 2 distinct weight values that are interleaved in time, using a four-rule decision tree based on wtallow, prior exclusions, and value dominance.

Scope Weight only
Prior Adult Step 11Wa: 2D Ordered Weight Pairs
Next Adult Step 11Wb: Moderate EWMA
Distinct/RVs Uses allRV (all included values) for both detection and evaluation
Exclusion Codes Exclude-A-2D-Non-Ordered
Configurable parameter names wtallow_formula

Overview:

This step handles subjects whose remaining included weights have exactly 2 distinct values that are NOT time-ordered — meaning the values are interleaved (e.g., 70, 110, 70, 110). Unlike 2D ordered pairs (Adult Step 11Wa), the values cannot be separated into “before” and “after” groups, so a different evaluation strategy is used.

The step applies a four-rule decision tree: (1) if all adjacent different-value pairs are within wtallow, keep everything; (2) if any pair exceeds wtallow and the subject has prior non-SDE exclusions, exclude all; (3) if one value is dominant (>65% of observations), exclude only the minority; (4) if neither value dominates, exclude all.

Key terms and variable names:

Configurable setting defaults and options:

Same as Adult Step 11Wa — uses compute_wtallow() with wtallow_formula. UW for each adjacent pair is the larger of the two weights in the pair (uw_pair).

Logic and implementation:

  1. Sort observations by age (tiebreaker: internal_id)
  2. For each adjacent pair where the weight values differ:
    • Compute time gap in months: abs(age_days[i+1] - age_days[i]) / 30.4375
    • Compute wtallow for that gap using compute_wtallow()
    • If abs(wt[i+1] - wt[i]) > wtallow + 0.12 → set any_outside = TRUE and stop checking
    • Skip adjacent pairs with identical weight values (diff = 0, never outside wtallow)
  3. Apply decision rules:
    • Rule 1: any_outside = FALSE → all observations stay included (no action)
    • Rule 2: any_outside = TRUE AND subject has prior non-SDE exclusions → exclude ALL observations
    • Rule 3: any_outside = TRUE, no prior non-SDE exclusions, AND one value is dominant (dominant_pct > 0.65, strict) → exclude only the minority value observations
    • Rule 4: any_outside = TRUE, no prior non-SDE exclusions, AND neither value dominates (dominant_pct <= 0.65) → exclude ALL observations

Prior non-SDE exclusion check:

Checks the full subject weight history (w_subj_keep), not just currently included observations. Uses exact code matching (not substring search) to identify SDE codes. Codes that do NOT count as “prior non-SDE”: Include, Exclude-A-Identical, Exclude-A-Extraneous, empty string, NA. Everything else — including Exclude-A-Scale-Max-Identical — counts as a prior non-SDE exclusion.

Rationale for selected decisions:


Moderate EWMA (Adult Step 11Wb)

Exclude weight outliers using a 7-step EWMA flow with trajectory rescue, alternate pathway, percentage criterion, and error load detection.

Scope Weight only
Prior Adult Step 11Wa2: 2D Non-Ordered Weight Pairs
Next Adult Step 13: Distinct Single (1D)
Distinct/RVs Independent: single pass, RVs as full participants. Linked: firstRV pass (non-RV only) with RV propagation, then allRV pass.
Exclusion Codes Exclude-A-Traj-Moderate, Exclude-A-Traj-Moderate-allRV, Exclude-A-Traj-Moderate-Error-Load, Exclude-A-Traj-Moderate-Error-Load-RV
Configurable parameter names wtallow_formula, ewma_window, repval_handling, mod_ewma_f, perclimit_low, perclimit_mid, perclimit_high

Overview:

This step handles subjects with 3+ distinct weight values. It uses EWMA deviation to identify outliers, but with a more nuanced 7-step flow compared to Extreme EWMA (Adult Step 9Wb). The key differences from Extreme EWMA are:

Each round excludes at most one non-error-load value (the highest-scoring candidate). Error load values are all excluded immediately. The step iterates until no candidates remain, fewer than 3 values are left, or the maximum round limit (100) is reached.

Key terms and variable names:

Configurable setting defaults and options:

Setting Default Unit Notes
wtallow_formula "piecewise" — Formula for wtallow (see Adult Step 11Wa and the wtallow formulas section)
mod_ewma_f 0.75 — Directional factor: loosest/looser = 0.75; tighter/tightest = 0.60
perclimit_low 0.5 — % criterion for wt ≤45 kg (loosest/looser); 0.7 (tighter/tightest)
perclimit_mid 0.4 — % criterion for 45<wt≤80 kg (all levels)
perclimit_high 0.0 — % criterion for wt >80 kg; 0 = disabled (loosest/looser); 0.4 (tighter/tightest)
ewma_window 15 observations Max observations on each side for EWMA. All EWMAs use this window.
repval_handling "independent" — Controls single vs two-pass EWMA
max_rounds 100 — Maximum EWMA iteration rounds

Logic and implementation:

Guards:

  1. Only subjects with 3+ distinct weight values enter this step (others went to 11Wa, 11Wa2, or 13)
  2. Must have ≥ 3 included observations
  3. Trigger check: Skip if weight range ≤ min wtallow AND (perclimit disabled OR min percent ratio ≥ max perclimit) — impossible to trigger any exclusion

EWMA computation:

7-step exclusion flow (each round):

Sub-step 1 — Standard pathway + trajectory rescue:

Sub-step 2 — Standard run detection + pair/trio prioritization:

Sub-step 3 — Alternate pathway:

Sub-step 4 — Alternate run detection + pair/trio prioritization:

Sub-step 5 — Percentage criterion:

Sub-step 6 — Consecutive check (all pathways combined):

Sub-step 6b — End-pair median guard:

Sub-step 7 — Final prioritization:

Mode handling:

Independent mode: Single pass with all values (including RVs) as full participants.

Linked mode:

  1. firstRV pass: Run remove_mod_ewma_wt() on non-RV values only. Label: "Exclude-A-Traj-Moderate".
    • Error load RV escalation: If any error-load value has RV copies, escalate the entire patient — all remaining Include values get "Exclude-A-Traj-Moderate-Error-Load-RV".
    • Non-error-load propagation: Propagate exclusions to RV copies via propagate_to_rv() (appends -RV-Propagated suffix).
    • Re-identify RVs on remaining values.
  2. allRV pass: Run remove_mod_ewma_wt() on all remaining values (RVs now participate). Label: "Exclude-A-Traj-Moderate-allRV".

Rationale for selected decisions:


Single Distinct Value: 1D Evaluation (Adult Step 13)

Exclude height or weight values when a subject has only one distinct value for that parameter, using BMI-dependent or no-BMI limits.

Scope Height and Weight
Prior Adult Step 11Wb: Moderate EWMA
Next Adult Step 14: Error Load
Distinct/RVs Weight uses firstRV for numdistinct. Remaining RVs are treated as Include.
Exclusion Codes Exclude-A-Single
Configurable parameter names single_ht_min_bmi, single_ht_max_bmi, single_wt_min_bmi, single_wt_max_bmi, single_ht_min_nobmi, single_ht_max_nobmi, single_wt_min_nobmi, single_wt_max_nobmi, single_bmi_min, single_bmi_max

Overview:

After all prior steps, some subjects may have only one distinct value remaining for height or weight. These cannot be evaluated by EWMA or pair comparisons, so this step applies simple range limits. The limits depend on whether BMI can be computed (i.e., the subject has both a height and a weight on the same day):

Key terms and variable names:

Configurable setting defaults and options:

Setting loosest looser tighter tightest
single_ht_min_bmi (cm) 60 120 142 147
single_ht_max_bmi (cm) 245 230 213 208
single_wt_min_bmi (kg) 12 30 36 39
single_wt_max_bmi (kg) 350 270 159 136
single_ht_min_nobmi (cm) 120 120 142 147
single_ht_max_nobmi (cm) 245 230 213 208
single_wt_min_nobmi (kg) 30 30 36 39
single_wt_max_nobmi (kg) 350 270 159 136
single_bmi_min (kg/m²) 10 12 16 18
single_bmi_max (kg/m²) 250 65 45 40

Note: For looser/tighter/tightest, the BMI-available and no-BMI limits are identical — BMI availability does not change which values are excluded at these levels. The BMI/no-BMI split only matters at loosest (wider limits when BMI is available: 60 cm vs 120 cm for ht_min; 12 kg vs 30 kg for wt_min). single_bmi_min/single_bmi_max match the overall PIV BMI limits at looser, tighter, and tightest; at loosest they are narrower (10–250 vs 5–300).

Logic and implementation:

The step runs as a two-pass loop over each subject (both passes use the same logic, but pass 2 catches cases where a pass 1 exclusion removes a same-day partner, causing BMI to become unavailable):

Pass 1:

  1. Determine which parameters are 1D: exactly 1 distinct included value and at least 1 observation
  2. If neither height nor weight is 1D, skip this subject
  3. Check BMI availability: find days where both an included height and an included weight exist
  4. If BMI available:
    • Compute a BMI for every same-day pairing of an included height with an included weight
    • If every BMI is extreme (bmi < single_bmi_min or bmi > single_bmi_max, strict): exclude ALL 1D height observations AND all 1D weight observations
    • If any BMI is within range: check each 1D parameter against the BMI-available limits (single_ht_min_bmi/single_ht_max_bmi, single_wt_min_bmi/single_wt_max_bmi). Exclude if outside limits, applying the 0.12 cm/kg rounding tolerance: excluded if < limit − 0.12 or > limit + 0.12.
  5. If no BMI available: check each 1D parameter against the no-BMI limits (single_ht_min_nobmi/single_ht_max_nobmi, single_wt_min_nobmi/single_wt_max_nobmi). Exclude if outside limits, applying the 0.12 cm/kg rounding tolerance: excluded if < limit − 0.12 or > limit + 0.12.

Pass 2:

Rationale for selected decisions:


Error Load (Adult Step 14)

Exclude all remaining included values for a parameter when the proportion of errors is too high, indicating widespread data quality issues.

Scope Height and Weight (evaluated separately)
Prior Adult Step 13: Single Distinct (1D)
Next Adult Step None (final step)
Distinct/RVs Weight uses firstRV exclusion codes for error counting
Exclusion Codes Exclude-A-Unevaluable-Trajectory (applied once per param family — HEIGHT and WEIGHT — in the long-format split; one code)
Configurable parameter names error_load_threshold

Overview:

This is the final step. It evaluates whether a subject has too many errors relative to the total number of meaningful observations. If the ratio of errors to (errors + included values) exceeds the threshold, all remaining included values for that parameter are excluded. Height and weight are evaluated independently — a subject can have error load for weight but not height, or vice versa.

Key terms and variable names:

Configurable setting defaults and options:

Setting Default Description
error_load_threshold 0.41 at loosest/looser, 0.29 at tighter/tightest Error ratio above this triggers exclusion (strict >)

Which exclusion codes count as errors:

Height errors:

Weight errors:

NOT counted as errors:

Logic and implementation:

  1. For each subject, process height and weight separately
  2. Remove SDE observations from consideration (not errors, not in denominator)
  3. Count errors (observations matching error codes above)
  4. Count remaining included observations
  5. Compute denominator = errors + included. If denominator < 3, skip (too few observations to evaluate)
  6. Compute ratio = errors / denominator
  7. If ratio > error_load_threshold (0.41 at loosest/looser, 0.29 at tighter/tightest; strict >): exclude all remaining included values for that parameter

Rationale for selected decisions:


wtallow: base formulas and adjustment

General concepts and terms

wtallow wtallow is a derived value used in multiple steps. Conceptually, it represents the threshold above which an absolute difference in weight between two values will result in exclusion of one of the two values. It is one of the most important values determining whether a value should be excluded. Importantly, most steps where wtallow is used include other constraints on exclusion or other paths to exclusion. Therefore, not every difference higher than wtallow will result in exclusion, and some differences below wtallow will result in exclusion. wtallow is dependent on two things - the interval between the two measurements being compared and the upper weight (UW) of the two measurements.

Upper weight (UW) UW is the higher of the two adjacent weights that are being evaluated when calculating wtallow. An UW of 120 is considered the base value. UW >120 are considered high and UW <120 are considered low.

wtallow-et-cap The wtallow-et-cap (ET cap) is a value related to wtallow used in Evil Twins (Adult Step 9Wa) and Extreme EWMA (Adult Step 9Wb). In Evil Twins, an adjacent-pair difference above the ET cap leads to an exclusion only when the pair is flanked by anchoring values (the anchor rule); in Extreme EWMA it is the threshold for the EWMA deviation. The wtallow-et-cap-6m is the et-cap for intervals <=6m. The wtallow-et-cap-12m is the et-cap for intervals >6m.

wtallow-cap These are values at 6m and 12m that represent a maximum value for wtallow even if the formula for wtallow would otherwise give a higher value. For intervals <=6m, no value can be above the wtallow-cap-6m. For intervals >6m, no value can be above the wtallow-cap-12m (this includes intervals >12m). The wtallow-cap values are also used in the wtallow formulas.

Piecewise-Higher (PW-H) The formula used to determine wtallow in looser and loosest permissiveness levels. It is adjusted for high UW and low UW. Piecewise-Lower (PW-L) The formula used to determine wtallow in tighter permissiveness levels. It is adjusted for low UW (not high UW). allofus15 The formula used to determine wtallow in tightest permissiveness levels, adapted from the Guide et al. (2024) All of Us algorithm. It is not adjusted based on UW.

Suffixes For PW-H and PW-L, we will use suffixes -Base, -highUW, and -lowUW to designate formulas for the three UW conditions (=120, >120, and <120).

wtallow-1-day The value of wtallow at one day. It is equal to 10 + 10 × log(1 + 5×1/30.4375) / log(6), approximately 10.85

ceiling A maximum wtallow for PW-H-lowUW and PW-L-lowUW that limits wtallow beyond the limits provided by the wtallow-cap. A ceiling is only used for PW formulas adjusted for lowUW.

Brief rationale for features of base formulas

Based on literature, input from experts, and clinical experience, the following concepts informed wtallow.

  • Weight change can be large even in a single day or very few days from fluid shifts. After 1-2 weeks, the slope of potential weight change generally slows.
  • Many of the largest weight changes occur after bariatric surgery or GLP1 agonist use. Literature shows that maximum weight loss after bariatric surgery or GLP1 agonist treatment occurs at approximately 12m, so wtallow does not rise after 12m. Of this weight change, a higher proportion occurs in the first 6m, so the slope is higher from 1m to 6m than 6m to 12m. Time courses for large weight loss with cancer can vary, but would generally not be larger or faster than those from weight loss surgery.
  • In general, PW-H was designed to include (i.e., not exclude) almost all physiologically plausible changes. PW-L was designed to include physiologically plausible changes for many, but not all, conditions. allofus15 was adapted from the Guide et al. (2024) All of Us algorithm to include changes that would be seen in people without severe or uncommon health problems.

Rationale for adjustment

People with higher weight can have larger absolute changes in body weight. Similarly, people with lower weight will typically have smaller absolute changes in body weight. The relationship is complex, varies over time, and depends on whether you are considering weight gain or weight loss. Adjusting allowed wt change by the upper weight (UW) of a pair can account for some of these differences, although it does not account for all situations. Most important, adjusting the wtallow (max wt changed allowed) can increase the risk of inappropriately including erroneous high weights. For this reason, we cap the adjustment for upper weights at the values that would be used for an upper weight of 180.

Formulas

PW-H (Piecewise-Higher)

PW-H-Base (UW = 120, no adjustment):

  • wtallow-cap-H-6m-base = 50

  • wtallow-cap-H-12m-base = 80

  • wtallow-et-cap-H-6m-base = 70

  • wtallow-et-cap-H-12m-base = 100

  • ceiling = N/A

  • months ≤ 1: 10 + 10 × log(1 + 5×months) / log(6)

  • 1 < months ≤ 6: linear increase from 20 @1m to wtallow-cap-H-6m-base @6m; works out to 20 + 6 × (months − 1)

  • 6 < months ≤ 12: linear increase from wtallow-cap-H-6m-base @6m to wtallow-cap-H-12m-base @12m; works out to 50 + 5 × (months − 6)

  • months > 12: flat at wtallow-cap-H-12m-base (80)

PW-H-highUW (Scaling up, UW>120): if UW is >180, use 180 as UW in formula

  • wtallow-cap-H-6m-highUW = 50 + 0.25 x (UW-120)

  • wtallow-cap-H-12m-highUW = 80 + 0.25 x (UW-120)

  • wtallow-et-cap-H-6m-highUW = 70 + 0.25 x (UW-120)

  • wtallow-et-cap-H-12m-highUW = 100 + 0.25 x (UW-120)

  • ceiling = N/A

  • months ≤ 1: linear increase from wtallow-1day (≈10.85) @ month 0 to 25 at 1m

  • 1 < months ≤ 6: linear increase from 25 @1m to wtallow-cap-H-6m-highUW @6m

  • 6 < months ≤ 12: linear increase from wtallow-cap-H-6m-highUW @6m to wtallow-cap-H-12m-highUW @12m

  • months > 12: flat at wtallow-cap-H-12m-highUW

PW-H-lowUW (Scaling down, UW < 120):

  • wtallow-cap-H-6m-lowUW = (50 − 20) × (UW / 120) + 20

  • wtallow-cap-H-12m-lowUW = (80 − 20) × (UW / 120) + 20

  • wtallow-et-cap-H-6m-lowUW = (50 − 20) × (UW / 120) + 40

  • wtallow-et-cap-H-12m-lowUW = (80 − 20) × (UW / 120) + 40

  • ceiling: UW × 2/3

  • months ≤ 1: 10 + 10 × log(1 + 5×months) / log(6)

  • 1 < months ≤ 6: linear increase from 20 @1m to wtallow-cap-H-6m-lowUW @6m

  • 6 < months ≤ 12: linear increase from wtallow-cap-H-6m-lowUW @6m to wtallow-cap-H-12m-lowUW @12m

  • months > 12: flat at wtallow-cap-H-12m-lowUW

  • PW-L (Piecewise-Lower)

PW-L-Base (UW = 120, no adjustment):

  • wtallow-cap-L-6m-base = 33.33

  • wtallow-cap-L-12m-base = 53.33

  • wtallow-et-cap-L-6m-base = 53.33

  • wtallow-et-cap-L-12m-base = 73.33

  • months ≤ 1: 10 + 10 × log(1 + 5×months) / log(6)

  • 1 < months ≤ 6: linear increase from 20 @1m to wtallow-cap-L-6m-base @6m

  • 6 < months ≤ 12: linear increase from wtallow-cap-L-6m-base @6m to wtallow-cap-L-12m-base @12m

  • months > 12: flat at wtallow-cap-L-12m-base

PW-L-highUW (UW>120): SAME AS BASE FORMULA

  • wtallow-cap-L-6m-highUW = 33.33

  • wtallow-cap-L-12m-highUW = 53.33

  • wtallow-et-cap-L-6m-highUW = 53.33

  • wtallow-et-cap-L-12m-highUW = 73.33

  • months ≤ 1: 10 + 10 × log(1 + 5×months) / log(6)

  • 1 < months ≤ 6: linear increase from 20 @1m to wtallow-cap-L-6m-base @6m

  • 6 < months ≤ 12: linear increase from wtallow-cap-L-6m-base @6m to wtallow-cap-L-12m-base @12m

  • months > 12: flat at wtallow-cap-L-12m-base

PW-L-lowUW (Scaling down, UW < 120):

  • wtallow-cap-L-6m-lowUW = (33.33 − 20) × (UW / 120) + 20

  • wtallow-cap-L-12m-lowUW = (53.33 − 20) × (UW / 120) + 20

  • wtallow-et-cap-L-6m-lowUW = (33.33 − 20) × (UW / 120) + 40

  • wtallow-et-cap-L-12m-lowUW = (53.33 − 20) × (UW / 120) + 40

  • ceiling: UW × 2/3

  • months ≤ 1: 10 + 10 × log(1 + 5×months) / log(6)

  • 1 < months ≤ 6: linear increase from 20 @1m to wtallow-cap-L-6m-lowUW @6m

  • 6 < months ≤ 12: linear increase from wtallow-cap-L-6m-lowUW @6m to wtallow-cap-L-12m-lowUW @12m

  • months > 12: flat at wtallow-cap-L-12m-lowUW


allofus15

allofus15 is not adjusted based on UW except that its 12m cap is limited to never exceed the PW-L 12m cap for that UW.

allofus15-cap-12m = min(40, effective PW-L at 12m for that UW)

  • For UW ≥ 120: effective PW-L-12m = 53.33, so allofus15-cap-12m = 40
  • For UW < 120: effective PW-L-12m = min(wtallow-cap-L-12m-lowUW, UW × 2/3) where wtallow-cap-L-12m-lowUW = (53.33 − 20) × (UW / 120) + 20; allofus15-cap-12m = min(40, that effective value)

allofus15 wtallow formula:

  • 1–2 days: 5
  • 3–7 days: 10
  • 8d to 6m: 15
  • 6m to 12m: linear increase from 15 @6m to allofus15-cap-12m @12m
  • >12m: flat at allofus15-cap-12m

allofus15-et-cap: allofus15-cap-12m


Permissiveness framework specification

The permissiveness levels and every preset value — overall (PIV) HT/WT/BMI limits, the 1D single-value limits, wtallow_formula, perclimit_*, mod_ewma_f, error_load_threshold, ht_band, allow_ht_loss/allow_ht_gain, repval_handling — are listed in full, with each setting’s role, in vignette("configuration") §1D; the values are also shown under Permissiveness Framework at the top of this narrative. They are not repeated in this section. (The overall/PIV and 1D limits apply the same whether or not same-day BMI is available; the BMI limits apply only when a same-day height and weight are both present.) The subsections below cover the wtallow / ET-cap mechanics and the design rationale specific to this narrative; every preset value they reference lives in §1D.


Worked examples: wtallow and ET caps by UW

The wtallow and ET-cap formulas — the PW-H / PW-L / allofus15 curves, the base values, the UW > 120 scale-up (PW-H only, capped at UW = 180), the UW < 120 scale-down with the UW × 2/3 ceiling, and ET cap = wtallow cap + 20 — are specified above under Formulas and General concepts and terms. The preset that selects the formula per permissiveness level is in vignette("configuration") §1D. The tables below give computed values at selected UW for quick reference.

wtallow tables

PW-H (loosest/looser) at selected UW values:

Months   @60     @120(base)  @160    @180(cap)
  1      20.0    20.0        25.0    25.0
  6      35.0    50.0        60.0    65.0
 12      40.0    80.0        90.0    95.0
 24      40.0    80.0        90.0    95.0

PW-L (tighter) at selected UW values:

Months   @60     @120(base)  @160
  1      20.0    20.0        20.0
  6      26.67   33.33       33.33
 12      36.67   53.33       53.33
 24      36.67   53.33       53.33

Note: PW-L is not scaled up for UW > 120.

ET cap tables

PW-H (loosest/looser) at selected UW values:

UW       ET ≤6m   ET >6m
 60 kg   55.0     70.0
120 kg   70.0     100.0
160 kg   80.0     110.0
180 kg   85.0     115.0

PW-L (tighter) at selected UW values:

UW       ET ≤6m   ET >6m
 60 kg   46.67    56.67
120 kg   53.33    73.33
160 kg   53.33    73.33

Note: PW-L is not scaled up for UW > 120.

Gap between wtallow and ET cap

The ET cap exceeds the wtallow cap by exactly 20 at the cap points (6m and 12m) for PW-H and PW-L formulas (for UW < 120, the UW × 2/3 ceiling can lower wtallow further, widening the gap). This 20 kg gap ensures that moderate EWMA outliers (wtallow < dewma < ET cap) are caught by Step 11Wb, while extreme outliers (dewma > ET cap) are caught earlier by Step 9.

The full formulas are in the Formulas section above.


Perclimit (Percentage Criterion)

The perclimit criterion is an independent exclusion pathway in the moderate EWMA step (Step 11Wb), where it excludes values where wt/ewma < perclimit for all three EWMA calculations (main, bef, aft), regardless of whether the standard EWMA or trajectory criteria are met. In the 2D ordered step (Step 11Wa) it is applied to the ratio of the smaller to the larger of the two weights.

This criterion targets very low weight values that may not trigger the absolute wtallow threshold but are implausibly small relative to the patient’s other measurements.

The three thresholds correspond to the parameters perclimit_low (wt ≤45 kg), perclimit_mid (45<wt≤80 kg), and perclimit_high (wt >80 kg).

The per-level perclimit_low / perclimit_mid / perclimit_high values are in vignette("configuration") §1D (loosest/looser disable the >80 kg tier with 0; tighter/tightest use 0.7 at ≤45 kg and 0.4 elsewhere).

Application

In the moderate EWMA step (Step 11Wb), perclimit is applied at the observation level: each weight observation’s perclimit is determined by that observation’s weight value.

In the 2D ordered step (Step 11Wa), perclimit is applied at the subject level: the maximum perclimit across all weight observations for the subject is used.

Rationale

Loosest/looser at ≤45 kg (0.5): A perclimit of 0.5 means a weight must be at least 50% of its EWMA to survive. This accommodates very underweight patients (e.g., severe cachexia, anorexia) whose weights may legitimately be quite low relative to their historical measurements. For example, a cachectic patient at 35 kg with EWMA of 52 (percewma = 0.67) would be excluded under 0.7 but retained under 0.5.

Tighter/tightest at ≤45 kg (0.7): At these levels, the goal is to exclude values likely reflecting severe illness, so the more restrictive threshold is appropriate.

All levels at 45–80 kg (0.4): At these weights, a value less than 40% of its EWMA is almost certainly an error regardless of permissiveness level.

Loosest/looser: perclimit disabled (0) for wt > 80 kg: In the code, “disabled” is implemented as perclimit = 0, meaning the check percewma < perclimit is never TRUE (no ratio can be < 0). This is distinct from Inf, which would incorrectly trigger on every observation. For weights above 80 kg, UW-adjusted wtallow always catches any value that would also be caught by perclimit = 0.4. The mathematical reason: perclimit = 0.4 fires when dewma > 1.5 × wt. For wt > 80, this requires dewma > 120. The UW-adjusted wtallow catches these values first because wtallow scales with UW (the upper weight of the pair) while the perclimit threshold grows only with the observation’s own weight. Above 80 kg the coverage gap vanishes entirely.

Tighter/tightest: perclimit 0.4 for all wt > 45 kg: With PW-L or allofus15 wtallow (not scaled up for high UW), the base caps are lower (53.33 for PW-L, 40 for allofus15). wtallow still catches values that perclimit = 0.4 would catch for wt > 45. However, the 0.4 threshold is retained at tighter and tightest as a belt-and-suspenders safeguard with negligible cost.


Error Load Threshold

The error load threshold (Step 14) controls when a subject’s remaining included values are excluded because the ratio of errors to total non-SDE values exceeds the threshold. A lower threshold means the algorithm is more aggressive about escalating when multiple errors are detected. The tolerance of the error load threshold addresses a different aspect of permissiveness than most other permissiveness thresholds, but follows the same pattern of more values being excluded in tighter/tightest.

Level error_load_threshold if 10 obs, # errors needed to trigger
loosest >0.41 5/10
looser >0.41 5/10
tighter >0.29 3/10
tightest >0.29 3/10

Moderate EWMA Directional Factor

The f factor in the Moderate EWMA step (Step 11Wb) controls how strict the directional EWMA check is. The standard pathway requires dewma_before > f * wtallow and dewma_after > f * wtallow (in addition to dewma_all > wtallow). A lower f value makes the directional check less strict, leading to more exclusions. Per-level mod_ewma_f values are in vignette("configuration") §1D (0.75 at loosest/looser, 0.60 at tighter/tightest).


Height Band, Height Loss, and Height Gain

Per-level ht_band, allow_ht_loss, and allow_ht_gain values are in vignette("configuration") §1D (ht_band 3″ at loosest/looser, 2″ at tighter/tightest; loss allowed only at loosest; gain allowed at all but tightest).

Note: htloss is not allowed in looser because allowing height loss introduces a large number of additional inclusions that are more likely to represent measurement error than true height loss. Height gain is allowed in looser and tighter because young adults may still be growing, and measurement error in height is common. Height gain rescue applies only when the earlier measurement is at age <25 years.


Repeated Value (RV) Handling

RVs are values identical to a prior value for the same subject and parameter (on a different day). Two modes are available:

independent (default for loosest and looser)

RVs are treated as fully informative values:

  • RVs contribute to EWMA calculations normally
  • RVs are evaluated against that EWMA like any other value
  • RVs are no more likely to be excluded than non-RV values

linked (default for tighter and tightest)

RVs are treated as less informative values:

  • RVs are left out of the EWMA in the firstRV pass of each EWMA step (extreme and moderate)
  • RVs do not have their own EWMA in firstRV; RV propagation serves as their exclusion criterion instead
  • In the allRV pass, the remaining RVs participate in the EWMA like any other value
  • RVs are also more likely to cause exclusion of surrounding non-RV values, because removing RVs from the EWMA can expose patterns that would otherwise be masked

Downstream effects of linked mode

Because RVs are ghosted from the EWMA, their exclusion (or survival) can affect how surrounding values are evaluated. For example:

Patient weights (close together): 60 100 95 60 60 60 70

  • Under independent: the 60s contribute to the EWMA, pulling it toward 60; the 70 looks fine relative to that EWMA and survives.
  • Under linked: the 60s are excluded from the EWMA; the 70 is evaluated against an EWMA that does not include them and may be flagged depending on the surrounding values.

This means linked mode can result in more exclusions of non-RV values in addition to the RVs themselves. Users choosing tighter or tightest should be aware that this may affect subjects with runs of repeated values followed by a return toward a prior level.

RV handling in other steps

The repval_handling setting changes processing only in the two EWMA steps (Extreme EWMA, Adult Step 9Wb; Moderate EWMA, Adult Step 11Wb). In Scale Max (Adult Step 4W) it changes only the code given to RV copies (Exclude-A-Scale-Max-RV-Propagated under linked). Evil Twins (9Wa) and the 2D ordered/non-ordered steps (11Wa/11Wa2) treat RVs the same under both modes.


User overrides

To change one or a few settings without switching preset, pass them in the adult_overrides named list to cleangrowth() — e.g. cleangrowth(df, adult_permissiveness = "tighter", adult_overrides = list(allow_ht_loss = TRUE)) applies the tighter preset but re-allows height loss. Each named value replaces that preset value for adult rows; an unrecognized name is an error. (cleanadult() is an internal function and is not called directly.) The override mechanism and the full preset table are documented in vignette("configuration") §1C/§1D. Separately, wtallow_formula accepts a custom .csv file path if a fully custom weight-allowance table is needed.