Pandas统计函数全解析:从describe到cumsum的数据探索实战
2026/8/3 8:55:18 网站建设 项目流程

1. 项目概述:从数据到洞察,Pandas统计函数全解析

刚接触数据分析那会儿,我最头疼的就是面对一堆原始数据,不知道从何下手。数据长什么样?有没有异常值?整体趋势如何?这些问题不搞清楚,后续的建模和可视化都是空中楼阁。后来,我发现了Pandas这个宝藏库,尤其是它那一系列开箱即用的统计函数,简直就是数据分析师的“瑞士军刀”。今天,我们就来彻底盘一盘Pandas里那些最常用、也最核心的统计分析方法,包括describequantilesummeanmediancountmaxminidxmaxidxminmadvarstdcumsum。别被这一长串名字吓到,它们其实各有分工,组合起来能帮你快速完成数据探索、清洗和特征工程的绝大部分工作。无论你是想快速了解一份销售数据的概貌,还是需要计算用户行为的波动性,或者想找出增长最快的时段,这些函数都能派上用场。这篇文章,我会结合我处理电商、金融时间序列等数据的实际经验,带你搞懂每个函数的“脾气”,以及它们组合使用的“套路”,让你告别对着一堆数字发呆的尴尬。

2. 核心统计函数全景图与设计思路

在深入每个函数之前,我们得先有个大局观。Pandas的统计函数不是孤立存在的,它们服务于数据分析的不同阶段和不同目的。我们可以大致把它们分为四类:描述性统计集中趋势度量离散程度度量累积计算。这个分类思路,直接决定了你拿到数据后的分析动线。

描述性统计,比如describe,是你的“第一眼”工具。它不追求深度,但求广度,目的是用最少的输出让你对数据的分布、量级和异常情况有一个快速的、整体的把握。我习惯在读取任何新数据集后,第一时间对关键列或整个DataFrame调用describe(),它能立刻告诉我数据有没有缺失、数值范围是否合理、是否存在极端大或极端小的值(通过最大值、最小值和分位数判断)。这步操作,相当于给数据做了一次快速的“体检”。

集中趋势度量,包括mean(均值)、median(中位数)、sum(总和)和quantile(分位数),回答的是“数据围绕哪个中心点聚集”的问题。mean是我们最熟悉的平均值,但它对异常值非常敏感。比如计算一个小区居民的平均收入,如果搬进来一位亿万富翁,均值会被瞬间拉高,失去代表性。这时median(中位数,即排序后位于中间的值)就更稳健。sum很简单,就是加总,常用于计算总量,如总销售额、总用户数。quantile则更灵活,它可以告诉我们“处于前25%的数据是多少”(0.25分位数),“中位数是多少”(0.5分位数),是分析数据分布形态的利器。

离散程度度量,包括std(标准差)、var(方差)、mad(平均绝对偏差)以及maxmin本身也能反映数据的范围。它们回答的是“数据有多分散”的问题。stdvar是亲兄弟,方差是标准差的平方,它们衡量数据点偏离均值的平均距离,是金融领域衡量风险、量化领域衡量稳定性的核心指标。mad是另一种衡量离散度的方法,它计算的是数据点与中位数绝对偏差的平均值,对异常值的敏感度比标准差低一些。

累积计算,主要是cumsum(累积和),它用于观察序列的累积效应,比如观察月度销售额的逐年累计情况,或者用户留存率的累积曲线,在时间序列分析和趋势预测中非常有用。

idxmaxidxmin这两个函数比较特殊,它们不直接给出统计值,而是给出达到最大值或最小值的索引标签。这在数据分析中至关重要,因为知道“最大值是多少”往往不如知道“最大值出现在哪里”更有价值。比如,找到销售额最高的那一天,或者找到用户流失率最高的那个渠道。

注意:Pandas的统计函数默认会自动跳过NaN(非数字)值进行计算,这是一个非常贴心且重要的设计。但在某些需要严格处理缺失值的场景下,你需要通过skipna参数来控制这一行为。

