Pandas describe() 的数据侦探术:从统计量中挖掘数据质量线索

第一次用describe()时,你可能只关注了那些平均值和标准差。但当你盯着屏幕上那几行统计量看了三年后,会发现这些数字背后藏着整个数据集的秘密。就像福尔摩斯能从烟灰推断出凶手抽什么牌子的雪茄,熟练的数据分析师能从describe()的输出中嗅出数据异常、分布问题甚至采集过程中的失误。

1. 数据质量诊断的六种武器

1.1 异常值检测:当最大值背叛了分位数

观察这个电商价格数据的describe()输出:

import pandas as pd
prices = pd.Series([15, 18, 22, 19, 17, 21, 16, 20, 2500, 24])
print(prices.describe())

输出结果中的异常信号:

count       10.000000
mean       266.200000
std        786.987569
min         15.000000
25%         17.250000
50%         19.500000
75%         21.750000
max       2500.000000

红色警报

  • 75%分位数是21.75,但最大值跳到了2500
  • 标准差(786)比均值(266)大近3倍
  • 中位数(19.5)与均值(266)差异巨大

这类情况常见于:

  • 数据录入错误(多输了个0)
  • 系统错误(单位混用,如把美元记成分)
  • 真实异常订单(需要业务确认)

处理方案对比:

方法 适用场景 代码示例
缩尾处理 保留大部分数据 prices.clip(lower=prices.quantile(0.01), upper=prices.quantile(0.99))
IQR过滤 严格剔除异常值 Q1 = prices.quantile(0.25); Q3 = prices.quantile(0.75); prices[(prices >= Q1-1.5*(Q3-Q1)) & (prices <= Q3+1.5*(Q3-Q1))]
业务规则修正 已知数据问题 prices.replace(2500, 25)

1.2 分布形态诊断:标准差与分位数的共谋

比较两个用户行为指标的统计量:

behavior = pd.DataFrame({
    'page_views': [12,15,18,14,13,16,17,19,11,20],
    'click_rate': [0.02,0.03,0.025,0.8,0.022,0.026,0.024,0.021,0.9,0.023]
})
print(behavior.describe())

关键发现:

  • click_rate的std(0.3)是page_views(3.0)的10%,但均值只有后者的1/500
  • click_rate的75%分位(0.026)与max(0.9)差距34倍

这暗示:

  1. click_rate存在严重右偏分布
  2. 可能有未处理的百分比/小数转换问题
  3. 需要做对数变换或分箱处理

分布修正方案:

# 对数变换
behavior['log_click'] = np.log1p(behavior['click_rate'])

# 分箱处理
bins = [0,0.05,0.1,1]
behavior['click_bin'] = pd.cut(behavior['click_rate'], bins=bins)

# 标准化对比
from sklearn.preprocessing import StandardScaler
behavior[['scaled_views','scaled_click']] = StandardScaler().fit_transform(behavior[['page_views','click_rate']])

2. 高级参数的数据侦查技巧

2.1 百分位数的战术配置

默认的25/50/75分位数可能掩盖关键节点:

sales = pd.Series([120,150,180,210,240,270,300,330,360,390,420,450,480,510,540,570,600,630,660,690,720,750,780,810,840,870,900,930,960,990])
print(sales.describe(percentiles=[0.1,0.3,0.7,0.9]))

输出揭示的销售模式:

count     30.000000
mean     555.000000
std      262.735845
min      120.000000
10%      246.000000
30%      426.000000
50%      555.000000
70%      684.000000
90%      864.000000
max      990.000000

业务洞见:

  • 10%分位(246)到30%分位(426)跨度180,而70%到90%跨度也是180
  • 但30%-70%跨度258,显示中部客户贡献更集中
  • 可以设置三档营销策略对应这三个区间

2.2 类型数据的隐藏信息

对混合类型DataFrame使用include参数:

mixed_df = pd.DataFrame({
    'age': [25,32,28,45,33],
    'income': [5000,8000,6000,12000,9000],
    'department': ['Sales','Tech','Tech','HR','Sales'],
    'join_date': pd.date_range('20230101', periods=5)
})

print(mixed_df.describe(include='all'))

文本型字段的关键发现:

       age        income department    join_date
count   5.0     5.000000          5            5
unique  NaN          NaN          3            5
top     NaN          NaN      Sales  2023-01-01
freq    NaN          NaN          2            1
  • Tech和Sales部门各2人,HR仅1人 → 可能部门样本不均衡
  • join_date的unique=count → 可能没有重复入职日
  • 结合age和income分析部门年龄收入结构

