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)
print(user_info)
# age city sex income
# name
# Tom 18.0 Bei Jing male 3000
# Bob 30.0 Shang Hai male 8000
# Mary 35.0 Guang Zhou female 8000
# James 18.0 Shen Zhen male 4000
# Andy NaN NaN NaN 6000
# Alice 30.0 female 7000
将对象分割成组
在进行分组统计前,首先要做的就是进行分组。既然是分组,就需要依赖于某个信息。
比如,依据性别来分组。直接调用
user_info.groupby(user_info["sex"])
或者user_info.groupby("sex")
即可完成按照性别分组。
在该分组的过程中,分组值会忽略NaN,None,NaT。
grouped = user_info.groupby(user_info["sex"])
print(grouped.groups)
# {'female': ['Mary', 'Alice'], 'male': ['Tom', 'Bob', 'James']}
grouped = user_info.groupby("sex")
print(grouped.groups)
# {'female': ['Mary', 'Alice'], 'male': ['Tom', 'Bob', 'James']}
print(type(grouped.groups))
# <class 'pandas.io.formats.printing.PrettyDict'>
连续分组
你可能会想,能不能先按照性别来分组,再按照年龄进一步分组呢?答案是可以的
grouped = user_info.groupby(["sex","age"])
print(grouped.groups)
# {
# ('male', 18.0): ['Tom', 'James'],
# ('male', 30.0): ['Bob'],
# ('female', 35.0): ['Mary'],
# (nan, nan): ['Andy'],
# ('female', 30.0): ['Alice']
}
grouped是一个DataFrame分组对象
关闭排序
默认情况下,groupby 会在操作过程中对数据进行排序。如果为了更好的性能,可以设置 sort=False。
grouped = user_info.groupby(["sex","age"],sort=False)
print(grouped.groups)
# {('male', 18.0): ['Tom', 'James'], ('male', 30.0): ['Bob'], ('female', 35.0): ['Mary'], (nan, nan): ['Andy'], ('female', 30.0): ['Alice']}
选择列
在使用 groupby 进行分组后,可以使用切片 [] 操作来完成对某一列的选择。
c = user_info.groupby('sex')
print(c)
# <pandas.core.groupby.generic.DataFrameGroupBy object at 0x000002423A2C9D88>
print(c['city'])
# <pandas.core.groupby.generic.SeriesGroupBy object at 0x000002423A2C9A48>
遍历分组
在对数据进行分组后,可以进行遍历。
grouped = user_info.groupby("sex")
for name, group in grouped:
print("name: {}".format(name))
print("group: {}".format(group))
print("--------------")
# name: female
# group: age city sex income
# name
# Mary 35.0 Guang Zhou female 8000
# Alice 30.0 female 7000
# --------------
# name: male
# group: age city sex income
# name
# Tom 18.0 Bei Jing male 3000
# Bob 30.0 Shang Hai male 8000
# James 18.0 Shen Zhen male 4000
# --------------
遍历后每一项的group都是一个DataFrame对象,name是分组的名称。
如果是根据多个字段来分组的,每个组的名称是一个元组。
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 sex income
# name
# Alice 30.0 female 7000
# --------------
# name: ('female', 35.0)
# group: age city sex income
# name
# Mary 35.0 Guang Zhou female 8000
# --------------
# name: ('male', 18.0)
# group: age city sex income
# name
# Tom 18.0 Bei Jing male 3000
# James 18.0 Shen Zhen male 4000
# --------------
# name: ('male', 30.0)
# group: age city sex income
# name
# Bob 30.0 Shang Hai male 8000
# --------------
选择一个组
分组后,我们可以通过 get_group 方法来选择其中的某一个组。
1.获取male分组的数据,返回DataFrame对象
grouped = user_info.groupby("sex")
c = grouped.get_group("male")
print(c)
# age city sex income
# name
# Tom 18.0 Bei Jing male 3000
# Bob 30.0 Shang Hai male 8000
# James 18.0 Shen Zhen male 4000
2.多个分组条件时
获取male分组以及age=18分组数据。
c = user_info.groupby(["sex", "age"]).get_group(("male", 18))
print(c)
# age city sex income
# name
# Tom 18.0 Bei Jing male 3000
# James 18.0 Shen Zhen male 4000
多个分组条件时,在选择一个分组的时候,参数必须完整,(每个分组条件的值都要有)
聚合
分组的目的是为了统计,统计的时候需要聚合,所以我们需要在分完组后来看下如何进行聚合。常见的一些聚合操作有:计数、求和、最大值、最小值、平均值等。
agg()
想要实现聚合操作,一种方式就是调用 agg 方法。
1.获取不同分类下所包含的数量
c = user_info.groupby('sex')
# print(c)
x = c.agg(len)
print(x)
# age city income
# sex
# female 2.0 2 2
# male 3.0 3 3
# 获取不同性别下所包含的人数
grouped = user_info.groupby("sex")
c = grouped.agg(len).age
print(c)
# sex
# female 2.0
# male 3.0
# Name: age, dtype: float64
2.获取不同分类下age的最大值
c = grouped.agg(max).age
print(c)
# sex
# female 35.0
# male 30.0
# Name: age, dtype: float64
3.如果是根据多个键来进行聚合,默认情况下得到的结果是一个多层索引结构。
grouped = user_info.groupby(["sex", "age"])
rs = grouped.agg(len)
print(rs)
# city income
# sex age
# female 30.0 1 1
# 35.0 1 1
# male 18.0 2 2
# 30.0 1 1
grouped = user_info.groupby(["sex", "age"])
rs = grouped.agg(len)
print(rs)
# city income
# sex age
# female 30.0 1 1
# 35.0 1 1
# male 18.0 2 2
# 30.0 1 1
print(rs.reset_index())
# sex age city income
# 0 female 30.0 1 1
# 1 female 35.0 1 1
# 2 male 18.0 2 2
# 3 male 30.0 1 1
有两种方式可以避免出现多层索引,上边是第一种。对包含多层索引的对象调用 reset_index 方法。
另外一种方式是在分组时,设置参数 as_index=False。
grouped = user_info.groupby(["sex", "age"], as_index=False)
c = grouped.agg(len)
print(c)
# sex age city income
# 0 female 30.0 1 1
# 1 female 35.0 1 1
# 2 male 18.0 2 2
# 3 male 30.0 1 1
4.Series 和 DataFrame 都包含了 describe 方法,我们分组后一样可以使用 describe 方法来查看数据的情况。
grouped = user_info.groupby("sex")
c = grouped.describe()
print(c)
# age ... income
# count mean std min ... 25% 50% 75% max
# sex ...
# female 2.0 32.5 3.535534 30.0 ... 7250.0 7500.0 7750.0 8000.0
# male 3.0 22.0 6.928203 18.0 ... 3500.0 4000.0 6000.0 8000.0
一次应用多个聚合操作
有时候进行分组后,不单单想得到一个统计结果,有可能是多个。比如想统计出不同性别下的一个收入的总和和平均值。
grouped = user_info.groupby("sex")
c = grouped['income'].agg([np.sum, np.mean])
print(c)
# sum mean
# sex
# female 15000 7500
# male 15000 5000
c = grouped.agg([np.sum, np.mean])
print(c)
# age income
# sum mean sum mean
# sex
# female 65.0 32.5 15000 7500
# male 66.0 22.0 15000 5000
如果想将统计结果进行重命名,可以传入字典。
grouped = user_info.groupby("sex")
c = grouped["income"].agg([np.sum, np.mean]).rename(columns={"sum": "income_sum", "mean": "income_mean"})
print(c)
# income_sum income_mean
# sex
# female 15000 7500
# male 15000 5000
对DataFrame列应用不同的聚合操作
有时候可能需要对不同的列使用不同的聚合操作。例如,想要统计不同性别下人群的年龄的均值以及收入的总和。
grouped = user_info.groupby("sex")
c = grouped.agg({"age": np.mean, "income": np.sum}).rename(columns={"age": "age_mean", "income": "income_sum"})
print(c)
# age_mean income_sum
# sex
# female 32.5 15000
# male 22.0 15000
transform 操作
前面进行聚合运算的时候,得到的结果是一个以分组名作为索引的结果对象。虽然可以指定 as_index=False ,但是得到的索引也并不是元数据的索引。如果我们想使用原数组的索引的话,就需要进行 merge 转换。
transform方法简化了这个过程,它会把 func 参数应用到所有分组,然后把结果放置到原数组的索引上(如果结果是一个标量,就进行广播)
# 通过 agg 得到的结果的索引是分组名
grouped = user_info.groupby("sex")
c = grouped["income"].agg(np.mean)
print(c)
# sex
# female 7500
# male 5000
# Name: income, dtype: int64
# 不改变数据的索引,但是数据的值为平均值
c = grouped['income'].transform(np.mean)
print(c)
# name
# Tom 5000.0
# Bob 5000.0
# Mary 7500.0
# James 5000.0
# Andy NaN
# Alice 7500.0
# Name: income, dtype: float64
apply 操作
除了 transform 操作外,还有更神奇的 apply 操作。
apply 会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试用 pd.concat() 把结果组合起来。func 的返回值可以是 Pandas 对象或标量,并且数组对象的大小不限。
1.比如想要统计不同性别最高收入的前n个值,可以通过下面这种方式实现。
grouped = user_info.groupby("sex")
def f1(ser, num=2):
return ser.nlargest(num).tolist()
c = grouped["income"].apply(f1)
print(c)
# sex
# female [8000, 7000]
# male [8000, 4000]
# Name: income, dtype: object
另外,如果想要获取不同性别下的年龄的均值,通过 apply 可以如下实现。
apply()是对其他操作的扩展,因为使用agg()时,只能使用其内置的函数,而apply()可以自定义函数。