⚙️ Setup¶
import numpy as np
import pandas as pd
NAVY="#0a1230"; INK="#1a2138"; INK_SOFT="#4a5578"
print("✅ Ready.")
✅ Ready.
answer = pd.DataFrame({
"variable":["blood type","exam grade (A-F)","temperature °C","weight kg",
"finishing place","temperature Kelvin","calendar year"],
"level": ["Nominal","Ordinal","Interval","Ratio","Ordinal","Ratio","Interval"],
"why": ["names, no order","ordered grades, unequal gaps","equal gaps, no true zero",
"true zero (0 kg = none)","ranked order only","0 K = no heat (true zero)",
"equal gaps, year 0 is arbitrary"],
})
answer
| variable | level | why | |
|---|---|---|---|
| 0 | blood type | Nominal | names, no order |
| 1 | exam grade (A-F) | Ordinal | ordered grades, unequal gaps |
| 2 | temperature °C | Interval | equal gaps, no true zero |
| 3 | weight kg | Ratio | true zero (0 kg = none) |
| 4 | finishing place | Ordinal | ranked order only |
| 5 | temperature Kelvin | Ratio | 0 K = no heat (true zero) |
| 6 | calendar year | Interval | equal gaps, year 0 is arbitrary |
Answer: the two tricky ones, Kelvin is ratio (0 K is a true "no heat" zero, so 200 K really is twice 100 K), while °C and calendar years are interval (their zeros are arbitrary).
check = pd.DataFrame({
"variable":["temperature °C","height","income","IQ score"],
"ratio scale?":["No","Yes","Yes","No"],
"\"twice\" valid?":["No (no true zero)","Yes","Yes (0 income = none)","No (no true zero)"],
})
check
| variable | ratio scale? | "twice" valid? | |
|---|---|---|---|
| 0 | temperature °C | No | No (no true zero) |
| 1 | height | Yes | Yes |
| 2 | income | Yes | Yes (0 income = none) |
| 3 | IQ score | No | No (no true zero) |
Answer: Height and income are ratio (true zero), so "twice as tall / twice the income" make sense. °C and IQ are interval, their zeros are arbitrary, so "twice the temperature / twice as smart" are meaningless.
colors = pd.Series(["red","blue","red","green","red"]) # nominal
rating = pd.Series([1, 3, 2, 3, 1]) # ordinal (Low=1..High=3)
salary = pd.Series([42000, 55000, 38000, 61000, 50000]) # ratio
print("Favorite color (nominal) -> mode :", colors.mode()[0])
print("Satisfaction (ordinal) -> median:", int(rating.median()))
print(f"Annual salary (ratio) -> mean : {salary.mean():,.0f}")
Favorite color (nominal) -> mode : red Satisfaction (ordinal) -> median: 2 Annual salary (ratio) -> mean : 49,200
Answer: Nominal → mode (most frequent). Ordinal → median (mean of codes is misleading). Ratio → mean is fully valid.
# Zip codes are NOMINAL labels — averaging them is meaningless
zips = pd.Series([60201, 60601, 60412, 60634])
print("Zip code is NOMINAL -> the average has no meaning.")
print(" Better: report the mode or counts per zip:", dict(zips.value_counts()))
# Satisfaction is ORDINAL -> mean assumes equal gaps; use the median
sat = pd.Series([1,5,3,5,1])
print(f"\nSatisfaction is ORDINAL -> report median ({sat.median():.0f}), not the mean ({sat.mean():.1f}).")
Zip code is NOMINAL -> the average has no meaning.
Better: report the mode or counts per zip: {60201: np.int64(1), 60601: np.int64(1), 60412: np.int64(1), 60634: np.int64(1)}
Satisfaction is ORDINAL -> report median (3), not the mean (3.0).
Answer: A zip code is a nominal label, so its mean is nonsense (use mode/counts). Satisfaction is ordinal, so the mean assumes equal spacing between levels, report the median instead. Matching the statistic to the level is the key idea of this chapter.