理解了这套分类逻辑,你在实际调用函数时就不会盲目,而是带着明确的目的去选择工具。接下来,我们就进入实战环节,看看这些函数具体怎么用,以及有哪些坑需要避开。

2.1 环境准备与数据构造

工欲善其事,必先利其器。在开始之前,确保你的Python环境已经安装了Pandas。通常使用pip install pandas即可完成安装。为了演示的完整性,我们构造一个包含多种情况的小型数据集,模拟一份简单的销售数据。

import pandas as pd import numpy as np # 设置随机种子以保证结果可复现 np.random.seed(42) # 构造一个DataFrame data = { ‘日期‘: pd.date_range(‘2023-01-01‘, periods=10, freq=‘D‘), ‘产品A销量‘: [120, 135, None, 118, 145, 160, 155, 142, 130, 138], # 包含一个缺失值 ‘产品B销量‘: [85, 92, 88, 150, 78, 82, 95, 103, 110, 98], # 包含一个可能的异常值150 ‘单价A‘: [10.5, 10.5, 10.5, 10.0, 10.0, 11.0, 11.0, 11.0, 10.8, 10.8], ‘单价B‘: [8.0, 8.0, 8.0, 8.0, 8.0, 8.5, 8.5, 8.5, 8.3, 8.3] } df = pd.DataFrame(data) print(“构造的原始数据:“) print(df) print(“\n数据类型:“) print(df.dtypes)

这段代码创建了一个10行5列的DataFrame。其中“产品A销量”第三行是NaN(缺失值),“产品B销量”第四行有一个相对较高的值150,模拟一个可能的异常值或促销日的销量。这样的数据能让我们观察到各种统计函数在遇到缺失值、异常值时的表现。

3. 描述性统计:describe与quantile的深度应用

describe函数是你的数据侦察兵。默认情况下,它对数值型列(int和float)进行统计,并返回八个关键指标:计数(count)、均值(mean)、标准差(std)、最小值(min)、下四分位数(25%)、中位数(50%)、上四分位数(75%)和最大值(max)。

print(“对数值列进行描述性统计:“) desc = df.describe() print(desc)

运行后,你会立刻得到一张清晰的统计表。以“产品A销量”为例,count是9,说明它有一个缺失值(因为总共有10行)。meanstd给出了平均销量和波动情况。通过观察min(118)和max(160),以及25%(130)、50%(138)、75%(145)这些分位数,你可以快速感知数据的分布:销量大致集中在130-145之间,最大值160可能是个小高峰。

describeincludeexclude参数非常有用。如果你想包含所有类型的列(比如对象类型的字符串列),可以使用include=‘all‘。这时,对于非数值列,它会返回唯一值数量、最高频值及其出现次数等统计信息。

# 假设我们增加一个‘产品类别‘的字符串列 df[‘产品类别‘] = [‘电子‘, ‘电子‘, ‘家居‘, ‘电子‘, ‘家居‘, ‘电子‘, ‘电子‘, ‘家居‘, ‘电子‘, ‘电子‘] print(“\n包含所有类型列的描述性统计:“) print(df.describe(include=‘all‘))

现在我们来深入它的核心组件之一:quantiledescribe里的25%、50%、75%就是通过quantile计算出来的。但quantile的能力远不止于此,它可以计算任意分位数。

print(“\n计算产品A销量的特定分位数:“) print(df[‘产品A销量‘].quantile([0.1, 0.25, 0.5, 0.75, 0.9]))

假设你想找出“产品A销量”最差的10%的数据点所对应的阈值,那么0.1分位数就是这个值。在风控领域,我们常用0.99或0.995分位数来定义极端异常值的阈值。

实操心得:describe的输出是一个DataFrame,这意味著你可以像操作普通DataFrame一样去切片、选择它。例如,desc.loc[‘mean‘, ‘产品A销量‘]就能直接取出产品A的平均销量。这个特性在需要自动化提取统计指标进行后续计算的脚本中非常好用。

