Pandas分组聚合详解

数据分析过程中,最常见的一个场景莫过于分组统计了,这个也是学习 Pandas 的一个核心功能。我们一起来看一看吧。

目录
1. 将对象分割成组
1.1 关闭排序
1.2 选择列
1.3 遍历分组
1.4 选择一个组
2. 聚合
2.1 一次应用多个聚合操作

2.2 对DataFrame列应用不同的聚合操作
3. transform 操作
4. apply 操作

更多:Pandas

# 导入相关库
import numpy as np
import pandas as pd
index = pd.Index(data=["Tom", "Bob", "Mary", "James", "Andy", "Alice"], name="name")

data = {
    "age": [18, 30, 35, 18, np.nan, 30],
    "city": ["Bei Jing ", "Shang Hai ", "Guang Zhou", "Shen Zhen", np.nan, " "],
    "sex": ["male", "male", "female", "male", np.nan, "female"],
    "income": [3000, 8000, 8000, 4000, 6000, 7000]
}

user_info = pd.DataFrame(data=data, index=index)
user_info

agecityincomesex
name



Tom18.0Bei Jing3000male
Bob30.0Shang Hai8000male
Mary35.0Guang Zhou8000female
James18.0Shen Zhen4000male
AndyNaNNaN6000NaN
Alice30.0
7000female
将对象分割成组

在进行分组统计前,首先要做的就是进行分组。既然是分组,就需要依赖于某个信息。

比如,依据性别来分组。直接调用 user_info.groupby(user_info["sex"])即可完成按照性别分组。

grouped  = user_info.groupby(user_info["sex"])
grouped.groups
{'female': Index(['Mary', 'Alice'], dtype='object', name='name'),
 'male': Index(['Tom', 'Bob', 'James'], dtype='object', name='name')}

可以看到,已经能够正确的按照性别来进行分组了。通常我们为了更简单,会使用这种方式来实现相同的功能:user_info.groupby("sex") 。

grouped  = user_info.groupby("sex")
grouped.groups
{'female': Index(['Mary', 'Alice'], dtype='object', name='name'),
 'male': Index(['Tom', 'Bob', 'James'], dtype='object', name='name')}

你可能会想,能不能先按照性别来分组,再按照年龄进一步分组呢?答案是可以的,看这里。

grouped  = user_info.groupby(["sex", "age"])
grouped.groups
{('female', 30.0): Index(['Alice'], dtype='object', name='name'),
 ('female', 35.0): Index(['Mary'], dtype='object', name='name'),
 ('male', 18.0): Index(['Tom', 'James'], dtype='object', name='name'),
 ('male', 30.0): Index(['Bob'], dtype='object', name='name'),
 (nan, nan): Index(['Andy'], dtype='object', name='name')}
关闭排序

默认情况下,groupby 会在操作过程中对数据进行排序。如果为了更好的性能,可以设置 sort=False

grouped  = user_info.groupby(["sex", "age"], sort=False)
grouped.groups
{('female', 30.0): Index(['Alice'], dtype='object', name='name'),
 ('female', 35.0): Index(['Mary'], dtype='object', name='name'),
 ('male', 18.0): Index(['Tom', 'James'], dtype='object', name='name'),
 ('male', 30.0): Index(['Bob'], dtype='object', name='name'),
 (nan, nan): Index(['Andy'], dtype='object', name='name')}

选择列

在使用 groupby 进行分组后,可以使用切片 [] 操作来完成对某一列的选择。

grouped  = user_info.groupby("sex")
grouped
<pandas.core.groupby.DataFrameGroupBy object at 0x000002E355D787B8>
grouped["city"]
<pandas.core.groupby.SeriesGroupBy object at 0x000002E355D78470>
遍历分组

在对数据进行分组后,可以进行遍历。

grouped  = user_info.groupby("sex")

for name, group in grouped:
    print("name: {}".format(name))
    print("group: {}".format(group))
    print("--------------")
name: female
group:         age        city  income     sex
name                                   
Mary   35.0  Guang Zhou    8000  female
Alice  30.0                7000  female
--------------
name: male
group:         age        city  income   sex
name                                 
Tom    18.0   Bei Jing     3000  male
Bob    30.0  Shang Hai     8000  male
James  18.0   Shen Zhen    4000  male
--------------

如果是根据多个字段来分组的,每个组的名称是一个元组。

grouped  = user_info.groupby(["sex", "age"])

for name, group in grouped:
    print("name: {}".format(name))
    print("group: {}".format(group))
    print("--------------")
name: ('female', 30.0)
group:         age city  income     sex
name                            
Alice  30.0         7000  female
--------------
name: ('female', 35.0)
group:        age        city  income     sex
name                                  
Mary  35.0  Guang Zhou    8000  female
--------------
name: ('male', 18.0)
group:         age       city  income   sex
name                                
Tom    18.0  Bei Jing     3000  male
James  18.0  Shen Zhen    4000  male
--------------
name: ('male', 30.0)
group:        age        city  income   sex
name                                
Bob   30.0  Shang Hai     8000  male
--------------

选择一个组

分组后,我们可以通过 get_group 方法来选择其中的某一个组。

grouped  = user_info.groupby("sex")
grouped.get_group("male")

