Weight Hierarchy
The canonical weight hierarchy and the code that walks it.
hierarchy
The canonical weight hierarchy, declared once.
Every table that carries a weight is declared here exactly once -- its weight
column, where the weight comes from, and the group within which that weight is
conserved. Everything else is a projection of that list, so the shape of the
hierarchy cannot drift between the propagation (processing.weighting.core.propagation), the supplied-weight path and the checksum.
Weights disaggregate down from households and aggregate back up into groupings; both directions are edges of one tree.
| Table | Weight | Derivation |
|---|---|---|
| households | hh_weight |
Anchor -- from the balancer, or supplied |
| persons | person_weight |
Down from hh_weight via hh_id |
| days | day_weight |
Split: person_weight / n_usable_days |
| unlinked | unlinked_trip_weight |
Down from day_weight via day_id |
| linked | linked_trip_weight |
Up: mean of member unlinked_trip_weight |
| tours | tour_weight |
Up: mean of member linked_trip_weight |
| joint trips | joint_trip_weight |
Up: sum of member linked_trip_weight |
| joint tours | joint_tour_weight |
Up: sum of member tour_weight |
Two units
Most weights count one row of their own table. The joint levels instead sum
their members, so they count person-trips -- the travel of the whole party,
not the joint occasion -- and are therefore an overlay on the member table,
never a partition of it: sum(linked) + sum(joint) double counts.
To count occasions instead, divide by the number of members that carried weight -- counted from the member table, not read off the grouping::
events = joint_trip_weight / n_member_trips_with_weight
A party-size column is not that divisor: it counts travellers, including any the weighting excluded.
One column set per profile
A run may weight several usability profiles, each fitted to its own universe. The
columns above are then suffixed with the profile's name, verbatim and unabridged
(hh_weight_ctramp), so a column names the universe it expands and no
rule is needed to read one back. Only [weight_col_for][processing.weighting.core
.hierarchy.weight_col_for] and its siblings spell that suffix; Level.weight_col
stays the base name, which is what keeps id_col derivable.
propagation
Walk the weight hierarchy, moving weights between canonical tables.
Used by both the compute_weights (computed) and add_existing_weights (supplied)
steps. The shape being walked is declared in processing.weighting.core.hierarchy; this module only moves weights along it.
The distribution rule
A record is admitted by a usability flag -- one of the usability profiles
stamped by the cascade_completeness step (see processing.completeness), so the weighted sample matches the records that
profile's consumer actually keeps. Pass survey_complete instead to weight the whole
valid survey, including partial and overnight tours.
Unusable records carry no weight, but their population does not vanish: it is carried by the usable records in the same scope. Give each record a claim equal to its parent's weight -- what it would carry if usable -- and the usable claims share the scope's whole claim:
w = claim x sum(claims in scope) / sum(claims of usable in scope)
Because every record in a scope is scaled by the same factor, the relative weights inside a household are untouched, and the conservation identity holds by construction:
sum(weight in scope) == sum(claim in scope)
Days: the average-day split
Days use a different DOWN rule (split=True): a person's usable days jointly
represent that person once, not once each --
day_weight = person_weight / n_usable_days (usable days; 0 otherwise)
sum(day_weight over a person's days) == person_weight
the vendor's own convention (their weighted days are exactly person_weight /
n), with our usability flag deciding which days count. Summing day weights
therefore yields persons on an average day, not person-days.
Conservation is strictly within the person -- never pooled across a household: pooling would siphon day-weight from members with fewer usable days toward members with more, silently distorting person-day totals while still passing any household-level checksum. A person with no usable day keeps their person weight (person weights stay calibrated to the person controls) but contributes nothing at the day level; that shortfall is reported by the checksum, never rescaled away. Unsurveyable persons (e.g. unrelated household members) carry no day rows at all, matching the vendor's day table.
Where a whole scope is unusable there is no denominator anywhere and the weight is genuinely stranded. That is reported as a shortfall, never silently rescaled.