3. 统计量的组合侦查法

3.1 变异系数的威力

单纯比较标准差可能误导:

metrics = pd.DataFrame({
    'api_latency_ms': [120,125,118,122,130],
    'db_query_s': [0.8,0.9,0.7,0.85,1.1]
})
stats = metrics.describe()
stats.loc['cv'] = stats.loc['std'] / stats.loc['mean']  # 添加变异系数行

得到的质量评估:

        api_latency_ms  db_query_s
mean          123.000       0.870
std             4.183       0.147
cv              0.034       0.169

虽然latency的std(4.18)比query(0.147)大,但变异系数显示:

  • query时间的相对波动是latency的5倍
  • 应该优先优化数据库查询稳定性

3.2 偏度与峰度的快捷估算

通过分位数关系快速判断分布形态:

def quick_skewness(s):
    q = s.quantile([0.25,0.5,0.75])
    return (q[0.25] + q[0.75] - 2*q[0.5]) / (q[0.75] - q[0.25])
    
quick_skewness(prices)  # 返回0.2表示右偏

经验阈值:

  • 绝对值>0.5:显著偏态
  • 0:右偏长尾

  • <0:左偏长尾

4. 自动化监控实践

4.1 动态阈值告警系统

class DataQualityMonitor:
    def __init__(self, baseline_data):
        self.baseline = baseline_data.describe()
        
    def check_deviation(self, new_data, threshold=0.2):
        current = new_data.describe()
        deviation = (current - self.baseline).abs() / self.baseline
        alerts = deviation[deviation > threshold].stack()
        return alerts[~alerts.index.isin([('count','')])]  # 排除样本量变化

# 使用示例
monitor = DataQualityMonitor(df_train)
alerts = monitor.check_deviation(df_new)
print(alerts.sort_values(ascending=False).head(3))

典型输出:

               metric
page_views   mean     0.35
             std      0.28
click_rate   max      0.42

4.2 统计量变化追踪面板

用Pandas的样式功能创建可视化监控:

def highlight_changes(val, baseline):
    threshold = 0.15
    diff = abs(val - baseline) / baseline
    color = 'red' if diff > threshold else 'black'
    return f'color: {color}'

current_stats = df_current.describe()
baseline_stats = df_baseline.describe()

current_stats.style.apply(lambda x: x.map(
    lambda val: highlight_changes(val, baseline_stats.loc[x.name])
))

效果说明:

  • 超过15%变化的统计量会显示为红色
  • 特别适合监控每日数据质量波动
  • 可扩展添加背景色渐变等效果

5. 实战中的经验法则

  1. 中位数与均值法则

    • (mean - median) > 0.1*std时,数据明显偏斜
    • 适合检查收入、价格等敏感指标
  2. 分位数间距警报

    • (Q3 - Q1) > 2*median 可能指示存在离群点
    • 常见于用户行为数据中的"超级用户"
  3. 零值检测技巧

    zeros = (df == 0).sum()
    zeros_pct = zeros / len(df)
    
    • 当零值占比与业务预期不符时(如交易数据0值过多)
  4. 量纲统一检查

    scales = df.describe().loc['max'] / df.describe().loc['mean']
    
    • 最大值与均值比>100时,考虑对数变换

6. 避免常见误判

  1. 样本量陷阱

    • 当count行显示样本量骤减时,先检查:
    df.isna().sum().sort_values(ascending=False)
    
    • 可能是合并数据时的连接问题
  2. 分类变量伪装

    • 数值型字段如果unique值少于20:
    df['suspected_cate'].value_counts()
    
    • 可能是误编码的分类变量
  3. 时间周期误导

    • 周数据与月数据统计量不可比:
    df.groupby(pd.Grouper(key='date', freq='M')).describe()
    
  4. 统计显著性幻觉

    • 小样本下的稳定std可能不可靠:
    def std_error(std, n):
        return std / np.sqrt(2*(n-1))
    

在真实项目中,我发现最有价值的往往不是那些完美的统计量,而是那些"不太合理"的数字。它们就像数据中的咳嗽声,提醒我们某些地方可能出了问题。最近一次分析用户年龄数据时,describe()显示的最大值120岁让我发现了注册表单中的年份选择器bug——有人把出生年份选成了1900年。这就是为什么我总把describe()作为数据清洗的第一步,而不是简单的统计汇总。

Logo

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

更多推荐