Data SciencePortfolio projects with Indian public data

Anaemia between NFHS-4 and NFHS-5 by state, with honest uncertainty

PK
Pankit Kumar
Sr. Data Scientist at Parexel (a Goldman Sachs–backed company) · 27 September 2026 · 5 min read
In this article (7 sections)

Related programme: the Data Science course (6 months; ₹1,30,000 (inclusive of GST) · EMI from ₹5,000/month).

The short answer

The National Family Health Survey reports anaemia prevalence by state for two recent rounds, NFHS-4 (2015-16) and NFHS-5 (2019-21). That makes a clean data science question possible: where did anaemia rise or fall, and are the changes related to where each state started? This project pulls the anaemia indicators from MoSPI's eSankhyiki portal and computes state-level changes. It then tests the "worse states improved more" idea while guarding against regression to the mean and ecological reasoning. You end with a chart, a short model and a well-argued paragraph on what the data can and cannot support.

Source and structure

MoSPI's eSankhyiki NFHS page provides NFHS indicators with these columns: Indicator, State, Sub Indicator, Sector, Survey, Value. The Survey column takes values like nfhs-4 and nfhs-5. Indicator groups include Anaemia, Blood Sugar, Hypertension, Child Feeding and Nutrition, Women's Empowerment and Tobacco/Alcohol.

Under Anaemia, the sub-indicators split by population group, for example children, non-pregnant women, pregnant women, all women and men, each with an age band. Check the exact sub-indicator strings in your download and copy them exactly. Near-duplicate labels are the most likely cause of a silent mismatch between rounds.

Two real features of these surveys to state in your README: NFHS-5 fieldwork ran in two phases across 2019-21, partly during the COVID-19 period, and both rounds are sample surveys. So every state figure has a sampling error that the portal table does not show. The official state fact sheets and reports from the survey organisers are where to look for standard errors or confidence intervals.

Why this is worth an employer's time

Public-health NGOs, CSR teams, health-tech companies and research consultancies work with NFHS constantly. The interesting part of this project is not the subtraction. It is recognising that a naive "change against baseline" correlation is biased and doing something about it. That is a statistical maturity signal interviewers notice.

Cleaning list

  1. 1Match sub-indicators across rounds exactly. If NFHS-5 renamed a label, map it by hand and record the mapping.
  2. 2Choose a sector. Total, urban or rural. Do not mix them in one comparison.
  3. 3Handle units that changed. Union territories were reorganised in 2019 and 2020 (Jammu and Kashmir, Ladakh, Dadra and Nagar Haveli and Daman and Diu). Only compare units present with the same boundary in both rounds, or document your mapping.
  4. 4Values are percentages. Keep changes in percentage points.

The regression-to-the-mean trap

If each state's NFHS-4 value contains sampling noise, then states that looked worst at baseline were partly unlucky. Their next measurement drifts back toward the average, even if nothing real changed. Regressing change on baseline therefore produces a negative slope from noise alone. The fix is to regress change on a baseline that is measured independently of the noise, or to estimate how large the artefact would be.

The code below shows the artefact on synthetic data where the true change is zero for every state. None of these numbers are NFHS results.

python
import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n_states = 30
true_level = rng.uniform(45, 60, n_states)       # true prevalence, unchanged between rounds
se = rng.uniform(3.0, 6.0, n_states)             # sampling error per state (percentage points)
nfhs4 = true_level + rng.normal(0, se)
nfhs5 = true_level + rng.normal(0, se)           # no real change at all
df = pd.DataFrame({"nfhs4": nfhs4, "nfhs5": nfhs5, "se": se})
df["change"] = df["nfhs5"] - df["nfhs4"]

naive_slope = np.polyfit(df["nfhs4"], df["change"], 1)[0]
print(f"naive slope of change on baseline: {naive_slope:.3f}  (true relationship is 0)")

# Correction 1: regress change on the average of the two rounds, which shares no single-round noise
avg_slope = np.polyfit((df["nfhs4"] + df["nfhs5"]) / 2, df["change"], 1)[0]
print(f"slope on the two-round average: {avg_slope:.3f}")

# Correction 2: simulate both slopes under 'no change' and see where the observed ones fall.
# The observed NFHS-4 value stands in for the unknown true level, which slightly exaggerates the artefact.
naive_null, avg_null = [], []
for _ in range(2000):
    a = df["nfhs4"] + rng.normal(0, df["se"])
    b = df["nfhs4"] + rng.normal(0, df["se"])
    naive_null.append(np.polyfit(a, b - a, 1)[0])
    avg_null.append(np.polyfit((a + b) / 2, b - a, 1)[0])
for name, obs, null in [("naive", naive_slope, naive_null), ("average", avg_slope, avg_null)]:
    lo, hi = np.percentile(null, [2.5, 97.5])
    print(f"{name}: null centre {np.median(null):.3f}, 95% band [{lo:.3f}, {hi:.3f}], "
          f"observed inside band: {lo <= obs <= hi}")

# Is each state's change larger than its sampling noise?
df["z"] = df["change"] / np.sqrt(2 * df["se"] ** 2)
print("states with |z| > 1.96:", int((df["z"].abs() > 1.96).sum()), "of", n_states)

In this synthetic world nothing changed, yet the naive slope is negative. The simulation shows why: under "no change", the naive slope is centred well below zero, while the slope on the two-round average is centred near zero. Both observed slopes sit inside their no-change bands, which is the honest conclusion. With only about thirty states, even the corrected slope is noisy, so report its band, not just the point estimate. With real data, you need each state's standard error to run the simulation and the z-scores. Take it from the official reports, or state an assumed range and show how sensitive the conclusion is.

Analysis plan

  • Extract one anaemia sub-indicator, for example all women aged 15–49, for Total in both rounds.
  • Compute change in percentage points and map it by state, or draw a sorted dot chart if you have no map boundaries.
  • Fit change on baseline naively, then with both corrections. Report all three.
  • Flag the states whose change exceeds its sampling noise, if you have standard errors.
  • Repeat for children and men to see whether the pattern holds across groups.

Deliverables

OutputContent
Dumbbell chartNFHS-4 and NFHS-5 values per state, sorted by change
ScatterChange against baseline with the naive and corrected fits
TableState, both values, change, z-score or "SE unavailable"
Memo250 words on what changed, what is noise and what cannot be concluded
READMEPortal, download date, sub-indicator strings, sector, boundary mapping, SE source

The memo must avoid causal claims. A falling anaemia rate does not identify which programme caused it, and state-level patterns say nothing certain about individuals.

Interview questions

  • Explain regression to the mean without using a formula.
  • Why does regressing change on the two-round average remove the artefact?
  • NFHS-5 fieldwork overlapped with the pandemic. How could that affect comparability?
  • What is the ecological fallacy, and where could it appear in your memo?
  • How would a hierarchical model improve the state estimates?

For more survey-based work, the youth unemployment project uses PLFS with a different kind of check. Measurement error and simulation-based reasoning are covered in our Data Science programme.

PK
Pankit Kumar
Lead Instructor, NeuraPath Academy

Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.

This article is part of our Data Science programme — 6 months. From data foundations to machine learning, deep learning and deployment.

Explore Data Science
Counselling is free · no obligation

Not sure which programme fits?

Tell us your background and we will map it to the right entry point — including saying so when a cheaper programme is the better fit. A counsellor replies within one working day.