分位数的插值方法:这是quantile函数一个容易忽略但至关重要的参数interpolation。当所需的分位数位置处于两个数据点之间时,如何取值?默认是linear。假设有数据[1, 3, 5, 7],计算0.25分位数(即第1.75个位置)。linear插值会在第一个数(1)和第二个数(3)之间按比例计算:1 + (3-1)*0.75 = 2.5。其他方法如lowerhighernearestmidpoint则分别取较低值、较高值、最近值或中点值。在金融领域计算VaR(风险价值)时,对插值方法的选择需要非常谨慎,因为它会直接影响风险计量结果。对于大多数业务分析,使用默认的linear即可。

4. 集中趋势与位置度量:mean, median, sum, idxmax/min

集中趋势指标是我们做汇报、看大盘时最常引用的数字。但用对地方是关键。

均值(mean)与中位数(median)的抉择

print(“产品B销量的均值与中位数:“) print(f“均值: {df[‘产品B销量‘].mean():.2f}“) print(f“中位数: {df[‘产品B销量‘].median():.2f}“)

你会发现产品B销量的均值(约97.1)明显高于中位数(90.0)。这是因为那个150的“异常值”把均值拉高了。这个简单的对比立刻揭示了数据分布是右偏的(存在少数大值)。在汇报整体销售水平时,如果使用均值,可能会给管理者造成“销量普遍接近100”的错觉,而中位数90更能代表大多数日期的销售情况。我的经验法则是:对于收入、房价、用户消费金额等通常包含极端大值的数据,报告中优先使用中位数;对于误差、温度、测试得分等通常对称分布的数据,使用均值更合适。

总和(sum)及其衍生计算: 总和计算看似简单,但结合其他操作能解决很多问题。比如计算总销售额:

df[‘销售额A‘] = df[‘产品A销量‘] * df[‘单价A‘] df[‘销售额B‘] = df[‘产品B销量‘] * df[‘单价B‘] print(f“产品A总销售额: {df[‘销售额A‘].sum():.2f}“) print(f“产品B总销售额: {df[‘销售额B‘].sum():.2f}“)

注意,由于产品A销量有缺失值(NaN),任何包含NaN的算术运算结果都是NaN。因此“销售额A”列也会有一个NaN。sum()函数默认会跳过这个NaN,只对有效的9行数据进行加总。这是一个需要留意的点:缺失值在计算中会被自动排除,这有时会导致总和与“均值*理论行数”的结果对不上。

位置索引查找器:idxmax与idxmin这两个函数的价值在于建立统计值与原始数据的联系。

max_sales_date = df[‘销售额B‘].idxmax() min_sales_date = df[‘销售额B‘].idxmin() print(f“产品B销售额最高的日期是: {df.loc[max_sales_date, ‘日期‘]}“) print(f“产品B销售额最低的日期是: {df.loc[min_sales_date, ‘日期‘]}“) print(f“该日期的详细数据:\n{df.loc[max_sales_date]}“)

idxmax()返回的是“销售额B”这一列中最大值所对应的索引标签。在我们的数据里,索引是默认的整数序列(0-9),但更常见的情况是索引为日期或ID。通过这个索引,我们可以用df.loc[]轻松定位到该行,查看那一天的完整情况(比如是不是周末、有没有促销活动),从而进行归因分析。这是单纯看一个最大值数字无法获得的信息深度。

踩坑记录:如果最大值或最小值有多个(即存在并列情况),idxmax()idxmin()默认只返回第一个出现的位置。这在某些需要找出所有极值点的场景下是不够的。解决方案是:df[df[‘销售额B‘] == df[‘销售额B‘].max()].index.tolist(),这样可以获取所有最大值对应的索引列表。

5. 离散程度与波动性度量:std, var, mad, range

离散程度指标帮助你理解数据的稳定性和风险。我们以“产品B销量”为例,它有一个疑似异常值150。

方差(var)与标准差(std): 方差是每个数据点与均值之差的平方的平均数。标准差是方差的平方根,它的单位和原始数据一致,因此更常被解释。

var_b = df[‘产品B销量‘].var() std_b = df[‘产品B销量‘].std() print(f“产品B销量方差: {var_b:.2f}“) print(f“产品B销量标准差: {std_b:.2f}“)

