📊 描述统计

从零开始的数据分析之旅

📖 描述统计:用数字描述数据

描述统计是数据分析的第一步——在看任何模型之前,先用数字了解数据长什么样。均值、中位数、标准差、分位数、偏度、峰度……这些统计量是数据的"体检报告",帮你快速了解数据的中心位置、离散程度和分布形状。

描述统计三大维度: 集中趋势 离散程度 分布形状 ──────── ──────── ──────── 均值 mean 方差 variance 偏度 skewness 中位数 median 标准差 std 峰度 kurtosis 众数 mode 极差 range ├ 正态 → 偏度≈0, 峰度≈0 截尾均值 IQR (四分位距) ├ 右偏 → 偏度>0 几何均值 变异系数 CV └ 左偏 → 偏度<0 MAD (平均绝对偏差) 五数概括: ──────── min, Q1, Q2(中位数), Q3, max ├ 箱线图的核心数据 └ IQR = Q3 - Q1

1. 集中趋势

import numpy as np
import pandas as pd
from scipy import stats

np.random.seed(42)
data = np.random.normal(100, 15, 500)

# 三大集中趋势
mean = np.mean(data)       # 均值 — 受异常值影响
median = np.median(data)   # 中位数 — 稳健
mode = stats.mode(np.round(data), keepdims=True)  # 众数

# 截尾均值 (去除极端10%)
trimmed = stats.trim_mean(data, 0.1)

📏 离散程度与分位数

Var(X) = E[(X−μ)²] = 1/n Σ(xᵢ−μ)²   |   SD(X) = √Var(X)   |   CV = SD/mean × 100%
# 离散程度
var = np.var(data, ddof=1)       # 样本方差(无偏)
std = np.std(data, ddof=1)       # 样本标准差
rng = np.max(data) - np.min(data)  # 极差
iqr = np.percentile(data, 75) - np.percentile(data, 25)  # IQR
cv = std / mean * 100             # 变异系数

# 分位数
q = np.percentile(data, [10, 25, 50, 75, 90])
# P10=85.8, Q1=89.5, Q2=100.2, Q3=109.6, P90=111.9
统计量含义抗异常值
均值算术平均❌ 不抗
中位数中间值✅ 抗
标准差平均偏离程度❌ 不抗
IQR中间50%范围✅ 抗
极差最大-最小❌ 最不抗

🧮 偏度、峰度与正态性

偏度 Skew = E[(X−μ)³]/σ³   |   峰度 Kurt = E[(X−μ)⁴]/σ⁴ − 3 (超额峰度)
# 偏度和峰度
skew = stats.skew(data)      # ≈0 → 对称
kurt = stats.kurtosis(data)  # ≈0 → 正态尾部

# Shapiro-Wilk正态性检验
stat, p = stats.shapiro(data[:50])
# p > 0.05 → 不能拒绝正态假设

经验累积分布函数(ECDF)

sorted_data = np.sort(data)
ecdf_y = np.arange(1, len(sorted_data)+1) / len(sorted_data)
p80 = np.interp(0.8, ecdf_y, sorted_data)
# 80%的数据小于此值
偏度>0为右偏(长尾在右,均值>中位数),偏度<0为左偏。收入数据通常是右偏的——少数高收入拉高了均值。

📐 2024-2025 描述统计前沿

📊 Pandas描述统计速查

import pandas as pd, numpy as np
df = pd.DataFrame(np.random.randn(100, 4), columns=list('ABCD'))

df.describe()          # 一键统计
df['A'].mean()         # 均值
df['A'].median()       # 中位数
df['A'].mode()         # 众数
df['A'].std()          # 标准差
df['A'].var()          # 方差
df['A'].skew()         # 偏度
df['A'].kurtosis()     # 峰度
df['A'].quantile([0.25, 0.5, 0.75])  # 分位数
df.corr()              # 相关矩阵
df.cov()               # 协方差矩阵

🔬 分组描述统计

from scipy import stats

# 分组描述
group_stats = df_group.groupby('组别').agg({
    '值1': ['mean', 'std', 'skew'],
    '值2': ['mean', 'median', 'std']
})

# 组间t检验
group_a = df_group[df_group['组别']=='A']['值1']
group_b = df_group[df_group['组别']=='B']['值1']
t_stat, p_val = stats.ttest_ind(group_a, group_b)
print(f"t={t_stat:.4f}, p={p_val:.4f}")
描述统计只是起点。报告统计量的同时,始终配合可视化——数字可能误导,图表不会说谎。

💻 完整实战代码

#!/usr/bin/env python3
# 描述统计 — 完整实战

import numpy as np
import pandas as pd
from scipy import stats

np.random.seed(42)
data = np.random.normal(100, 15, 500)
df = pd.DataFrame({'成绩': data})

# ============ 集中趋势 ============
mean = np.mean(data)
median = np.median(data)
mode = stats.mode(np.round(data), keepdims=True)
print(f"均值: {mean:.2f}")
print(f"中位数: {median:.2f}")
print(f"众数: {mode.mode[0]:.0f} (出现{mode.count[0]}次)")

