Skip to contents

A SAS candidate pool offers every transformation of a variable as a separate candidate: age, ln_age, in_age, age2 and agee all compete. A stepwise screen then spends its steps on them, and a per-variable selection frequency reports each form separately. On one real screen five forms of age each cleared 80% while the retained set collapsed to four distinct clinical concepts – and the paper the job comes from reports concepts.

Usage

concept_of(
  v,
  pool,
  affixes = POOL_AFFIXES,
  plain_suffix = POOL_PLAIN_SUFFIX,
  aliases = character(0),
  min_stem = POOL_MIN_STEM
)

Arguments

v

Character scalar: the variable name.

pool

Character vector: the candidate pool v belongs to.

affixes

Character vector of regular expressions for the study's transformation affixes. See POOL_AFFIXES.

plain_suffix

Suffix carried by untransformed measurements.

aliases

Named character vector, c(variable = concept), declaring groupings no affix rule can reach. An alias on the variable itself wins over every rule: it is the study saying so directly.

min_stem

Minimum stem length before the prefix rule is tried.

Value

A character scalar: the concept, or v itself when no rule applies.

Details

The rule is deliberately conservative. A variable joins a concept only when stripping one known affix yields a name that is itself in the pool (or that name plus plain_suffix). So zexp2 groups under zexp because zexp is a candidate, and ln_crcl groups under crcl_pr because that is the plain form present. A name whose stem is absent stays its own concept: agee does not join age, because no rule strips a trailing e.

Grouping too little costs some pruning. Grouping too much silently merges two clinical concepts and discards a real candidate, which is the error that cannot be seen in the output. The rule errs the safe way, and callers are expected to report the map so a human can check it.

Two mechanisms are needed, and only one can be mechanical. vars.sas truncates the stem when it names a derivative, so stripping the affix yields a name absent by exact match:

  • Truncation – effi is a prefix of effic, so it can be found. Handled automatically, but only when the stem prefixes exactly one pool member that is not itself a derived form, and only when the stem is at least min_stem characters. Ambiguity declines to group rather than guessing.

  • Contraction – arin is not a prefix of area_int and no rule can find it. Declare it via aliases, so the judgement is recorded in the job rather than inferred by the function.

Left ungrouped on a real screen, effic at 86.4% and ln_effi at 60.8% were both reported as retained risk factors at r = 0.9988, and in_arin at 70.6% hid a concept whose union across its forms was 86.4%.

A permanent compatibility layer, not a temporary shim

Stem truncation exists because SAS capped variable names at 8 characters. A modern builder has no such cap and a pool built that way needs none of this, because ln_efficiency strips cleanly to efficiency.

But the two regimes coexist indefinitely: reproducing a legacy SAS job in R is routine work, not a migration with an end date, so truncated pools keep arriving from built*.sas7bdat extracts for as long as old jobs are re-run. Do not delete the prefix rule or the alias table when a new builder ships. They are how a legacy pool is read. The right expectation is that new datasets simply never need them.

Examples

pool <- c("age", "ln_age", "age2", "agee")
vapply(pool, concept_of, character(1), pool = pool)
#>    age ln_age   age2   agee 
#>  "age"  "age"  "age" "agee"