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)的信号。标准差衡量的是数据点与均值的平均距离,对异常值非常敏感。

正确做法:当看到标准差特别大时,应该:

  1. 检查数据中是否存在异常值
  2. 考虑使用更稳健的离散度指标,如四分位距(IQR)
  3. 对数据进行分组分析,而不是整体分析

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

深度分析

  1. sessions列

    • 均值(4.985)和中位数(5)接近,分布较对称
    • 标准差2.226表明波动不大
    • 从25%(3)到75%(6)看,大部分用户会话次数在3-6次之间
  2. purchase_amount列

    • 均值(149.32)远大于中位数(99.87),右偏分布
    • 标准差491.54极大,存在极端高值
    • 75%分位数110.69与最大值6832差距巨大,证实少数高消费用户存在
  3. 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 针对偏态数据的处理方法

对于严重偏态数据,考虑:

  1. 对数变换:
log_amount = np.log1p(df['purchase_amount'])
print(log_amount.describe())
  1. 分箱分析:
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))
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