⚙️ Setup¶
import numpy as np
import pandas as pd
from scipy import stats
rng = np.random.default_rng(111)
print("Ready.")
Ready.
mean_, median_ = 420_000, 350_000
gap = mean_ - median_
print(f"mean - median = {gap:,} ->", "right-skewed" if gap>0 else "left-skewed")
mean - median = 70,000 -> right-skewed
Answer: The mean sits above the median, so the long tail points right: the data is right-skewed (a few expensive homes pull the mean up). The median (350,000) better describes a typical home, because it is not dragged by the high outliers.
sample = rng.exponential(scale=5, size=3000)
sk = stats.skew(sample)
band = "fairly symmetric" if abs(sk)<0.5 else ("moderately skewed" if abs(sk)<1 else "highly skewed")
print(f"skewness = {sk:+.2f} -> {band}, tail points {'right' if sk>0 else 'left'}")
skewness = +1.91 -> highly skewed, tail points right
Answer: An exponential sample has true skewness 2, so the computed value is well above 1: it is highly skewed to the right. The sign gives direction (+ = right), the magnitude gives severity. Remember the 0.5 and 1 cutoffs are heuristics, not hard law.
normal_s = rng.normal(0, 1, 5000)
laplace_s = rng.laplace(0, 1, 5000)
def label(k): return "mesokurtic (~normal tails)" if abs(k)<0.5 else ("leptokurtic (heavy tails)" if k>0 else "platykurtic (light tails)")
for name,d in [("normal",normal_s),("laplace",laplace_s)]:
k = stats.kurtosis(d) # excess: normal ≈ 0
print(f"{name:8} excess kurtosis = {k:+.2f} -> {label(k)}")
normal excess kurtosis = +0.00 -> mesokurtic (~normal tails) laplace excess kurtosis = +2.48 -> leptokurtic (heavy tails)
Answer: The normal sample lands near 0 (mesokurtic); the Laplace sample is clearly positive (leptokurtic). Positive excess kurtosis means heavier tails, more outliers, not a taller peak. That is the modern, correct reading of kurtosis: it is about tail extremity, not peakedness.
msg = ("Two peaks suggest two subpopulations mixed together, ",
"for example mostly-sedentary users and active users. ",
"A single overall average would fall in the empty valley ",
"between them and describe almost nobody. ",
"The analyst should segment the users and analyze each group separately.")
print("".join(msg))
Two peaks suggest two subpopulations mixed together, for example mostly-sedentary users and active users. A single overall average would fall in the empty valley between them and describe almost nobody. The analyst should segment the users and analyze each group separately.
Answer: A bimodal shape almost always signals two mixed subpopulations (here, sedentary vs. active users). The overall mean lands in the valley between the peaks and represents no real user. The right move is to disaggregate and study each group on its own.
raw = rng.lognormal(mean=2, sigma=1, size=4000)
logged = np.log(raw)
print(f"raw skewness = {stats.skew(raw):+.2f}")
print(f"log() skewness = {stats.skew(logged):+.2f}")
raw skewness = +5.02 log() skewness = -0.01
Answer: The raw data is strongly right-skewed (skew well above 1); after np.log the skewness drops to near 0, the long tail is pulled in and the shape is nearly symmetric. Caveat: the log is undefined for 0 or negative values (use log1p or a shift), and results are now on a log scale, so interpret them accordingly.