cassionData Analysis

Lesson 7 of 8

Unit · The design and the fit

Two coefficients cross the line and one crosses back

The rural-remote stratum holds 11% of households and a third of the interviews. Fit the model unweighted and livestock livelihoods look like a significant risk factor; weight it and they stop being one, while household size and returnee status start.

PythonR150 minDemographic and Health Survey (DHS)Multiple Indicator Cluster Survey (MICS)SMART surveyUNICEF indicator definitions

The design the sample was drawn under

import pandas as pd

survey = pd.read_csv("household-survey-2025.v1.csv")
frame = pd.read_csv("household-survey-frame-2025.v1.csv")

print(frame.groupby("stratum")["households"].sum())
print(survey.groupby("stratum").size())
library(dplyr)
frame |> summarise(households = sum(households), .by = stratum)
survey |> count(stratum)
Stratum Households in frame Share Interviews Share
Rural accessible 28,958 51.3% 337 33.8%
Urban 21,270 37.7% 316 31.7%
Rural remote 6,200 11.0% 343 34.4%

Rural remote is 11.0% of the population and 34.4% of the sample. That is a deliberate design choice — the survey course explains why you over-sample a small stratum you need estimates for — and it means every unweighted number from this file describes a population that does not exist.

What the weights do to an estimate

stratum_households = frame.groupby("stratum")["households"].sum()
base_weight = stratum_households / (25 * 14)
interviewed = frame[frame["selected"]].set_index("ea_id")["households_interviewed"]

survey["weight"] = (survey["stratum"].map(base_weight)
                    * 14 / survey["ea_id"].map(interviewed))
assert abs(survey["weight"].sum() - frame["households"].sum()) < 1

unweighted = survey["food_insecure"].mean()
weighted = np.average(survey["food_insecure"], weights=survey["weight"])
print(f"unweighted {unweighted:.1%}   weighted {weighted:.1%}")
survey <- survey |>
  mutate(weight = base_weight[stratum] * 14 / households_interviewed)

c(unweighted = mean(survey$food_insecure),
  weighted   = weighted.mean(survey$food_insecure, survey$weight))

32.8% unweighted, 29.1% weighted. Rural remote has the highest food insecurity (46.4%), and over-representing it by three times pulls the national figure up by 3.7 points.

A regression fitted on this file without weights inherits exactly that problem, and it is not fixed by adding stratum as a covariate — that changes the question from “what is the association in the population” to “what is it within strata”. Both are legitimate; only one is what a national estimate means.

The same model, weighted and not

import statsmodels.api as sm
import statsmodels.formula.api as smf

FORMULA = ("food_insecure ~ improved_water_source + displacement_status"
           " + main_livelihood + household_size")

plain = smf.glm(FORMULA, data=d, family=sm.families.Binomial()).fit(
    cov_type="cluster", cov_kwds={"groups": d["ea_id"]})

weighted = smf.glm(FORMULA, data=d, family=sm.families.Binomial(),
                   freq_weights=d["weight"]).fit(
    cov_type="cluster", cov_kwds={"groups": d["ea_id"]})
library(survey)

design <- svydesign(ids = ~ea_id, strata = ~stratum, weights = ~weight,
                    data = survey, nest = TRUE)
svyglm(food_insecure ~ improved_water_source + displacement_status +
         main_livelihood + household_size, design = design,
       family = quasibinomial())
Term Unweighted OR Weighted OR Moves?
Improved water source 0.75 (0.55–1.03) 0.74 (0.51–1.08) —
Displacement: returnee 0.56 (0.25–1.26) 0.39 (0.15–0.97) becomes significant
Livelihood: farming 1.55 (1.03–2.34) 1.69 (1.04–2.75) —
Livelihood: livestock 1.51 (1.04–2.18) 1.26 (0.73–2.18) stops being significant
Livelihood: salaried 0.41 (0.22–0.77) 0.45 (0.24–0.87) —
Household size 1.04 (0.98–1.11) 1.08 (1.01–1.16) becomes significant