标准差约等于22.5。如何理解这个数字?我们可以粗略地使用“经验法则”(对于近似正态分布的数据):约有68%的数据落在均值±1个标准差范围内(97.1 ± 22.5),约有95%的数据落在均值±2个标准差范围内。对于产品B,均值+2倍标准差是142.1,而我们的数据中出现了150,这提示150可能是一个需要关注的异常点。在质量控制中,标准差是计算过程能力指数(Cp, Cpk)的基础;在金融中,标准差直接度量了资产价格的波动性(风险)。

平均绝对偏差(mad): MAD计算的是数据点与其中位数(默认)或均值绝对偏差的平均值。与方差/标准差相比,它对异常值的敏感度更低,因为不用平方。

mad_b_median = df[‘产品B销量‘].mad() # 默认相对于中位数 mad_b_mean = df[‘产品B销量‘].mad(skipna=True) # Pandas的mad已不推荐使用‘center‘参数,通常指对中位数。如需对均值,可手动计算。 # 手动计算相对于均值的MAD manual_mad_mean = (df[‘产品B销量‘] - df[‘产品B销量‘].mean()).abs().mean() print(f“相对于中位数的MAD: {mad_b_median:.2f}“) print(f“相对于均值的MAD(手动计算): {manual_mad_mean:.2f}“)

由于MAD对异常值不敏感,它的值(相对于中位数约12.9)比标准差(22.5)小很多。这进一步印证了150这个点远离数据主体。在需要稳健统计的场合,例如评估传感器读数的一致性时,MAD是比标准差更好的选择。

极差(range): 极差就是最大值与最小值之差,是最简单的离散度度量。

range_b = df[‘产品B销量‘].max() - df[‘产品B销量‘].min() print(f“产品B销量极差: {range_b}“)

极差是72(150-78)。它的计算简单,但缺点也很明显:完全由两个极端值决定,容易受异常值影响,且无法反映中间数据的分布情况。通常只作为辅助参考。

6. 累积分析与趋势观察:cumsum的应用

累积和cumsum是分析时间序列或任何有序数据趋势的利器。它能将一系列增量数据转化为累积总量,让我们直观看到增长过程。

df[‘累计销售额B‘] = df[‘销售额B‘].cumsum() print(“每日销售额B及其累计值:“) print(df[[‘日期‘, ‘销售额B‘, ‘累计销售额B‘]])

输出结果会显示,第二列的“累计销售额B”每一行都是当前行及之前所有行“销售额B”的总和。这个简单的变换,能立刻回答很多业务问题:

  1. 业绩完成度:到当前日期,累计销售额完成了月度/季度目标的百分之多少?
  2. 增长趋势:绘制累计销售额曲线,其斜率就代表了销售速度。如果曲线越来越陡峭,说明增速在加快;如果变平缓,则增速下降。
  3. 里程碑定位:累计销售额何时突破某个关键门槛(如10万)?用df[df[‘累计销售额B‘] > 100000].index[0]就能快速找到。

cumsum还可以和其他函数结合。例如,计算滚动累计平均值:df[‘销售额B‘].expanding().mean()。或者,你想分析累计销售额达到总量一半的时间点:

total_sales = df[‘销售额B‘].sum() halfway_point = total_sales / 2 # 找出累计销售额首次超过一半的日期 halfway_date = df[df[‘累计销售额B‘] >= halfway_point].iloc[0][‘日期‘] print(f“总销售额的一半是: {halfway_point:.2f}“) print(f“达到一半销售额的日期是: {halfway_date}“)

注意事项:cumsum(以及类似的cumprod累积积、cummax累积最大值)在遇到缺失值(NaN)时,从该缺失值开始,后续的所有累积结果都会变成NaN。这是因为NaN具有传染性。在计算累积值前,务必处理好缺失值,通常可以用前向填充(.fillna(method=‘ffill‘))或插值法来填补。

7. 综合实战:一份完整的数据探索性分析(EDA)报告

现在,让我们把这些函数串起来,模拟一个真实的数据探索性分析场景。假设你拿到一份新的销售数据df,你的任务是在5分钟内给出核心洞察。

