唉,说起用 Python怎么分组 这事儿,简直就是数据处理里的家常便饭,但真要玩转它,里头学问可不少。就像你把一堆乱七八糟的东西按类别归拢,有时候是按颜色,有时候是按大小,有时候得看生产日期……数据分组也一样,得看你为啥分,按啥分,分完想干嘛。它可不是个冰冷的命令,而是你理解数据、提炼信息,甚至发现惊喜的关键一步。
我记得刚开始学 Python 处理数据那会儿,遇到分组需求,脑子第一反应就是循环。一层一层for循环套进去,判断条件if else写得比火车皮还长,那叫一个笨拙、效率低下。跑个小数据还凑合,数据量一大,眼看着程序在那儿吭哧吭哧,电脑风扇呼呼转,心里那个焦躁啊,真是坐立不安。后来才知道,Python 早就为你准备好了更优雅、更高效的武器,尤其是在数据科学领域,pandas 这个库简直就是神一样的存在。
pandas 里的 groupby()
方法,就是解决 Python怎么分组 问题的核心利器。它不像你想的那么直接,不是说你一用 groupby()
数据就神奇地分成了一堆堆。不是的,groupby()
更像是一个“分组器”或者说一个“组织者”。它执行的是一个惰性操作,在你真正需要对分组后的数据进行计算(比如求和、平均、计数、找最大最小值等等)之前,它并不会真正地把数据复制成独立的小组。它只是帮你把原始数据按照你指定的键(key)“标记”好了,知道哪些行属于哪个组。这思路,多巧妙!省内存,效率高,处理大数据量的时候优势尤其明显。
那么,具体 Python怎么用pandas进行分组 呢?核心步骤就三板斧:分割(Split)、应用(Apply)、合并(Combine)。这就是著名的 Split-Apply-Combine
范式。
首先是 分割(Split)。你得告诉 groupby()
你想按哪一列或哪几列来分。比如你有一个销售订单的数据集,里面有“产品类别”、“地区”、“销售额”这些列。你想看看每个地区的总销售额,那你就得按“地区”列来分。代码可能就是 df.groupby('地区')
。或者你想看每个地区里,不同产品类别的销售情况,那就得按“地区”和“产品类别”两列来分,写成 df.groupby(['地区', '产品类别'])
。看,多简单,指定列名就行。如果你的分组键是DataFrame的索引,那就更直接了,可以指定 level
参数,或者如果索引本身就是你要分的那一列,直接 df.groupby(level=0)
(按第一个索引层级分)也行。分组键可以是列表、Series,甚至是一个函数!对,你没听错,一个函数!这个函数会作用于每一行的索引,然后根据函数的返回值来决定这一行属于哪个组。这个功能,用好了能玩出花来,特别灵活。比如你想按日期的年份来分组,即使你的日期列是精确到秒的时间戳,你也可以写一个lambda函数 lambda x: x.year
然后传给 groupby
。
分割完了,数据集就“逻辑上”被分成了若干个小组,每个小组包含原始数据中属于同一分组键值的所有行。这时候,这些小组还只是待处理的状态。
接着是 应用(Apply)。这是你真正干活儿的地方。你得告诉 Python,或者说告诉 pandas,对每个小组你想干嘛?是想计算每组的平均值?求和?找最大值?或者跑个更复杂的自定义函数?这就是 apply
这个词的含义——把某个操作或函数应用到每个小组上。
最常见的应用操作是使用聚合函数(Aggregation Functions)。pandas 提供了一堆开箱即用的聚合函数,比如 .sum()
、.mean()
、.count()
、.max()
、.min()
、.size()
、.std()
、.var()
等等。这些函数可以直接链在 groupby()
后面用。比如,想看每个地区的总销售额,那就是 df.groupby('地区')['销售额'].sum()
。注意这里的 ['销售额']
,这是在分组后选择了你想聚合的列。如果你不指定列,直接 df.groupby('地区').sum()
,pandas 会尝试对所有适合进行求和的数值列进行求和。想看每个地区有多少个订单,可以用 .size()
或 .count()
。.size()
计算的是每个组的行数(包含NaN),.count()
计算的是每个组非NaN值的数量。这俩有时候结果不一样,得注意。
除了这些内置的聚合函数,你还可以用 .agg()
方法,这方法更强大、更灵活。用 .agg()
,你可以一次性对同一列应用多个聚合函数,比如 df.groupby('地区')['销售额'].agg(['sum', 'mean', 'count'])
,这样一下子就能得到每个地区的总销售额、平均销售额和订单数。甚至,你可以对不同的列应用不同的聚合函数,这用 .agg()
传递一个字典就行,字典的键是列名,值是要应用的聚合函数(可以是字符串函数名,也可以是实际的函数对象)。比如 df.groupby('地区').agg({'销售额': 'sum', '订单ID': 'count', '日期': 'max'})
,看看每个地区总销售额、订单总数以及最新一笔订单的日期。是不是感觉门一下子就打开了?
更高级的应用是用 .apply()
方法。这个方法可以接受任何一个函数,只要这个函数能接收一个DataFrame(每个小组就是一个小的DataFrame)作为输入,并返回一个 pandas 对象(Series或DataFrame)或一个标量。这赋予了你无限的自由度,你可以写非常复杂的逻辑在这个函数里,对每个小组进行个性化处理。比如,你想找到每个地区销售额最高的那个订单的所有信息,光靠简单的聚合函数做不到,但用 .apply(lambda x: x.loc[x['销售额'].idxmax()])
就能实现。这代码的意思是,对每个小组 x
,找到它里面 ‘销售额’ 列的最大值对应的索引 idxmax()
,然后用 loc
根据这个索引把整行数据取出来。这例子,看着就有点高阶了,但它展示了 apply
的强大之处。不过 apply
通常比内置聚合或 .agg()
要慢,因为它没有内置优化,所以非必要不滥用,能用内置的或 .agg()
解决的,尽量用它们。
最后一步是 合并(Combine)。在应用操作完成后,pandas 会把每个小组的处理结果合并起来,形成一个新的DataFrame或Series,这个新的数据结构通常会以你的分组键作为索引。比如 df.groupby('地区')['销售额'].sum()
的结果,就是一个Series,索引是地区名,值是对应地区的总销售额。df.groupby(['地区', '产品类别'])['销售额'].mean()
的结果,就是一个MultiIndex Series,索引是地区和产品类别的组合,值是对应的平均销售额。而使用 .agg()
或 .apply()
返回DataFrame时,结果也是一个DataFrame,同样以分组键作为索引。这个合并的过程是自动的,你通常不需要额外写代码。
掌握了 Split-Apply-Combine
范式和 pandas 的 groupby()
方法,基本上就抓住了 Python怎么分组 的精髓。但在实际应用中,还有一些细节和技巧需要注意。
比如处理缺失值(NaN)。默认情况下,pandas 的聚合函数会忽略 NaN 值。如果你想包含它们,或者有特定的处理方式,可能需要在分组前或分组后进行填充或删除。
再比如,分组后的索引问题。默认情况下,分组键会成为结果DataFrame或Series的索引。有时候你可能不想要这个,想让分组键变回普通的列。这时候可以在 groupby()
后面加上 as_index=False
参数,比如 df.groupby('地区', as_index=False)['销售额'].sum()
。这样结果的索引就会是默认的数字索引,而“地区”会作为一列出现在结果里。这个小技巧,有时候挺实用的。
还有性能考虑。对于超大规模的数据,直接在内存里用 pandas 的 groupby
可能会遇到内存瓶颈。这时候,可能就需要考虑更专业的大数据处理框架,比如 PySpark 或者 Dask。不过对于绝大多数日常的数据分析任务,pandas 的 groupby
已经足够强大和高效了。
除了基本的数值聚合,pandas 分组还能干很多有意思的事儿。比如,用 .filter()
根据组的属性来筛选整个组。比如,只想保留那些总销售额大于某个阈值的地区的订单数据。df.groupby('地区').filter(lambda x: x['销售额'].sum() > 10000)
这行代码的意思是,对每个地区的小组 x
,计算它的总销售额,如果总销售额大于10000,就把这个小组 所有 的原始数据行都保留下来。注意,filter
返回的是一个DataFrame,保留了原始数据的行,这跟聚合返回聚合结果是不一样的。
再比如,用 .transform()
方法。这个方法也挺神奇的。它对每个小组应用一个函数,但是返回的结果的形状(索引和行数)跟原始DataFrame是一样的。这意味着 transform
的结果可以直接加回原始DataFrame作为新的一列。一个经典的例子是计算每个销售额占其所在地区总销售额的比例。你可以先按地区分组,然后对每个小组的销售额除以该小组销售额的总和。df['地区销售额占比'] = df.groupby('地区')['销售额'].transform(lambda x: x / x.sum())
。这里的 lambda 函数 lambda x: x / x.sum()
对每个小组 x
的 ‘销售额’ 列进行操作,用该小组的每个销售额除以该小组的总销售额 x.sum()
。transform
保证了返回结果的索引和原始DataFrame的索引一致,所以可以直接赋值给新列。这操作,是不是很漂亮?
总之,Python怎么分组,尤其是利用 pandas 进行分组,远不止一个简单的命令。它是一种思考数据的方式,一套解决问题的范式。从最简单的按一列求和,到按多列进行复杂聚合,再到用 apply
、filter
、transform
进行更灵活的操作,每一步都体现了数据处理的智慧。当你面对一堆数据,不知从何下手时,不妨想想,“我能不能先把它们按某个特征分组?”,很多问题的答案,也许就在分组之后显现出来。它能帮你看到数据的整体面貌,也能帮你深入到每个细节,发现隐藏在数据背后的规律。所以,别小看这个“分组”操作,玩熟了,你就是数据的魔术师!
评论(0)