Pediatrics growth chart
CDC LMS weight-for-age percentile curves, girls 2–20 years
Example from the compendium of canonical charts
Python Code
"""Pediatrics growth chart — CDC LMS weight-for-age percentile curves, girls 2–20 years."""
import numpy as np
import pandas as pd
import plotly.graph_objects as go
WTAGE_URL = "https://www.cdc.gov/growthcharts/data/zscore/wtage.csv"
# Percentile columns and their display labels
PCTS = ["P3", "P5", "P10", "P25", "P50", "P75", "P90", "P95", "P97"]
# Line styles: outer percentiles thin/dashed, inner thicker, median bold
PCT_STYLES = {
"P3": dict(color=MUTED, width=1, dash="dot"),
"P5": dict(color=MUTED, width=1.5, dash="dash"),
"P10": dict(color=TEAL, width=1.5, dash="dash"),
"P25": dict(color=GREEN, width=1.5),
"P50": dict(color=VIOLET, width=2.5),
"P75": dict(color=GREEN, width=1.5),
"P90": dict(color=TEAL, width=1.5, dash="dash"),
"P95": dict(color=MUTED, width=1.5, dash="dash"),
"P97": dict(color=MUTED, width=1, dash="dot"),
}
def generate():
print("fetching CDC weight-for-age LMS table …")
try:
df = fetch_csv(WTAGE_URL)
df.columns = [c.strip() for c in df.columns]
print(f" cols: {list(df.columns[:8])}")
except Exception as e:
print(f" fetch failed ({e}), using synthetic percentiles")
df = None
# Filter: female (Sex==2), ages 24–240 months
if df is not None:
df["Sex"] = pd.to_numeric(df.get("Sex", df.get("sex", pd.Series([]))), errors="coerce")
df["Agemos"] = pd.to_numeric(df.get("Agemos", df.get("agemos", pd.Series([]))), errors="coerce")
for p in PCTS:
df[p] = pd.to_numeric(df[p], errors="coerce")
df = df[(df["Sex"] == 2) & (df["Agemos"] >= 24) & (df["Agemos"] <= 240)].copy()
df = df.sort_values("Agemos").reset_index(drop=True)
if len(df) < 10:
df = None
if df is None:
# Synthetic CDC-like weight-for-age percentiles (girls 24-240 months)
ages = np.arange(24, 241)
# Approximate CDC values using a growth model
# Reference median ~13 kg at 24 mo, ~58 kg at 240 mo
def wt_pct(age_mo, z):
# LMS-based approximation for girls
m = 11.5 * (age_mo / 24) ** 0.42
s = 0.14
l = -0.1
return m * (1 + l * s * z) ** (1 / l)
z_vals = {"P3": -1.88, "P5": -1.645, "P10": -1.28, "P25": -0.674,
"P50": 0, "P75": 0.674, "P90": 1.28, "P95": 1.645, "P97": 1.88}
df = pd.DataFrame({"Agemos": ages})
for p, z in z_vals.items():
df[p] = wt_pct(ages, z)
ages = df["Agemos"].values
fig = go.Figure()
# Shaded band: P5 to P95
fig.add_trace(go.Scatter(
x=np.concatenate([ages, ages[::-1]]),
y=np.concatenate([df["P95"].values, df["P5"].values[::-1]]),
fill="toself",
fillcolor="rgba(132, 94, 238, 0.07)",
line=dict(width=0),
name="P5–P95 range",
hoverinfo="skip",
showlegend=True,
))
# Percentile lines
for p in PCTS:
style = PCT_STYLES[p]
show_in_legend = p in ("P5", "P50", "P95")
fig.add_trace(go.Scatter(
x=ages,
y=df[p].values,
mode="lines",
name=p,
line=style,
showlegend=show_in_legend,
hovertemplate=f"{p}: %{{y:.1f}} kg at %{{x:.0f}} mo<extra></extra>",
))
# Percentile labels at right edge
for p in ["P3", "P10", "P25", "P50", "P75", "P90", "P97"]:
last_y = df[p].values[-1]
fig.add_annotation(
x=242, y=last_y,
text=p, showarrow=False,
font=dict(size=9, color=PCT_STYLES[p]["color"]),
xanchor="left",
)
# Sample child: annual well-child visits from age 3 to 16 (36–192 months)
# Tracking between P50 and P75 with realistic visit-to-visit variation
sample_ages = np.array([36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, 168, 180, 192])
rng = np.random.default_rng(37)
idx_df = df.set_index("Agemos")
p50 = idx_df["P50"].reindex(sample_ages).values
p75 = idx_df["P75"].reindex(sample_ages).values
frac = np.clip(0.35 + rng.normal(0, 0.04, len(sample_ages)), 0.1, 0.7)
sample_wts = p50 + frac * (p75 - p50)
fig.add_trace(go.Scatter(
x=sample_ages,
y=sample_wts,
mode="markers+lines",
name="Sample child",
marker=dict(color=PINK, size=9, symbol="circle",
line=dict(color="white", width=1.5)),
line=dict(color=PINK, width=2.5, dash="dot"),
hovertemplate="Age %{x} mo: %{y:.1f} kg<extra></extra>",
))
fig.update_layout(
xaxis=dict(
title="Age (months)",
tickvals=list(range(24, 241, 24)),
ticktext=[str(m) for m in range(24, 241, 24)],
range=[22, 245],
),
yaxis=dict(title="Weight (kg)", range=[8, 80]),
legend=dict(orientation="h", y=1.08),
margin=dict(t=50, b=50, l=60, r=60),
height=500,
)
return fig
if __name__ == "__main__":
generate()
Made with Plotly