Three coefficients change which side of the line they sit on, in both directions. Weighting is not a correction that makes everything weaker or everything stronger — it re-weights which households the association is estimated from, and the answer moves wherever those households differ.

Livestock livelihoods are concentrated in the over-sampled remote stratum. Unweighted, they carry a third of the sample’s influence; weighted, they carry their real 11%, and the association thins out to nothing.

Weights, clusters and strata are three separate things

They arrive together and they do three different jobs, and a model can easily get one right and the other two wrong.

Design feature Fixes If you omit it
Weights The estimate Estimates describe the sample, not the population
Clusters The standard error Standard errors too small, as in the last lesson
Strata The standard error Standard errors slightly too large — the conservative error

Weights change the coefficient; clusters and strata change its interval. That is the same distinction as adjustment-versus-clustering from lesson 2, arriving from a different direction, and it is the one to hold onto.

print(f"clusters: {d['ea_id'].nunique()} enumeration areas")
print(f"strata:   {d['stratum'].nunique()}")
print(f"weights:  {d['weight'].min():.1f} to {d['weight'].max():.1f}")
# survey::svydesign wants all three; a glm with weights= handles only one.

glm(..., weights=) is not survey-weighted regression. It applies the weights and computes standard errors as though every observation were independent and the weights were frequencies. Use survey::svyglm in R, and in Python pass both freq_weights and a cluster covariance — or accept that the interval is wrong and say so.

When to leave the weights out

Two cases, and neither is “the weights made my result go away”.

A within-stratum question. “Among rural remote households, does an improved water source go with lower food insecurity?” is a question about that stratum, and the design weight within a stratum is nearly constant anyway.

A model whose covariates include everything the weights are built from. If stratum and selection probability are fully captured by the covariates, the weighted and unweighted estimates answer the same question and the unweighted one is more precise. Check it rather than assume it: fit both, and if they differ materially the covariates did not capture the design.

print(f"unweighted {plain.params['main_livelihood[T.livestock]']:.3f}")
print(f"weighted   {weighted.params['main_livelihood[T.livestock]']:.3f}")
# A large gap between the two is evidence the design is not in the model.

A large difference between the weighted and unweighted fit is itself diagnostic, and it is the check to run before deciding the weights are optional.

Report it whole

Food insecurity, household survey 2025

  976 households with complete covariates (of 996 interviewed),
  75 enumeration areas, 3 strata.
  Survey-weighted logistic regression; weights sum to 56,428 households,
  which is the frame total. Standard errors account for clustering by
  enumeration area and for stratification.

  Weighted prevalence      29.1%   (unweighted 32.8%)

  Adjusted odds ratios (weighted):
    Improved water source          0.74   95% CI 0.51 to 1.08
    Returnee household             0.39   95% CI 0.15 to 0.97
    Farming livelihood             1.69   95% CI 1.04 to 2.75
    Livestock livelihood           1.26   95% CI 0.73 to 2.18
    Salaried livelihood            0.45   95% CI 0.24 to 0.87
    Household size, per person     1.08   95% CI 1.01 to 1.16

  The unweighted model makes livestock livelihoods significant (OR 1.51,
  95% CI 1.04 to 2.18) and household size not. The rural remote stratum is
  11% of households and 34% of interviews, and livestock is concentrated
  there; the unweighted result is an artefact of the sampling design.

  Odds ratios are reported because food insecurity is 29% and the ratios are
  moderate; the weighted risk difference for salaried livelihoods is -14.2
  points against the observed mix of livelihoods, which is the number in
  the summary.

The paragraph naming the artefact is what a reviewer will check first, and writing it yourself is better than having it found.

What comes next

Every model so far has been fitted and read. The last lesson fits one that explains three per cent of its outcome, does not improve on guessing, and is the most useful result in its report — because what it fails to predict is the operational answer.

Teach this lesson

The lesson as a slide deck, with the prose kept in the speaker notes rather than on the slide. Generated from this page, so it cannot fall out of step with it.

Start the slideshowRead the slides

The PDF needs no software and projects from any machine. The PowerPoint file is there to be edited — add your organisation's branding, cut a section for a shorter session, or merge two lessons into a workshop.