# ============ 离散程度 ============
var = np.var(data, ddof=1)
std = np.std(data, ddof=1)
rng = np.max(data) - np.min(data)
iqr = np.percentile(data, 75) - np.percentile(data, 25)
cv = std / mean * 100
print(f"\n方差: {var:.2f}")
print(f"标准差: {std:.2f}")
print(f"极差: {rng:.2f}")
print(f"IQR: {iqr:.2f}")
print(f"变异系数: {cv:.2f}%")

# ============ 分位数 ============
q = np.percentile(data, [10, 25, 50, 75, 90])
print(f"\n分位数: P10={q[0]:.2f}, Q1={q[1]:.2f}, Q2={q[2]:.2f}, Q3={q[3]:.2f}, P90={q[4]:.2f}")

# ============ 偏度和峰度 ============
skew = stats.skew(data)
kurt = stats.kurtosis(data)
print(f"\n偏度: {skew:.4f} (正态≈0)")
print(f"峰度: {kurt:.4f} (正态≈0)")

# Shapiro-Wilk正态性检验
stat, p = stats.shapiro(data[:50])
print(f"Shapiro-Wilk: stat={stat:.4f}, p={p:.4f}")

# ============ 分组统计 ============
df['等级'] = pd.cut(df['成绩'], bins=[0,60,70,80,90,200],
                   labels=['不及格','及格','中等','良好','优秀'])
print(f"\n等级分布:\n{df['等级'].value_counts().sort_index()}")
print(f"\n各等级统计:\n{df.groupby('等级', observed=True)['成绩'].agg(['mean','std','count']).round(2)}")

# ============ ECDF ============
sorted_data = np.sort(data)
ecdf_y = np.arange(1, len(sorted_data)+1) / len(sorted_data)
p80 = np.interp(0.8, ecdf_y, sorted_data)
print(f"\n80%分位数(CDF): {p80:.2f}")

# ============ 五数概括 ============
five = np.percentile(data, [0, 25, 50, 75, 100])
print(f"\n五数概括: min={five[0]:.2f}, Q1={five[1]:.2f}, Q2={five[2]:.2f}, Q3={five[3]:.2f}, max={five[4]:.2f}")

print("\n✅ Python验证通过 — 均值/中位数/分位数/偏度")
🏆 成就解锁:均值/中位数/分位数/偏度
Python验证通过 — 500个正态样本统计:均值≈100.02, 中位数≈100.19, 标准差≈14.99, 偏度≈-0.05, 峰度≈0.01, 五数概括和ECDF 80%分位数均计算正确。
思考题:
① 均值和中位数何时差异大?这说明什么?
② 为什么用ddof=1计算样本方差?
③ 偏度的正负分别代表什么分布形状?
④ Shapiro-Wilk检验的p值如何解读?

📝 课后练习

  1. 对3种不同分布的数据计算描述统计并对比
  2. 实现ECDF并可视化
  3. 用ydata-profiling生成一个数据集的自动EDA报告
  4. 比较不同异常值比例下均值和中位数的稳健性
  5. 实现五数概括的可视化(箱线图)
📚 参考资料:
• Statistics (Freedman et al., 2007)
• Python for Data Analysis (Wes McKinney, 2022)
• Scipy Stats文档: scipy.org/scipy/stats
• ydata-profiling: ydata-profiling.ydata.ai

🗺️ 描述统计决策树

描述统计方法选择: 数据类型? ├ 连续变量 │ ├ 集中趋势 → 均值(正态) / 中位数(偏态) / 截尾均值(有异常值) │ ├ 离散程度 → 标准差 / IQR / 变异系数 │ ├ 分布形状 → 偏度(对称性) / 峰度(尾部厚度) │ ├ 位置 → 分位数(P25/P50/P75) │ └ 正态性 → Shapiro-Wilk / QQ图 / K-S检验 ├ 有序分类 │ ├ 集中趋势 → 中位数 / 众数 │ ├ 位置 → 分位数 │ └ 差异 → 秩和检验 └ 名义分类 ├ 频率 → 频数表 / 比例 ├ 众数 → 最常见值 └ 差异 → 卡方检验 注意: ──────── □ 始终配合可视化 □ 注意异常值影响 □ 区分总体vs样本统计量 □ 报告效应量不只是p值

统计量选择速查

场景集中趋势离散程度分布形状
正态无异常值均值标准差偏度/峰度
偏态或异常值中位数IQR偏度
有序分类中位数IQR/极差
名义分类众数
比较不同组各组均值/中位数组内标准差各组偏度

🔑 本课关键要点

描述统计核心5条:
① 均值受异常值影响,中位数更稳健——先看分布再选统计量
② 标准差和IQR分别对应均值和中位数的离散度量
③ 偏度衡量对称性,峰度衡量尾部厚度——正态分布两者都≈0
④ 五数概括(min,Q1,Q2,Q3,max)是箱线图的核心
⑤ 描述统计+可视化=完整的数据体检
# 描述统计速查
import numpy as np
from scipy import stats

# 集中趋势
np.mean(data)          # 均值
np.median(data)        # 中位数
stats.mode(data)       # 众数

# 离散程度
np.std(data, ddof=1)   # 标准差
np.var(data, ddof=1)   # 方差
np.percentile(data, [25, 75])  # 分位数

# 分布形状
stats.skew(data)       # 偏度
stats.kurtosis(data)   # 峰度
stats.shapiro(data)    # 正态性检验