Python案例如何用Pandas做数据分组排序

wen python案例 1

一文精通Python Pandas数据分组排序:从入门到实战案例

目录导读


引言:为什么你需要掌握Pandas分组排序?

在日常数据分析工作中,你大概率遇到过这样的场景:需要统计每个部门的销售额排名,或者计算每个用户在平台上的消费次数排序,如果没有掌握分组加排序的复合操作,你可能需要写多行循环代码,效率极低且容易出错。

Python案例如何用Pandas做数据分组排序

分组排序(GroupBy + Sort)是Pandas库中最实用的复合技能之一,它允许你:

  • 按某一列(如“城市”)分组
  • 在每个组内,按另一列(如“销售额”)进行排序
  • 甚至可以为每个组的数据打上“组内排名”

本文将从一个完整的Python案例出发,结合搜索引擎中常见的痛点问题(如“为什么我排序后组内顺序还是乱的?”“如何保留Top 3数据?”),带你彻底掌握这一技能。

问答:Pandas的分组排序与Excel的排序有什么本质区别?

  • Excel:需要先排序整个表,然后手动按分组筛选,操作繁琐且无法自动化。
  • Pandas:通过groupby()sort_values()组合,可将分组逻辑与排序逻辑完全编程化,支持大数据量(百万行级)实时处理。

第一部分:Pandas数据分组核心函数groupby详解

1 基础结构

Pandas的groupby操作遵循“分割-应用-合并”(Split-Apply-Combine)三阶段:

  1. 分割:根据指定的列(或列组合),将DataFrame按值拆分为多个子集。
  2. 应用:对每个子集独立执行某种操作(如求和、求均值、排序等)。
  3. 合并:将处理后的结果合并为新的DataFrame。

2 典型分组场景

import pandas as pd
# 创建示例数据
df = pd.DataFrame({
    '部门': ['销售A', '销售B', '销售A', '销售B', '销售A', '销售B'],
    '姓名': ['张三', '李四', '王五', '赵六', '陈七', '周八'],
    '销售额': [50000, 60000, 48000, 55000, 62000, 59000]
})

代码演示

# 按部门分组并计算总销售额
grouped_total = df.groupby('部门')['销售额'].sum().reset_index()
print(grouped_total)

输出

    部门     销售额
0  销售A  160000
1  销售B  174000

问答:groupby之后的结果为什么有时候是Series,有时候是DataFrame?

  • groupby后只选一列(如['销售额'])且聚合函数返回标量值时,返回的是Series;若要保留列名结构,需用reset_index()或传入列名为列表形式(如[['销售额']])。

第二部分:排序操作:从单列到多级排序

1 单列排序:sort_values()

语法df.sort_values(by='列名', ascending=True)

  • ascending=True表示升序(默认),False表示降序。
df_sorted = df.sort_values(by='销售额', ascending=False)
print(df_sorted)

输出

    部门  姓名   销售额
4  销售A  陈七  62000
1  销售B  李四  60000
5  销售B  周八  59000
3  销售B  赵六  55000
0  销售A  张三  50000
2  销售A  王五  48000

2 多级排序:多列组合排序

当一列有重复值时,可通过多列控制排序优先级:

df_multi_sort = df.sort_values(by=['部门', '销售额'], ascending=[True, False])
print(df_multi_sort)

输出

    部门  姓名   销售额
4  销售A  陈七  62000
0  销售A  张三  50000
2  销售A  王五  48000
1  销售B  李四  60000
5  销售B  周八  59000
3  销售B  赵六  55000

问答:sort_values和sorted函数有什么区别?

  • sorted()是Python内置函数,只能处理列表、元组等基础容器;sort_values()是Pandas DataFrame的方法,支持缺失值处理、多级排序、inplace原址修改等高级功能。

第三部分:经典案例实战:销售数据分组排名

1 场景描述

某电商公司有月度订单数据,需要:

  1. 按“区域”分组
  2. 在每个区域内按“销售额”降序排序
  3. 为每个区域内的数据添加“排名”列(排名从1开始)
  4. 最后只保留每个区域排名前3的记录

2 数据准备

# 生成模拟数据
import numpy as np
np.random.seed(42)
sales_data = pd.DataFrame({
    '区域': np.random.choice(['华东', '华南', '华北', '西南'], size=20),
    '产品': ['产品A', '产品B', '产品C', '产品D', '产品E'] * 4,
    '销售额': np.random.randint(5000, 20000, size=20),
    '月份': np.random.choice(['1月', '2月', '3月'], size=20)
})

3 逐步实现分组排序

核心代码

