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.
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.
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:
dewma_all vs 50
kg; dewma_before/dewma_after vs 45 kg (0.9
factor applied to 50 kg base); adjacency diffs vs 50 kgWhere tolerance is NOT applied:
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.
| 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.
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:
cleangrowth(df) uses all looser defaultscleangrowth(df, adult_permissiveness = "tighter") uses
all tighter defaultscleangrowth(df, adult_permissiveness = "tighter", adult_overrides = list(allow_ht_loss = TRUE))
uses tighter defaults but overrides height loss to be allowedThe 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).
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:
exclude column.
Consolidated across steps and across child/adult.exclude_detailed
column, added when exclude_detail = TRUE (or
full_detail = TRUE). Keeps the -A- prefix,
renames PIV→Hard-Limit, and folds the
RV/firstRV/allRV/-All/-RV-Propagated passes
into their base code (the -Error-Load escalation is
retained as its own Detailed code). Shared same-day codes
(Exclude-Identical, Exclude-Extraneous) carry
no prefix at any level.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).
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 |
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. |
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_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_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.
permissiveness_presets() — no arguments. Returns a
named list of four lists; each inner list has identical parameter names
(PIV/1D limits, wtallow_formula, perclimit_*,
error_load_threshold, mod_ewma_f,
ht_band, allow_ht_loss,
allow_ht_gain, repval_handling).resolve_permissiveness(permissiveness, ...) —
permissiveness must be one of the four valid level strings;
stops with an informative message otherwise. ... are named
parameter overrides. Returns a single flat named list of resolved
parameters identical in structure to one inner list from
permissiveness_presets().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.
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.
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.
as.matrix.delta_dn(agedays) — numeric vector; returns
an n × n numeric matrix.ewma_dn(agedays, meas, ewma.exp = -5, ewma.adjacent = TRUE, ewma_window = 15)
— agedays and meas are aligned numeric
vectors. Returns a data.frame (unlike the child
ewma() which returns a named list) with columns
ewma.all, ewma.before,
ewma.after. When ewma.adjacent = FALSE,
returns only ewma.all. Emits an unsorted-input warning but
does not stop.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_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.
adult_ewma_cache_init(agedays, meas, ewma_exp = -5, ewma_window = 15)
— sorted, aligned numeric vectors. Returns a named list:
delta (n × n weight matrix), ws (weighted
sums), rs (row sums), meas,
agedays, n, ewma_all,
ewma_before, ewma_after. Returns NULL for n =
0.adult_ewma_cache_update(cache, pos_j) —
cache from adult_ewma_cache_init();
pos_j is the integer row index to remove. Returns a new
named list with n – 1 observations (does not mutate the input, unlike
the child version which mutates an environment in place). Returns NULL
when the input cache has n ≤ 1 (removal would leave n =
0).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.
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.
remove_piv(subj_df, type, piv_df) —
subj_df has a meas_m column; type
is a row-name key into piv_df (a matrix-like object with
“low” and “high” columns); returns a logical vector the same length as
nrow(subj_df).remove_piv_low(subj_df, type, piv_df) — returns
logical: TRUE if
meas_m < piv_df[type, "low"] – 0.12.remove_piv_high(subj_df, type, piv_df) — returns
logical: TRUE if
meas_m > piv_df[type, "high"] + 0.12.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.
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.
identify_rv(w_subj_df) — single-subject weight data
frame, assumed pre-sorted by age_days /
internal_id. Returns the same data frame with
is_rv and is_first_rv columns added or
overwritten.redo_identify_rv(w_subj_df) — same structure. Only
re-derives RV flags when at least one is_first_rv is
currently extraneous; returns unchanged data frame otherwise.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.
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.
temp_sde(subj_df, ptype = "height") — single-subject
data frame with age_days, meas_m,
internal_id, and (for weight) is_rv. Returns
the same data frame with extraneous column added. If
nrow == 0, returns as-is. If nrow == 1 or no
duplicate days, returns with extraneous = FALSE for all
rows.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.
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.
compute_wtallow(months, formula = "piecewise", uw = NULL)
— months is a numeric vector of interval lengths in months;
formula is "piecewise" (PW-H),
"piecewise-lower" (PW-L), "allofus15", or a
file path to a custom CSV with columns months and
wtallow; uw is an optional upper-weight vector
(NULL = base, UW = 120). Returns a numeric vector of weight
allowances.compute_et_limit(interval_months, formula = "piecewise", uw = NULL)
— same signature; returns ET caps. Two-tier output: ≤ 6 months → et_6m
cap, > 6 months → et_12m cap (except allofus15, which is flat across
all intervals).compute_perc_limit(meas, perclimit_low, perclimit_mid, perclimit_high)
— vectorized over meas (weight in kg). Returns
perclimit_low for meas ≤ 45, perclimit_mid for
45 < meas ≤ 80, perclimit_high for meas > 80.Key mechanics not in the wtallow formulas section.
uw_eff at 180 kg via
pmin(uw, 180) before applying scale factors..gc_cache under
.wtallow_custom_cache; a path attribute on the
cache detects when a different file is passed. This side effect persists
across cleangrowth() calls within the same R session.compute_et_limit() for allofus15: returns a flat value
(the allofus15 12-month cap for that UW) regardless of interval — unlike
PW-H/PW-L which have ≤ 6m / > 6m tiers.Code location. R/adult_support.R
(compute_et_limit, compute_perc_limit,
compute_wtallow).
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.
ht_allow(velocity, ageyears1, ageyears2) — three
scalars; returns a scalar. Formula:
velocity × (log(ageyears2 – 16.9) – log(ageyears1 – 16.9)).
Undefined for ageyears ≤ 16.9 — safe in practice because
the adult algorithm processes only ages at or above
adult_cutpoint (18–20 years).ht_change_groups(h_subj_df, cutoff, type = "loss") —
h_subj_df pre-sorted by age; cutoff is the
maximum number of groups to form; type = "loss" exits early
if any measurement drops > 5.08 + 0.12 cm below the current group
minimum, type = "gain" exits if any measurement rises >
5.08 + 0.12 cm above the current group maximum. Returns a list with
entries "meas" (list of named numeric vectors, names =
internal_id) and "age" (list of age vectors). Returns empty
lists if the early-exit condition fires before completing any
groups.ht_3d_growth_compare(mean_ht, min_age, glist, compare = "before")
— mean_ht and min_age are numeric vectors
indexed by group; compare = "before" checks each group
against the immediately preceding group, compare = "first"
checks against the first group. Velocity tiers: < 1-year gap → 20
cm/yr, 1–3 year gap → 15 cm/yr, > 3-year gap → 12 cm/yr. Comparison
age is capped at 25 years. Returns a single boolean: TRUE if any
adjacent pair fails the velocity check.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.
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.
detect_runs(flagged) — logical vector; returns a named
list with three integer vectors of the same length: run_id
(NA for non-run elements), run_len (NA for non-run
elements), run_pos (NA for non-run elements). All NAs for
empty input or all-FALSE input.compute_trajectory_fails(meas, age_days, err = 5) —
aligned numeric vectors; returns a logical vector. For n < 3, returns
all TRUE (no rescue possible). NA interpolation/extrapolation results
are explicitly coerced to FALSE before the final fails computation, so
NA observations are treated as “rescued” (kept).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()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.
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).
propagate_to_rv(exc_codes, w_subj_df, w_subj_keep) —
exc_codes is a named character vector (internal_id →
exclusion code) from the firstRV pass; w_subj_df is the
full weight data frame for the subject (must have meas_m
and internal_id); w_subj_keep is the named
exclusion-code vector for all weight rows. Returns a list:
w_subj_keep (updated) and propagated_ids
(character vector of IDs that received propagated codes). Returns the
unchanged inputs unchanged if exc_codes is empty.Code location. R/adult_support.R
(remove_ewma_wt, propagate_to_rv).
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.
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:
eval_2d_nonord() — identifies and applies exclusion
rules to subjects with exactly two interleaved (non-time-ordered)
distinct weight values: all-within-wtallow → keep all; outside-wtallow +
prior non-SDE exclusions → exclude all; outside-wtallow + dominant value
(> 65%) → exclude minority; outside-wtallow + neither dominant →
exclude all. Returns character vector of internal_id values
to exclude.eval_1d() — two-pass evaluation of subjects with a
single distinct height or weight value. Pass 1: evaluate with BMI when
available (BMI-based limits; a BMI is formed for every same-day pairing
of an included height with an included weight, and the subject counts as
in range if any of them is), or without (no-BMI limits). Pass 2:
re-evaluate if Pass 1 excluded a height or weight that removed BMI
availability. Returns character vector of internal_id
values.eval_error_load() — per-subject, per-param (HT and WT
processed separately) error ratio check. Enumerates param-specific error
codes (HT errors: PIV, Single, Ord-Pair, Window; WT errors: PIV, Single,
2D-Ordered, 2D-Non-Ordered, Scale-Max variants, Evil-Twins; EWMA
trajectory codes count as errors, except -RV-Propagated
variants). Excludes SDEs from denominator. Requires
denom ≥ 3. Excludes all remaining Include rows when ratio
exceeds error_threshold. Returns named character vector
(internal_id → exclusion code).Code locations. eval_2d_nonord(),
eval_1d(), and eval_error_load() (in file
order; eval_error_load() is at the end of the file).
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:
piv_df — data frame holding the PIV thresholds, indexed
by “height” and “weight” with columns “low” and “high”remove_piv() — support function that returns a logical
vector identifying values outside PIV limitsConfigurable 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:
overall_ht_min,
overall_ht_max] (with 0.12 tolerance)overall_wt_min,
overall_wt_max] (with 0.12 tolerance)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.internal_id order) and compute BMI = weight_kg / (height_cm
/ 100)²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 valueRationale for selected decisions:
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:
is_rv — logical flag: TRUE for non-first occurrences of
a repeated valueis_first_rv — logical flag: TRUE for the first
occurrence of a repeated valueis_rv = FALSE and
is_first_rv = FALSEidentify_rv() — support function that sets RV flags
based on current included valuesredo_identify_rv() — support function that
re-identifies RVs specifically after temp SDE marks a first_rv as
extraneousConfigurable 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:
meas_m), count how
many times it appears for the subjectis_first_rv = FALSE,
is_rv = FALSEis_first_rv = TRUEis_rv = TRUEidentify_rv() is called fresh on the remaining
rowstemp_sde() is re-runredo_identify_rv() cleans up if SDE marked a first_rv
as extraneousRationale for selected decisions:
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:
extraneous = TRUE means this value is tentatively set
asidetemp_sde() — support function that computes the median,
diffs, and sets extraneous flagsredo_identify_rv() — called after 3W to update RV
identification if a first_rv was flagged extraneousConfigurable setting defaults and options:
None. This step has no configurable settings.
Logic and implementation:
age_days)diff = abs(measurement - median)extraneous = TRUEredo_identify_rv() to update RV identification in case a
first_rv was flagged extraneoush_extraneous or w_extraneous)Rationale for selected decisions:
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:
Scale-Max-Identical).Scale-Max). RVs of excluded values are also excluded
(code: Scale-Max-RV-propagated in linked mode).Key terms and variable names:
adult_ewma_cache_init() — support function computing
EWMA with before/after variants using matrix operationscheck_between() — support function checking if values
fall within a rangeConfigurable 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:
scale_max_lbs == Inf or fewer than 2 included
non-extraneous valuesscale_max_lbs to kg; define detection range as
kg rounded to 0.1 kg (round(kg, 1)) ± 0.1 kgScale-Max-Identicalewma_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)identify_rv(),
temp_sde(), redo_identify_rv() on remaining
valuesRationale for selected decisions:
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:
|B−C| > etcap(B,C) + 0.12,
|A−B| ≤ 0.60·etcap(A,B) + 0.12, and
|C−D| ≤ 0.60·etcap(C,D) + 0.12.med_diff..adult_csd_ref()), used only for the UER3 plausible-group
test and the median-band |z| tiebreak. NA-safe: missing sex resolves to
an M/F-averaged fallback row.unit_error_range group of one CSD-z sign vs a
plausible |z|<3 group whose mean kg differ by ~2.2046, the lb↔︎kg
factor).evil_twins() — support function implementing the anchor
rule and selection. Signature:
evil_twins(w_subj_df, wtallow_formula)compute_et_limit() — ET cap for a given interval,
formula, and UW. The adjacent-pair caps serve the inner and both outer
comparisons and are computed once per iteration (not recomputed per
pair).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:
compute_et_limit(1, formula, uw = min(weights)); no pair
could exceed any interval-specific cap).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.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.identify_rv(),
temp_sde(), redo_identify_rv() on remaining
values.Rationale for selected decisions:
0.20·etcap(B,C) (kg),
mirroring the child redesign that halved its band. The child uses an
absolute z-unit band; the two widths are intentionally not
unified (see the comment in evil_twins() and
calc_otl_evil_twins()).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:
h_subj_df; extraneous flags identify
which days have duplicatesExclude-A-Identicaltemp_sde() to update extraneous flags on
remaining valuesPart B — Non-identical SDEs:
daymed: median within each daynonsdemed: median of values on non-SDE days (NA if no
non-SDE days exist)medmed: median of per-day mediansdaystot == 1daystot >= 4 AND sderatio < 0.5daystot == 2 or 3 OR sderatio >= 0.5Exclude-A-ExtraneousRationale for selected decisions:
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:
max(wt, ewma) for
each observation. Used for UW-based ET cap scaling.remove_ewma_wt() — support function implementing the
iterative exclusion logic (shared with Moderate EWMA, Adult Step 11Wb).
Signature:
remove_ewma_wt(subj_df, wtallow_formula, ...)compute_et_limit() — computes the ET cap per
observation: compute_et_limit(min_gap_months, formula, uw).
See the wtallow section below for the full
specification.adult_ewma_cache_init() — EWMA computation with
position-based window (default 15 on each side)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:
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):
remove_ewma_wt() with all non-extraneous values
(including RVs)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
roundLinked mode (two-pass):
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.propagate_to_rv()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.Rationale for selected decisions:
ewma_all, so an extreme edge value can still
be excluded based on the neighbors that do exist.internal_id as a final tiebreaker in all passes for
deterministic sort order.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:
ht_band inches. If so, attempt loss rescue
(height decreased within plausible limits), gain rescue (young adult
height gain within allowance), and frequency rescue (one value appears
≥4/3 as often as the other).ht_band inches that captures the most
values with sufficient ratio to outliers. Values outside the best window
are excluded, unless rescued by loss-group or gain-group analysis.Subjects with only 1 distinct height skip this step entirely.
Key terms and variable names:
ht_band * 2.54 + 0.12 cm.velocity * (ln(age2 - 16.9) - ln(age1 - 16.9))ht_band * 2.54 + 0.12 cm starting from each distinct
heightht_allow() — support function computing height growth
allowanceht_change_groups() — support function forming
sequential height groupsht_3d_growth_compare() — support function validating
gain patterns across groupscheck_between() — support function for inclusive range
checksConfigurable 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
abs(ht_1 - ht_2) > (ht_band * 2.54 + 0.12) — if FALSE,
values are within band; skip (all remain included)ageyears1 < 25 (young adult). Checks:
(ht_2 - ht_1) > -0.12 (in cm, with
rounding tolerance)(ht_2 - ht_1) ≤ (htallow + 2) * 2.54 + 0.12 (htallow in
inches, converted to cm with tolerance; 2-inch buffer)allow_ht_loss = TRUE. Checks:ht_1 > ht_2 - 0.12 (in cm, with
rounding tolerance)(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 ≥
50keepht1 (ht_1
count ≥ ht_2 count × 4/3), rescue ht_1 values. Same for keepht2.Exclude-A-Ord-Pair if frequency rescue saved some
valuesExclude-A-Ord-Pair-All if no frequency rescue (all
excluded)Adult Step 10Hb: 3+ Distinct Heights
Part 1 — Window (w2) evaluation:
[ht, ht + ht_band * 2.54 + 0.12]tot_w2 + 0.5 * numdistinct_w2); ties broken by largest
mean absolute distance from w2 mean to o2 valuesExclude-A-Window-All)Exclude-A-Window)Part 2 — Loss group rescue (if
allow_ht_loss = TRUE):
Part 3 — Gain group rescue:
ht_3d_growth_compare():
Rationale for selected decisions:
allow_ht_loss:
This is a configurable parameter because height loss (e.g., from
kyphosis, osteoporosis) is real but uncommon. Setting
allow_ht_loss = FALSE makes the algorithm more conservative
— any large height difference results in exclusion.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:
temp_sde() is re-run with
ptype = "weight" (non-RV median) and
redo_identify_rv() updates RV flagsnonsdemed) is calculated from non-RV
values on non-SDE days onlyidentify_rv()See Adult Step 9H for full details on categories, sorting, and tiebreakers.
Rationale for selected decisions:
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:
mean_ht — cleanadult() output column: the
calculated mean height for each height observation. Carried through by
cleangrowth() and returned by default when the data has
adult rows (set mean_ht = FALSE to omit). Computed from
finally-included heights only and populated on all of the subject’s
adult height rows (see Step 11H, item 5); NA for child
rows, adult weight rows, and subjects with no included height. See
vignette("output").meas_m — the metric measurement value (used for mean
calculation)loss_groups / gain_groups — group
assignments from Adult Step 10Hpairhtloss / pairhtgain — flags from Adult
Step 10Ha indicating pair rescue typeLogic and implementation:
meanht as NA for all height
observationsmeas_m directly (no averaging)glist_loss, compute mean(meas_m) for
observations in that group; all observations in the group receive the
same meanglist_gainmean(meas_m) across
all included heights; all observations receive the same valuemean_ht column (returned by
cleanadult(); returned in cleangrowth() output
by default)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:
meas_m (metric measurement):
Since SDE resolution retains a keeper’s original value without averaging
(Adult Step 9H), meas_m equals the raw recorded measurement
for all surviving heights. The mean is based on these raw values, not
any intermediate calculation.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:
wt_last - wt_firstmin age of value 2 - max age of value 1 (in
years)compute_wtallow(months, formula, uw). UW is the upper
weight of the pair (max(wt_first, wt_last)).min(wt_first, wt_last) / max(wt_first, wt_last)compute_perc_limit() across observations.
Weight-dependent and permissiveness-dependent (see permissiveness table
above).Configurable setting defaults and options:
wtallow_formula (default
"piecewise"): Selects the formula for computing weight
allowance from the time interval between measurements. All formulas are
UW-adjusted (scaled by the upper weight of the pair). Options:
"piecewise" (PW-H) — Three-segment
curve. Default for loosest/looser. At UW=120 (base):
10 + 10 × log(1 + 5×months) / log(6))12 months: flat at 80 kg
(base_cap − 20) × (UW/120) + 20; ceiling of UW × 2/3"piecewise-lower" (PW-L) — Same shape
as PW-H but with lower caps. Default for tighter. At UW=120 (base):
12 months: flat at 53.33 kg
(base_cap − 20) × (UW/120) + 20; ceiling of UW × 2/3"allofus15" — step function for short
intervals, linear ramp for long intervals. Default for tightest.
12 months: flat at allofus15-cap-12m
months and
wtallow (linearly interpolated, no UW adjustment).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):
w_nonrv)max(ages of value 1) < min(ages of value 2) (strict
less-than)Evaluation (if 2D ordered):
wt_first (earliest weight) and
wt_last (latest weight) from firstRV observationsageyears_diff =
min age of value 2 - max age of value 1 (gap between the
two groups)wtallow using
compute_wtallow(ageyears_diff * 12, formula, uw = max(wt_first, wt_last))abs(wt_diff) > wtallow + 0.12
(with rounding tolerance)wt_perc =
min(wt_first, wt_last) / max(wt_first, wt_last)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.wt_perc < perclimit (strict
less-than)Rationale for selected decisions:
compute_perc_limit() across all observations. At
loosest/looser, perclimit is disabled (0) for weights >80 kg, so the
subject-level value is driven by any lighter observations.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:
Include,
Exclude-A-Identical, Exclude-A-Extraneous,
empty string, or NA)max(count_v1, count_v2) / totalConfigurable 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:
abs(age_days[i+1] - age_days[i]) / 30.4375compute_wtallow()abs(wt[i+1] - wt[i]) > wtallow + 0.12 → set
any_outside = TRUE and stop checkingany_outside = FALSE → all
observations stay included (no action)any_outside = TRUE AND subject
has prior non-SDE exclusions → exclude ALL observationsany_outside = TRUE, no prior
non-SDE exclusions, AND one value is dominant
(dominant_pct > 0.65, strict) → exclude only the
minority value observationsany_outside = TRUE, no prior
non-SDE exclusions, AND neither value dominates
(dominant_pct <= 0.65) → exclude ALL observationsPrior 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:
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:
compute_wtallow(months, formula, uw) using the minimum of
the before and after age gaps. UW = max(wt, ewma) for the
observation.compute_perc_limit(meas, perclimit_low, perclimit_mid, perclimit_high).
Weight-dependent and permissiveness-dependent (see permissiveness
table). Note: observation-level here, unlike Adult Step 11Wa which uses
subject-level max.exc_stand) — observations that pass the standard EWMA
criterion and fail all trajectory rescue methods (Sub-step 1)exc_pair) — observations flagged by the alternate pathway
for being adjacent to unreliable neighbors (Sub-step 3)exc_wt_i) — combined set from standard (Sub-step 2) and
alternate (Sub-step 4) pathways, used for consecutive-run detectionerror_load) —
observations in runs of 4+ consecutive accumulated candidates, excluded
immediately in Sub-step 7Configurable 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:
EWMA computation:
ewma_window
(default 15) observations on each side contribute7-step exclusion flow (each round):
Sub-step 1 — Standard pathway + trajectory rescue:
|dewma_all| > wtallow + 0.12 AND
both directional dewma exceed mod_ewma_f × wtallow + 0.12
in the same direction (mod_ewma_f = 0.75 at loosest/looser,
0.60 at tighter/tightest)exc_stand = standard_criteria AND fails_all_trajectorySub-step 2 — Standard run detection + pair/trio prioritization:
exc_stand within each
subject|dewma_aft / wtallow_aft_unrel| (wtallow
computed as if before-neighbor were removed)|dewma_bef / wtallow_bef_unrel||dewma_bef/wtallow_bef_unrel + dewma_aft/wtallow_aft_unrel|Sub-step 3 — Alternate pathway:
|dewma_all| > wtallow + 0.12 AND
|dewma_aft| > mod_ewma_f × wtallow + 0.12, same
direction)exc_pairSub-step 4 — Alternate run detection + pair/trio prioritization:
Sub-step 5 — Percentage criterion:
wt/ewma_all < perclimit AND wt/ewma_bef < perclimit AND wt/ewma_aft < perclimitcompute_perc_limit(meas, perclimit_low, perclimit_mid, perclimit_high)Sub-step 6 — Consecutive check (all pathways combined):
Sub-step 6b — End-pair median guard:
n real-data case the
guard is designed for); both members are independently
eligible (flagged by the standard, alternate, or percentage
pathway, ignoring error load); the interior-side neighbor is NOT
eligible (position n-2 for penult/last, position 3 for
first/second — a 3+ run defers to the run logic); and the within-pair
age gap is < endpair_gap_days (default 90).max(1, n-4)..n-2 for penult/last; positions
3..min(n, 5) for first/second). At n = 4 the
window holds 2 values, so the “median” is the mean of those two — less
robust than the 3-value form, but adequate for the partner-contamination
case the guard targets. The member closer to that
median is kept (removed from this round’s candidate set); the
farther member is forced into the candidate set as the
round’s nominee. An exact distance tie keeps the edge value (the first
or the last) and nominates the inner member (the second or the
penult).Sub-step 7 — Final prioritization:
"<label>-Error-Load"
(e.g. Exclude-A-Traj-Moderate-Error-Load)pmax(0, |dewma_all| - wta) / wta_base where
wta is UW-adjusted wtallow and wta_base is
wtallow at UW=120 (no adjustment)(excess_bef + excess_aft) where each
excess = pmax(0, |dewma| - wta) / wta_base
min_excess = min(excess_bef, excess_aft)multiplier = min(1.0, 0.6 + 0.4 × min_excess)Mode handling:
Independent mode: Single pass with all values (including RVs) as full participants.
Linked mode:
remove_mod_ewma_wt()
on non-RV values only. Label: "Exclude-A-Traj-Moderate".
"Exclude-A-Traj-Moderate-Error-Load-RV".propagate_to_rv() (appends
-RV-Propagated suffix).remove_mod_ewma_wt()
on all remaining values (RVs now participate). Label:
"Exclude-A-Traj-Moderate-allRV".Rationale for selected decisions:
n >= 4; at n = 4
the window holds only 2 interior-side values, so the “median” is the
mean of those two and is less robust than the 3-value form at
n >= 5. It is restricted to short within-pair gaps
(< endpair_gap_days, default 90), since larger real
shifts are expected over longer intervals, and it defers to the run
logic whenever the interior-side neighbor is also flagged.compute_perc_limit(). At loosest/looser, observations
>80 kg have perclimit=0 (disabled).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:
weight_kg / (height_cm / 100)² from same-day height/weight
pairs[single_bmi_min, single_bmi_max] (out of range =
strict < / >)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:
bmi < single_bmi_min or
bmi > single_bmi_max, strict): exclude ALL 1D height
observations AND all 1D weight observationssingle_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.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:
bmi < single_bmi_min /
> single_bmi_max) is the exception: it uses strict
inequalities with no tolerance.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:
n_errors / (n_errors + n_included) for one parameter within
one subjectn_errors + n_included
(must be ≥ 3 to evaluate)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:
Exclude-A-PIVExclude-A-Single (1D)Exclude-A-Ord-PairExclude-A-Ord-Pair-AllExclude-A-WindowExclude-A-Window-AllWeight errors:
Exclude-A-PIVExclude-A-Single (1D)Exclude-A-2D-OrderedExclude-A-2D-Non-OrderedExclude-A-Scale-MaxExclude-A-Scale-Max-IdenticalExclude-A-Scale-Max-RV-PropagatedExclude-A-Evil-TwinsExclude-A-Traj-* codes (Extreme and Moderate, all
rounds and variants)NOT counted as errors:
Logic and implementation:
error_load_threshold (0.41 at
loosest/looser, 0.29 at tighter/tightest; strict >):
exclude all remaining included values for that parameterRationale for selected decisions:
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.
Based on literature, input from experts, and clinical experience, the following concepts informed wtallow.
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.
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-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 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)
allofus15 wtallow formula:
allofus15-et-cap: allofus15-cap-12m
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.
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.
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.
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.
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.
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).
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.
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.
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 |
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).
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.
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:
linked (default for tighter and
tightest)RVs are treated as less informative values:
linked modeBecause 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
independent: the 60s contribute to the EWMA,
pulling it toward 60; the 70 looks fine relative to that EWMA and
survives.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.
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.
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.