第一步:整体概览与数据质量检查

print(“=== 1. 数据整体概览 ===“) print(f“数据形状: {df.shape}“) # 查看行数列数 print(“\n前5行数据:“) print(df.head()) print(“\n数据类型与缺失情况:“) print(df.info()) print(“\n快速描述性统计:“) print(df.describe(include=‘all‘))

这一步用shapeheadinfodescribe快速了解数据规模、预览内容、检查每列数据类型和缺失值数量。describe会直接告诉你数值列的统计特征。

第二步:关键指标的中心与离散趋势

print(“\n=== 2. 核心指标分析 ===") # 选择核心数值列进行分析 core_columns = [‘产品A销量‘, ‘产品B销量‘, ‘销售额A‘, ‘销售额B‘] for col in core_columns: if pd.api.types.is_numeric_dtype(df[col]): print(f“\n--- {col} ---“) print(f“ 有效样本数: {df[col].count()}“) print(f“ 总和: {df[col].sum():.2f}“) print(f“ 均值: {df[col].mean():.2f} | 中位数: {df[col].median():.2f}“) print(f“ 标准差: {df[col].std():.2f} | 变异系数(CV): {df[col].std()/df[col].mean():.2%}“) print(f“ 最小值: {df[col].min():.2f} (位置: {df[col].idxmin()})“) print(f“ 最大值: {df[col].max():.2f} (位置: {df[col].idxmax()})“) # 判断是否有潜在异常值:使用IQR法则 Q1 = df[col].quantile(0.25) Q3 = df[col].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers = df[(df[col] < lower_bound) | (df[col] > upper_bound)][col] if not outliers.empty: print(f“ ! 发现潜在异常值(IQR法则): {outliers.tolist()}“)

这里我们不仅计算了基本的meanmedianstd,还引入了变异系数(CV),即标准差除以均值,它是一个无量纲的相对离散度指标,便于比较不同量级数据的波动性。同时,我们实现了简单的异常值检测(IQR法则),自动找出并提示可能的问题数据点。

第三步:趋势与累积分析

print(“\n=== 3. 趋势与累积分析 ===“) # 计算累计销售额 df[‘累计总销售额‘] = (df[‘销售额A‘].fillna(0) + df[‘销售额B‘]).cumsum() # 找到累计销售额增长最快的时段(日环比) df[‘日销售额‘] = df[‘销售额A‘].fillna(0) + df[‘销售额B‘] df[‘日环比增长‘] = df[‘日销售额‘].pct_change() * 100 max_growth_day = df[‘日环比增长‘].idxmax(skipna=True) print(f“累计总销售额最终值: {df[‘累计总销售额‘].iloc[-1]:.2f}“) print(f“销售额日环比增长最高的一天是第{max_growth_day}天,增长率为{df.loc[max_growth_day, ‘日环比增长‘]:.1f}%“)

这一步结合了cumsumpct_change(百分比变化,非本次主题但很常用),分析了累积趋势和增长动能,找到了增长爆发点。

第四步:生成简易文本报告

print(“\n=== 4. 初步洞察总结 ===“) print(f“1. 数据质量: 共{df.shape[0]}行数据。‘产品A销量‘存在{df[‘产品A销量‘].isna().sum()}个缺失值。“) print(f“2. 销售表现: 产品B总销售额({df[‘销售额B‘].sum():.0f})高于产品A({df[‘销售额A‘].sum():.0f})。“) print(f“3. 稳定性: 产品B销量的波动性(CV={df[‘产品B销量‘].std()/df[‘产品B销量‘].mean():.1%})较高,需关注异常高值(150)。“) print(f“4. 增长趋势: 销售额在第{max_growth_day}天增长最快({df.loc[max_growth_day, ‘日环比增长‘]:.1f}%),值得深入分析该日情况。“)

通过这样一套组合拳,你就能在极短时间内,从一份原始数据中提炼出数据质量、业务表现、稳定性和趋势性四个维度的核心洞察,为后续的深度分析或决策提供扎实的起点。

8. 常见问题与性能优化技巧实录

在实际使用中,你肯定会遇到一些疑问和性能瓶颈。这里我总结几个最常见的问题和解决办法。

问题一:统计结果出现NaN,或者和预期不符。这几乎总是由缺失值(NaN)引起的。首先要明确Pandas的统计函数默认skipna=True,即跳过NaN计算。但某些操作会产生NaN:

  • 源头有NaN:任何包含NaN的算术运算(如df[‘A‘] + df[‘B‘]),结果位置仍是NaN。
  • 空序列或全NaN序列:对全为NaN的列求mean,结果为NaN。
  • 解决方案
    1. 检查数据:df.isna().sum()查看每列缺失情况。
    2. 决定处理方式:删除df.dropna()、填充df.fillna(value)或使用特定方法填充df.fillna(method=‘ffill‘)
    3. 明确计算意图:如果希望将NaN视为0参与求和,使用df[‘col‘].fillna(0).sum()

问题二:对非数值列(如字符串、日期)调用mean()std()等函数报错。这些数学函数只能用于数值类型(int, float)。对于非数值列,Pandas会抛出TypeError

  • 解决方案
    1. 选择性统计:使用df.describe(include=[‘number‘])df.describe(exclude=[‘object‘])
    2. 类型转换:如果字符串列存储的是可转换为数字的数据(如‘123‘),使用pd.to_numeric(df[‘col‘], errors=‘coerce‘)进行转换,无效的转换会变成NaN。
    3. 使用针对特定类型的描述:对于日期列,可以计算df[‘date_col‘].min(),df[‘date_col‘].max(),df[‘date_col‘].nunique()等。

问题三:数据量巨大时,describe()或循环计算多个统计量速度很慢。对于百万行以上的大数据集,频繁调用单个统计函数或使用describe可能会成为瓶颈。

  • 性能优化技巧
    1. 批量计算df.agg([‘mean‘, ‘std‘, ‘count‘])可以一次性对每列计算多个统计量,比分别调用mean()std()效率更高。
    2. 指定数值列:如果DataFrame有很多列,但只关心其中几列,使用df[[‘col1‘, ‘col2‘]].describe()而不是对整个df操作。
    3. 使用Numpy:对于超大规模单列数据,将其转换为NumPy数组再计算有时更快,例如np.mean(df[‘col‘].values)。但要注意,这会将缺失值NaN也纳入计算(NumPy的np.mean会返回nan),需先处理缺失值。
    4. 分块处理:对于内存无法容纳的数据,可以考虑使用Dask库或Pandas的chunksize参数进行分块读取和计算。

问题四:如何对分组数据(GroupBy)应用这些统计函数?这是Pandas最强大的功能之一。分组统计是业务分析的常态,例如按地区、按产品类别统计。

# 假设我们按‘产品类别‘分组 grouped = df.groupby(‘产品类别‘) print(“按产品类别分组统计销售额:“) print(grouped[‘销售额B‘].agg([‘sum‘, ‘mean‘, ‘count‘, ‘std‘]))

groupbyagg(聚合)的组合,可以让你一次性得到每个分组的多种统计指标,输出一个清晰的多级索引DataFrame,非常适合制作汇总报表。

问题五:idxmax/idxmin返回的索引不直观,如何看到对应的其他列信息?这是初学者常问的。idxmax只返回索引,你需要用这个索引去定位原数据。

max_idx = df[‘销售额B‘].idxmax() # 方法1: 使用loc定位单行 print(df.loc[max_idx]) # 方法2: 如果想看到特定几列 print(df.loc[max_idx, [‘日期‘, ‘产品B销量‘, ‘单价B‘]]) # 方法3: 更直接的方法,使用条件筛选 print(df[df[‘销售额B‘] == df[‘销售额B‘].max()])

方法三虽然代码稍长,但逻辑更清晰,特别是当存在多个最大值时,它能返回所有符合条件的行。

掌握这些函数和技巧,你就能从容应对日常数据分析中80%的统计需求。记住,工具是死的,业务是活的。永远先问自己:“我计算这个指标是为了回答什么业务问题?” 带着问题去选择工具,你的分析才会有的放矢,直击要害。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询