# 第一步:按区域分组并对每组内部按销售额降序排序
grouped_sorted = sales_data.sort_values(
    by=['区域', '销售额'], 
    ascending=[True, False]
).groupby('区域').apply(lambda x: x.reset_index(drop=True))
# 第二步:在每个组内添加排名列
grouped_sorted['排名'] = grouped_sorted.groupby('区域').cumcount() + 1
# 第三步:筛选每个区域排名前三的记录
top3_per_region = grouped_sorted[grouped_sorted['排名'] <= 3]

查看结果

print(top3_per_region)

输出示例(因随机数据,每次结果不同):

        区域    产品    销售额  月份  排名
区域                                    
华东  0   华东  产品A  18000  3月   1
     1   华东  产品B  15000  1月   2
     2   华东  产品D  12000  2月   3
华南  0   华南  产品C  19000  1月   1
     1   华南  产品E  16500  2月   2
     2   华南  产品A  14000  3月   3
...

4 更简洁的替代方案:使用rank()函数

如果不需要手动排序,可直接用groupby + rank()生成排名:

sales_data['全局排名'] = sales_data.groupby('区域')['销售额'].rank(
    method='dense', ascending=False
)
top3_rank = sales_data[sales_data['全局排名'] <= 3]

问答:rank()函数的method='dense''min'有何不同?

  • method='min':相同值并列排名时,取最小排名(如两个第2名,则下一个是第4名);dense:并列时排名连续(如两个第2名,下一个仍第3名),业务排行常用dense

第四部分:高频问答区(FAQ)

Q1:分组排序后,为什么我的索引还是乱序?

原因groupby操作默认不会重置索引。
解决方法:在apply内部使用reset_index(drop=True),确保每个子组的索引从0重新开始。

Q2:数据有几百万行,分组排序太慢怎么办?

优化策略

  • 使用sort_valueskind='mergesort'参数(稳定排序,对大数据友好)
  • 先对分组列建立索引:df.set_index('分组列', inplace=True),再使用groupby+sort_index组合
  • 考虑使用dask.dataframemodin进行分布式加速

Q3:如何对分组后的数据进行多列排名(如按销售额降序,再按日期升序)?

# 自定义排名逻辑
def custom_rank(group):
    return group.sort_values(
        by=['销售额', '日期'], 
        ascending=[False, True]
    ).assign(自定义排名=range(1, len(group)+1))
result = df.groupby('区域', group_keys=False).apply(custom_rank)

Q4:分组排序后,如何获取每个组的最大值/最小值记录?

# 获取每个区域销售额最高的记录
max_by_group = df.loc[df.groupby('区域')['销售额'].idxmax()]

Q5:非数值列能参与排序吗?

可以,字符串列按字母顺序排序,日期列按时间顺序排序,若需自定义顺序(如固定“大-中-小”),需先转换为Categorical类型。


第五部分:性能优化与最佳实践

1 避免在循环中使用groupby

错误示例:

for region in df['区域'].unique():
    subset = df[df['区域'] == region]
    subset.sort_values(...)

推荐写法:一次性对整个DataFrame操作,利用groupby的向量化特性。

2 使用pipe()链式调用提升可读性

def add_ranking(df):
    return df.assign(
        排名=df.groupby('区域')['销售额'].rank(method='dense', ascending=False)
    )
def filter_top3(df):
    return df[df['排名'] <= 3]
result = (
    sales_data
    .pipe(add_ranking)
    .pipe(filter_top3)
    .sort_values(['区域', '排名'])
)

3 内存优化技巧

当使用groupby().apply()时,若apply函数返回整个DataFrame,Pandas会创建大量中间对象,建议:

  • 改用transform(返回与原DataFrame相同形状的结果)避免数据膨胀
  • 或使用groupby().agg()聚合到较小的结果

掌握分组排序的思维模型

通过本文的案例,你应该已经理解:分组排序的本质是“先聚拢同类,再内部排序”,这个思维不仅适用于Pandas,也适用于SQL的PARTITION BY ORDER BY、R语言的dplyr::group_by + arrange等工具。

核心口诀

  1. groupby划分“领地”
  2. sort_valuesrank明确“内部秩序”
  3. .apply.transform执行自定义逻辑
  4. 最后用筛选器(如[df['排名']<=3])提取目标数据

最后送你一个检测理解的小测验:
假设你有用户行为日志(user_id, event_time, event_type),如何找到每个用户最新的3条行为记录?
(提示:按user_id分组,然后按event_time降序排序,再取组内前3行)

如果你能用Pandas写出这个逻辑,恭喜你,已经彻底掌握了分组排序的精髓!

抱歉,评论功能暂时关闭!