Pandas数据分析避坑指南:describe()函数输出的这些统计指标,你真的看懂了吗?
Pandas数据分析避坑指南:describe()函数输出的统计指标深度解析
当你第一次在Jupyter Notebook中运行df.describe()时,可能会被那个整齐的统计表格所迷惑——看似简单的数字背后,隐藏着数据分布的复杂故事。很多数据分析师在这个阶段容易犯一个致命错误:把describe()的输出当作绝对真理,而忽略了数据背后的真实分布形态。
1. 为什么describe()的输出会误导你?
在数据分析的日常工作中,describe()函数通常是探索性分析的第一步。这个便捷的方法能快速生成计数、均值、标准差、最小值、四分位数和最大值等统计指标。但问题在于,这些数字单独看时常常具有欺骗性。
1.1 均值与中位数的"分道扬镳"
考虑一个电商平台的用户消费数据:
import pandas as pd
spending = [50, 60, 55, 58, 62, 10000] # 前五位是普通用户,最后一个是企业采购
df = pd.DataFrame({'spending': spending})
print(df.describe())
输出结果会显示:
spending
count 6.0
mean 1717.5
std 3981.5
min 50.0
25% 55.0
50% 58.5
75% 62.0
max 10000.0
这里出现了典型的**均值(1717.5)远大于中位数(58.5)**的情况。如果你只关注均值,可能会得出"平均每个用户消费1717元"的错误结论,而实际上大多数普通用户的消费在50-62元之间。
1.2 标准差的"夸张"表现
上例中3981.5的标准差看起来大得惊人,这实际上是数据中存在极端值(10000)的信号。标准差衡量的是数据点与均值的平均距离,对异常值非常敏感。
正确做法:当看到标准差特别大时,应该:
- 检查数据中是否存在异常值
- 考虑使用更稳健的离散度指标,如四分位距(IQR)
- 对数据进行分组分析,而不是整体分析
2. 四分位数的业务解读陷阱
describe()输出的25%、50%、75%分位数是理解数据分布的关键,但它们的业务含义常常被误解。
2.1 分位数不等于均匀分布
假设分析某SaaS产品的用户活跃天数(每月):
active_days = [0, 1, 2, 3, 4, 5, 10, 15, 20, 30]
df = pd.DataFrame({'active_days': active_days})
print(df.describe())
输出中的25%分位数是2.75天。新手常误以为"25%的用户活跃2.75天",实际上应该理解为"有25%的用户活跃天数≤2.75天"。
2.2 分位数间距的业务含义
观察以下销售业绩数据:
sales = [2000, 2100, 2200, 2300, 2400, 10000]
df = pd.DataFrame({'sales': sales})
stats = df.describe()
print(stats)
输出中75%分位数是2400,而最大值是10000,两者差距巨大。这表明:
- 75%的销售员业绩在2400以下
- 但最高业绩达到了10000,远高于第三四分位数
- 这种分布可能暗示销售团队中存在"超级销售员"现象
业务决策建议:
- 对于大多数(75%)销售员,制定2400以下的激励政策
- 对于顶尖销售员,需要单独分析其成功因素
- 考虑是否存在数据录入错误或特殊交易
3. 数据类型对describe()的影响
describe()的输出会根据数据类型自动调整,这可能导致分析时的疏忽。
3.1 数值型与类别型的不同输出
创建包含不同类型的数据框:
data = {
'age': [25, 30, 35, 40, 45], # 数值型
'department': ['Sales', 'IT', 'HR', 'IT', 'Sales'] # 类别型
}
df = pd.DataFrame(data)
print(df.describe(include='all')) # 使用include='all'显示所有列
输出结果:
age department
count 5.0 5
unique NaN 3
top NaN IT
freq NaN 2
mean 35.0 NaN
std 7.9 NaN
min 25.0 NaN
25% 30.0 NaN
50% 35.0 NaN
75% 40.0 NaN
max 45.0 NaN
关键发现:
- 数值型列(age)显示常规统计指标
- 类别型列(department)显示计数、唯一值数、众数(top)和出现频率(freq)
- 混合分析时容易忽略这种差异,导致错误解读
3.2 时间序列数据的特殊处理
对于时间类型数据,describe()会有不同表现:
dates = pd.to_datetime(['2023-01-01', '2023-01-02', '2023-01-03'])
df = pd.DataFrame({'dates': dates})
print(df.describe())
输出将包含最早日期、最晚日期等时间特有统计量,而非数值型统计指标。
4. 高级应用:定制化describe()输出
默认的describe()输出可能不满足所有分析需求,pandas提供了多种定制选项。
4.1 添加自定义百分位数
df = pd.DataFrame({'values': range(1, 101)})
custom_stats = df.describe(percentiles=[0.1, 0.2, 0.8, 0.9])
print(custom_stats)
输出将包含10%、20%、80%、90%分位数,适合分析数据分布的尾部情况。
4.2 按数据类型筛选输出
data = {
'score': [85, 90, 78, 92],
'grade': ['A', 'A', 'B', 'A'],
'pass': [True, True, False, True]
}
df = pd.DataFrame(data)
# 只查看数值型列
print(df.describe(include=['number']))
# 只查看布尔型列
print(df.describe(include=['bool']))
# 排除类别型列
print(df.describe(exclude=['object']))
4.3 结合groupby的多维度分析
对于分组数据,describe()能提供更丰富的洞察:
data = {
'department': ['Sales', 'Sales', 'IT', 'IT', 'HR'],
'salary': [6000, 6500, 8000, 8200, 5500]
}
df = pd.DataFrame(data)
grouped = df.groupby('department')['salary']
print(grouped.describe())
输出将显示每个部门的薪资统计分布,便于跨组比较。
5. 实战案例:电商用户行为分析
让我们通过一个真实场景展示如何正确解读describe()输出。
5.1 数据准备
假设我们有以下用户行为数据:
import numpy as np
# 生成模拟数据
np.random.seed(42)
data = {
'user_id': range(1, 1001),
'sessions': np.random.poisson(5, 1000), # 大部分用户5次左右
'purchase_amount': np.concatenate([
np.random.normal(100, 20, 990), # 普通用户
np.random.normal(5000, 1000, 10) # 高净值用户
]),
'time_on_site': np.random.exponential(30, 1000) # 指数分布
}
df = pd.DataFrame(data)
print(df.describe())
5.2 关键指标解读
输出结果可能类似:
user_id sessions purchase_amount time_on_site
count 1000.00000 1000.000000 1000.000000 1000.000000
mean 500.50000 4.985000 149.318283 30.117647
std 288.81944 2.226136 491.536329 29.960043
min 1.00000 0.000000 34.521764 0.045671
25% 250.75000 3.000000 87.314724 8.729145
50% 500.50000 5.000000 99.874204 20.886932
75% 750.25000 6.000000 110.692089 41.670050
max 1000.00000 13.000000 6832.218984 184.511726
深度分析:
-
sessions列:
- 均值(4.985)和中位数(5)接近,分布较对称
- 标准差2.226表明波动不大
- 从25%(3)到75%(6)看,大部分用户会话次数在3-6次之间
-
purchase_amount列:
- 均值(149.32)远大于中位数(99.87),右偏分布
- 标准差491.54极大,存在极端高值
- 75%分位数110.69与最大值6832差距巨大,证实少数高消费用户存在
-
time_on_site列:
- 均值(30.12)大于中位数(20.89),右偏分布
- 最小值接近0,最大值184.51,标准差29.96与均值接近
- 符合指数分布特征:多数用户停留时间短,少数用户停留很久
5.3 可视化验证
通过可视化验证describe()的发现:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
df['sessions'].plot.hist(ax=axes[0], bins=10)
axes[0].set_title('Sessions Distribution')
df['purchase_amount'].plot.hist(ax=axes[1], bins=50)
axes[1].set_title('Purchase Amount Distribution')
axes[1].set_xlim(0, 1000) # 排除极端值更好观察主体分布
df['time_on_site'].plot.hist(ax=axes[2], bins=50)
axes[2].set_title('Time on Site Distribution')
plt.tight_layout()
plt.show()
可视化结果将直观展示各列的实际分布形态,验证describe()的数字解读。
6. 替代方案:何时不使用describe()
虽然describe()很方便,但在某些情况下,其他方法可能更合适:
6.1 使用agg()自定义统计量
custom_stats = df.agg({
'purchase_amount': ['mean', 'median', lambda x: x.quantile(0.95)],
'time_on_site': ['min', 'max', 'skew'] # 偏度
})
print(custom_stats)
6.2 针对偏态数据的处理方法
对于严重偏态数据,考虑:
- 对数变换:
log_amount = np.log1p(df['purchase_amount'])
print(log_amount.describe())
- 分箱分析:
bins = [0, 50, 100, 200, 500, 1000, float('inf')]
labels = ['0-50', '50-100', '100-200', '200-500', '500-1000', '1000+']
df['amount_bin'] = pd.cut(df['purchase_amount'], bins=bins, labels=labels)
print(df['amount_bin'].value_counts())
6.3 稳健统计量替代方案
考虑使用不受异常值影响的统计量:
from scipy import stats
def robust_stats(series):
return pd.Series({
'median': series.median(),
'iqr': stats.iqr(series),
'mad': series.mad(), # 平均绝对偏差
'trim_mean_10': stats.trim_mean(series, 0.1) # 去除10%极端值的均值
})
print(df['purchase_amount'].pipe(robust_stats))
更多推荐


所有评论(0)