agecityincomesex
name



Tom18.0Bei Jing3000male
Bob30.0Shang Hai8000male
James18.0Shen Zhen4000male
user_info.groupby(["sex", "age"]).get_group(("male", 18))

agecityincomesex
name



Tom18.0Bei Jing3000male
James18.0Shen Zhen4000male
聚合

分组的目的是为了统计,统计的时候需要聚合,所以我们需要在分完组后来看下如何进行聚合。常见的一些聚合操作有:计数、求和、最大值、最小值、平均值等。

想要实现聚合操作,一种方式就是调用 agg 方法。

# 获取不同性别下所包含的人数
grouped = user_info.groupby("sex")
grouped["age"].agg(len)
sex
female    2.0
male      3.0
Name: age, dtype: float64
# 获取不同性别下包含的最大的年龄
grouped = user_info.groupby("sex")
grouped["age"].agg(np.max)
sex
female    35.0
male      30.0
Name: age, dtype: float64

如果是根据多个键来进行聚合,默认情况下得到的结果是一个多层索引结构。

grouped = user_info.groupby(["sex", "age"])
rs = grouped.agg(len)
rs


cityincome
sexage

female30.011
35.011
male18.022
30.011

有两种方式可以避免出现多层索引,先来介绍第一种。对包含多层索引的对象调用 reset_index 方法。

rs.reset_index()

sexagecityincome
0female30.011
1female35.011
2male18.022
3male30.011

另外一种方式是在分组时,设置参数 as_index=False

grouped = user_info.groupby(["sex", "age"], as_index=False)
grouped.agg(len)

sexagecityincome
0female30.011
1female35.011
2male18.022
3male30.011

Series 和 DataFrame 都包含了 describe 方法,我们分组后一样可以使用 describe 方法来查看数据的情况。

grouped = user_info.groupby("sex")
grouped.describe()

ageincome

countmeanstdmin25%50%75%maxcountmeanstdmin25%50%75%max
sex















female2.032.53.53553430.031.2532.533.7535.02.07500.0707.1067817000.07250.07500.07750.08000.0
male3.022.06.92820318.018.0018.024.0030.03.05000.02645.7513113000.03500.04000.06000.08000.0
一次应用多个聚合操作

有时候进行分组后,不单单想得到一个统计结果,有可能是多个。比如想统计出不同性别下的一个收入的总和和平均值。

grouped = user_info.groupby("sex")
grouped["income"].agg([np.sum, np.mean])

summean
sex

female150007500
male150005000

如果想将统计结果进行重命名,可以传入字典。

grouped = user_info.groupby("sex")
grouped["income"].agg([np.sum, np.mean]).rename(columns={"sum": "income_sum", "mean": "income_mean"})

income_sumincome_mean
sex

female150007500
male150005000
对DataFrame列应用不同的聚合操作

有时候可能需要对不同的列使用不同的聚合操作。例如,想要统计不同性别下人群的年龄的均值以及收入的总和。

grouped = user_info.groupby("sex")
grouped.agg({"age": np.mean, "income": np.sum}).rename(columns={"age": "age_mean", "income": "income_sum"})

age_meanincome_sum
sex

female32.515000
male22.015000
transform 操作

前面进行聚合运算的时候,得到的结果是一个以分组名作为索引的结果对象。虽然可以指定 as_index=False ,但是得到的索引也并不是元数据的索引。如果我们想使用原数组的索引的话,就需要进行 merge 转换。

transform方法简化了这个过程,它会把 func 参数应用到所有分组,然后把结果放置到原数组的索引上(如果结果是一个标量,就进行广播)

# 通过 agg 得到的结果的索引是分组名
grouped = user_info.groupby("sex")
grouped["income"].agg(np.mean)
sex
female    7500
male      5000
Name: income, dtype: int64
# 通过 transform 得到的结果的索引是原始索引,它会将得到的结果自动关联上原始的索引
grouped = user_info.groupby("sex")
grouped["income"].transform(np.mean)
name
Tom      5000.0
Bob      5000.0
Mary     7500.0
James    5000.0
Andy        NaN
Alice    7500.0
Name: income, dtype: float64

可以看到,通过 transform 操作得到的结果的长度与原来保持一致。

apply 操作

除了 transform 操作外,还有更神奇的 apply 操作。

apply 会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试用 pd.concat() 把结果组合起来。func 的返回值可以是 Pandas 对象或标量,并且数组对象的大小不限。

# 使用 apply 来完成上面的聚合
grouped = user_info.groupby("sex")
grouped["income"].apply(np.mean)
sex
female    7500.0
male      5000.0
Name: income, dtype: float64

来看下 apply 不一样的用法吧。

比如想要统计不同性别最高收入的前n个值,可以通过下面这种方式实现。

def f1(ser, num=2):
    return ser.nlargest(num).tolist()

grouped["income"].apply(f1)
sex
female    [8000, 7000]
male      [8000, 4000]
Name: income, dtype: object

另外,如果想要获取不同性别下的年龄的均值,通过 apply 可以如下实现。

def f2(df):
    return df["age"].mean()

grouped.apply(f2)
sex
female    32.5
male      22.0
dtype: float64

发表评论

电子邮件地址不会被公开。 必填项已用*标注