groupby

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

GroupBy

group_by的底层步骤:

(1)split:依据某个标准把数据 分割

(2)Apply:将某个函数 应用 到每一组数据,例如agg、transform

(3)combining:将结果合并

在apply这一步,我们可以采取:

(1)聚合操作:将一个group的数据映射为一个数,例如求平均值、group内元素的个数等

(2)转换操作:数据分布正态化等

(3)过滤操作:去除较少元素的group或者去除较小的元素等,必须传入一个返回布尔值的func

1-spliting into groups

groupby函数有两个比较重要的参数:by和axis,其中:
(1)by表示依据什么进行分组,可以传入一个 column-name、list、dict、func,具体特点为:
column-name:以该column对应的series为依据进行分组,series内容相同的为同组;
list:根据lis内容进行分类汇总;
dict:key是对应的axis label(可以是index也可以是column),value是目标label,同一个目标label的为一组;
func:实现axis label到目标label的转换,同一个label的为一组。
(2)axis表示按照什么方向进行groupby,他将决定数据切分的方式,具体而言,axis=0/"index"表示以行为单位进行split;
反之,axis=1/“columns”,表示数据以列为单位进行split。不要被惯性干扰,其实对于一个dataframe而言,内容完全是对称的。

df = pd.DataFrame({
    "class":["bird", "bird", "mammal", "mammal", "mammal"],
    "order":["Falconiformes", "Psittaciformes", "Carnivora", "Primates", "Carnivora"],
    "max_speed":[389.0, 24.0, 80.2, np.nan, 58]
}, index=["falcon", "parrot", "lion", "monkey", "leopard"])
df
classordermax_speed
falconbirdFalconiformes389.0
parrotbirdPsittaciformes24.0
lionmammalCarnivora80.2
monkeymammalPrimatesNaN
leopardmammalCarnivora58.0

按照行进行分割

df.groupby("class", axis="index")  # axis=0和axis=index等价;1和column等价
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fea93816ca0>
df.groupby("class", axis=0).groups  # 返回字典,key是group label,value是axis labels(index)
{'bird': ['falcon', 'parrot'], 'mammal': ['lion', 'monkey', 'leopard']}
df.groupby(["class", "order"])
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fea93861580>
df.groupby(["class", "order"]).groups
{('bird', 'Falconiformes'): ['falcon'], ('bird', 'Psittaciformes'): ['parrot'], ('mammal', 'Carnivora'): ['lion', 'leopard'], ('mammal', 'Primates'): ['monkey']}
In [6]: df = pd.DataFrame(
   ...:     {
   ...:         "A": np.random.randn(5),
   ...:         "B": np.random.randn(5),
   ...:         "C": np.random.randn(5),
   ...:         "D": np.random.randn(5),
   ...:     }
   ...: )
df
ABCD
01.047022-0.2665271.659578-1.731920
1-0.726851-0.931228-2.7740250.443112
20.265430-0.1721120.2784150.333350
31.128021-0.499495-0.694711-0.884655
40.1806620.802036-1.210244-1.251326

按照列column进行分割

def get_letter_type(letter):
    if letter.lower() in "aeiou":
        return "vowel"
    else:
        return "constant"
grouped = df.groupby(get_letter_type, axis=1)
grouped.groups  # key 是 group key,value是对应的axis labels(列名)
{'constant': ['B', 'C', 'D'], 'vowel': ['A']}
grouped.get_group("constant").head(3)
BCD
0-0.2665271.659578-1.731920
1-0.931228-2.7740250.443112
2-0.1721120.2784150.333350

根据字典进行groupby

df.groupby({"A":"A", "B":"A", "C":"C", "D":"C"}, axis=1).get_group("A").head(3)
AB
01.047022-0.266527
1-0.726851-0.931228
20.265430-0.172112
df.groupby({"A":"A", "B":"A", "C":"C", "D":"C"}, axis=1).get_group("C")
CD
01.659578-1.731920
1-2.7740250.443112
20.2784150.333350
3-0.694711-0.884655
4-1.210244-1.251326
df.groupby({"A":"A", "B":"A", "C":"C", "D":"C"}, axis=1).agg(max)
AC
01.0470221.659578
1-0.7268510.443112
20.2654300.333350
31.128021-0.694711
40.802036-1.210244

小结:

重要属性:groups属性返回字典,key就是group-key,value是命中的axis-label

重要接口:get_group()接口返回 当前 group key 对应的所有数据

特点:pandas 的index是允许重复的,如果一个包含重复内容的index被作为grouby的key,那么

同一个key指向的value会被分配到同一个组和,所以groupby之后的内容是没有重复的。

问题:如果是行切的话,可以基于列名做聚合,那如果是列切,是基于index做聚合吗?最后的例子表明是的。

另外,groupby作用于df时返回Dataframegroupby对象(dfgb),作用与Series返回sgb,dfgb虽然是df,但也要从group的角度理解其数据

GroupBy Sort

groupby之后生成dfgb对象,聚合操作后生成df对象,可输出

groupby操作会对key排序,如果不需要的话可以领sort为False以加速

df2 = pd.DataFrame({"X": ["B", "B", "A", "A"], "Y": [1, 2, 3, 4]})
b = df2.groupby("X").sum()
type(b)
pandas.core.frame.DataFrame
b
Y
X
A7
B3
df2.groupby("X", sort=False).sum()
Y
X
B3
A7

groupby 去空

使用groupby操作时,通过配置 dropna参数为True或者False来确定,空数据时候可以作为group的key

df_list = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]]
df_dropna = pd.DataFrame(df_list, columns=["a", "b", "c"])
df_dropna
abc
012.03
11NaN4
221.03
312.02

group-key列具有空值

df_dropna.groupby("b").sum()  
ac
b
1.023
2.025
df_dropna.groupby("b", dropna=False).sum()
ac
b
1.023
2.025
NaN14

每一个group内部都有一定的数据,在执行聚合函数时候,只会跳过nan数据本身,不干扰同行其他数据

df_dropna.groupby("a").sum()
bc
a
14.09
21.03

dfgroupby对象的列选操作–column selectioon

df groupby 之后,可能希望对不同的列做不同的操作,这个时候可以使用列选操作,后面会有列选后续操作的使用
GroupBy 多重索引-MultiIndex:https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html

df_dropna
abc
012.03
11NaN4
221.03
312.02
grouped = df_dropna.groupby("a")
grouped["c"]  # 等价于 grouped.c
<pandas.core.groupby.generic.SeriesGroupBy object at 0x7fbc71f67d60>
grouped["c"].get_group(1)
0    3
1    4
3    2
Name: c, dtype: int64

groupby对象的迭代

如前所述,groupby对象可以看作是key-value,其中key是group key,value就是其他数据构成的dataframe

for key, group in grouped:
    print(key)
    print(group)
    print(type(group))
    print("")
1
   a    b  c
0  1  2.0  3
1  1  NaN  4
3  1  2.0  2
<class 'pandas.core.frame.DataFrame'>

2
   a    b  c
2  2  1.0  3
<class 'pandas.core.frame.DataFrame'>

聚合操作-Aggregation

对groupbyDataframe 对象可以进一步用聚合函数得到同类汇聚的结果进行展示
可以直接调用groupbyDataframe的成员函数例如:sum/mean/max/size/count等,但是注意加括号

df = pd.DataFrame(
    {
        "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
        "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
        "C": np.random.randn(8),
        "D": np.random.randn(8),
    }
)
df
ABCD
0fooone0.3514050.425799
1barone-2.175271-0.278632
2footwo-0.175300-0.910150
3barthree-1.7231090.614626
4footwo-1.765967-0.465801
5bartwo0.6535391.727326
6fooone0.037191-0.381093
7foothree2.056519-1.862873
df.groupby("A").sum()  # df.groupby("A").agg(np.sum)  # 等价
CD
A
bar-3.2448412.063321
foo0.503848-3.194118

小插曲:聚合后得到dataFrame,然后可以基于to_dict接口得到字典

df.groupby("A").sum().to_dict("index")
{'bar': {'C': -3.2448414872221907, 'D': 2.063321126560749},
 'foo': {'C': 0.5038481220391884, 'D': -3.194117627544297}}
df.groupby("A").sum().to_dict()
{'C': {'bar': -3.2448414872221907, 'foo': 0.5038481220391884},
 'D': {'bar': 2.063321126560749, 'foo': -3.194117627544297}}

可以看出,aggergation之后,group names会作为新的index出现在列表中,如果不想让他们
作为index,可以配置 as_index=False,或者调用reset_index方法

df.groupby("A", as_index=False).agg(np.sum)  # df.groupby("A").agg(np.sum).reset_index()   # 等价
ACD
0bar0.5129781.766982
1foo2.510977-3.129734
df.groupby("A").size() # 针对A
A
bar    3
foo    5
dtype: int64
df.groupby("A").count()  # 针对 BCD
BCD
A
bar333
foo555
df = pd.DataFrame(
    {
        "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
        "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
        "C": np.random.randn(8),
        "D": np.random.randn(8),
    }
)
df
ABCD
0fooone0.596527-1.168976
1barone1.3874880.439306
2footwo-0.105645-0.085638
3barthree0.0192560.758142
4footwo-0.038975-0.505880
5bartwo-0.2124901.759912
6fooone0.259668-0.550867
7foothree-1.184676-1.158491
grouped = df.groupby("A")

Applying multiple functions at once-单次投放多个函数

grouped.agg([np.sum, np.mean])  # 对于groupby dataFrame,可以传入 多个 函数,那么将会返回多层标签对应的dataframe结果
CD
summeansummean
A
bar0.9529340.3176451.5652010.521734
foo-3.357455-0.671491-1.722682-0.344536
grouped.agg([np.sum, np.mean]).to_excel("groupby_test.xlsx")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3h515W4T-1629604064172)(attachment:groupby_test.png)]

grouped["C"].agg([np.sum, np.max, np.mean])  # 对于groupby Series 将不同的函数 apply 到统一series获取不同的结果构成dataFrame
sumamaxmean
A
bar0.9529340.4568410.317645
foo-3.3574550.979221-0.671491

agg后现实的名字是 function 确定的,如果想重命名,可以用rename方法,Series和DataFrame方法类似如下

grouped["C"].agg([np.sum, np.mean]).rename(columns={"sum":"和", "mean":"平均值"})
平均值
A
bar0.9529340.317645
foo-3.357455-0.671491

Named Aggregation

为了支持面向 列 的聚合操作,pandas支持Named Aggregagtion操作

key就是输出的列名,value包含两部分,面向的column,使用的aggfunc,具体见下面的例子

逻辑:groupby之后,显然任一个key对应的height和weight都是一个series,NamedAggregation可以为不同的series配置不用的聚合函数

animals = pd.DataFrame(
    {
        "kind": ["cat", "dog", "cat", "dog"],
        "height": [9.1, 6.0, 9.5, 34.0],
        "weight": [7.9, 7.5, 9.9, 198.0],
    }
)

animals
kindheightweight
0cat9.17.9
1dog6.07.5
2cat9.59.9
3dog34.0198.0
animals.groupby("kind").agg(
    min_height=pd.NamedAgg(column="height", aggfunc="min"),
    max_height=pd.NamedAgg(column="height", aggfunc="max"),
    average_weight=pd.NamedAgg(column="weight", aggfunc="mean")

)
min_heightmax_heightaverage_weight
kind
cat9.19.58.90
dog6.034.0102.75

上面的NamedAgg使用的是namedTuple,下面是等价的普通元组形式:

animals.groupby("kind").agg(
    min_height=("height", "min"),
    max_weight=("weight", "max")
)
min_heightmax_weight
kind
cat9.19.9
dog6.0198.0

当选取的keywords不是python合法命名时,可以用字典+unpack的方式实现

animals.groupby("kind").agg(
    **{"min height":("height", "min")},
    max_weight=("weight", "max")
)
min heightmax_weight
kind
cat9.19.9
dog6.0198.0

如果只关注其中一个columns,那么可以对SeriesGroupBy对象使用named聚合
由于事先指定了列,那么value部分就只有函数

animals.groupby("kind").height.agg(
    min_height="min", 
    max_height="max"
)
min_heightmax_height
kind
cat9.19.5
dog6.034.0
animals.groupby("kind")["height"].agg(
    min_height="min", 
    max_height="max"
)
min_heightmax_height
kind
cat9.19.5
dog6.034.0

上述功能可以基于字典等价实现如下:

animals.groupby("kind").agg({"height":[min], "weight":max}).rename(columns={"height":"高度", "min":"最小值"})
高度weight
最小值max
kind
cat9.19.9
dog6.0198.0

note:传入的func可以是np系列,也可以是groupby类的成员函数,成员函数可以用字符串的形式访问/也可以不用字符串


基于用户自定义函数进行聚合

首先,明确不同情况下返回的数据结构如下,可以看出 groupby之后返回的是groupby dataframe

1、之后进行列选 返回的是 groupby Series;

2、用[[column]]返回dataframe对象;

以上是和直接基于dataFrame进行列选的的结果相同的

animals.groupby("kind")
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fea94016490>
animals.groupby("kind")[["weight"]]
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fea94016a00>
animals.groupby("kind")["weight"]
<pandas.core.groupby.generic.SeriesGroupBy object at 0x7fea94025460>
animals.groupby("kind")[["weight"]].agg(lambda x: set(x)) # x是一个group的数据
weight
kind
cat{9.9, 7.9}
dog{198.0, 7.5}

小结:

1、多列使用1个函数

2、每列使用不同的函数

3、一列使用不同的函数

4、Named Aggregation

5、基于自定义函数

Transformation

对group内部的数据做映射
与aggregation不同的是 Transform不直接追求数据的降维,而是对每一个数据做映射

index = pd.date_range("10/1/1999", periods=1100, freq="D")  # 创建时间索引
ts = pd.Series(np.random.normal(0, 2, 1100), index=index)  # 均值为0,方差为4,1100个点
ts.head()
1999-10-01   -0.215051
1999-10-02   -1.241018
1999-10-03   -3.258130
1999-10-04   -0.441858
1999-10-05   -2.059294
Freq: D, dtype: float64
grouped = ts.groupby(lambda x: x.year)
grouped.mean()  # 以年为单位显示,因为按照year分组
1999   -0.080469
2000   -0.009197
2001    0.030337
2002    0.165433
dtype: float64
grouped.var()
1999    4.574915
2000    3.531059
2001    4.272156
2002    4.283034
dtype: float64
transformer = grouped.transform(lambda x: (x - np.mean(x)) / np.std(x))
type(transformer)
pandas.core.series.Series
transformer  # 映射之后的结果,和原数据一样的长度
1999-10-01   -0.063266
1999-10-02   -0.545563
1999-10-03   -1.493789
1999-10-04   -0.169886
1999-10-05   -0.930227
                ...   
2002-09-30    0.305898
2002-10-01   -2.120204
2002-10-02    0.328765
2002-10-03   -1.115765
2002-10-04   -0.054039
Freq: D, Length: 1100, dtype: float64
transformer.mean()
-3.087176978701998e-17
transformer.var()
1.0009099181073695
compare = pd.DataFrame({"original":ts, "Transformed":transformer})
compare.plot()

必要时,transform会broadcast

ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min())
1999-10-01    11.571518
1999-10-02    11.571518
1999-10-03    11.571518
1999-10-04    11.571518
1999-10-05    11.571518
                ...    
2002-09-30    12.554169
2002-10-01    12.554169
2002-10-02    12.554169
2002-10-03    12.554169
2002-10-04    12.554169
Freq: D, Length: 1100, dtype: float64
ts.groupby(lambda x: x.year).transform(lambda x: max(x))
1999-10-01    6.142405
1999-10-02    6.142405
1999-10-03    6.142405
1999-10-04    6.142405
1999-10-05    6.142405
                ...   
2002-09-30    6.754179
2002-10-01    6.754179
2002-10-02    6.754179
2002-10-03    6.754179
2002-10-04    6.754179
Freq: D, Length: 1100, dtype: float64

基于transform填补空缺值

df = pd.DataFrame([[np.nan, 2, 0, 0],
...                    [3, 4, 0, 1],
...                    [np.nan, np.nan, 1, 5],
...                    [5, 3, 2, 4]],
...                   columns=list("ABCD"))
df
ABCD
0NaN2.000
13.04.001
2NaNNaN15
35.03.024
grouped = df.groupby([0, 0, 1, 1])  # 这个给出了基于 list groupby的方法,列表内容也可以是字符串
grouped.transform(lambda x: x.fillna(x.mean()))
ABCD
03.02.000
13.04.001
25.03.015
35.03.024

滑窗操作

df = pd.DataFrame({"A": [1]*10 + [5]*10, "B":np.arange(20)})
df
AB
010
111
212
313
414
515
616
717
818
919
10510
11511
12512
13513
14514
15515
16516
17517
18518
19519
df.groupby("A").rolling(4).B.mean()  # 解释:将滑窗宽度设置为4,求4个值的平均值,无法放置窗口的置为None
A    
1  0      NaN
   1      NaN
   2      NaN
   3      1.5
   4      2.5
   5      3.5
   6      4.5
   7      5.5
   8      6.5
   9      7.5
5  10     NaN
   11     NaN
   12     NaN
   13    11.5
   14    12.5
   15    13.5
   16    14.5
   17    15.5
   18    16.5
   19    17.5
Name: B, dtype: float64
df.groupby("A").expanding().sum()  # 将当前值与前面的所有值求和,关键在于expand函数
B
A
100.0
11.0
23.0
36.0
410.0
515.0
621.0
728.0
836.0
945.0
51010.0
1121.0
1233.0
1346.0
1460.0
1575.0
1691.0
17108.0
18126.0
19145.0

resample函数:对数据进行重采样(可以变少/多)

df_re = pd.DataFrame(
    {
        "date": pd.date_range(start="2016-01-01", periods=4, freq="W"),
        "group": [1, 1, 2, 2],
        "val": [5, 6, 7, 8],
    }
).set_index("date")
df_re
groupval
date
2016-01-0315
2016-01-1016
2016-01-1727
2016-01-2428
df_re.resample("2D").ffill()
groupval
date
2016-01-0315
2016-01-0515
2016-01-0715
2016-01-0915
2016-01-1116
2016-01-1316
2016-01-1516
2016-01-1727
2016-01-1927
2016-01-2127
2016-01-2327


Filtration

必须为filter函数传入一个func作为参数,这个函数的每一个元素都是groupbyDataFrame中的一个Group

dff = pd.DataFrame({"A":np.arange(8), "B":list("aaabbbbc")})
dff.groupby("B").filter(lambda x: len(x) >= 3)
AB
00a
11a
22a
33b
44b
55b
66b

dispatching

这里展示了一些groupbySeriesd的简便的实例函数的调度方法:std、nlargest、nsmallest

df
AB
010
111
212
313
414
515
616
717
818
919
10510
11511
12512
13513
14514
15515
16516
17517
18518
19519
df.groupby("A").std()
B
A
13.02765
53.02765
df.groupby("A").agg(np.std)
B
A
13.02765
53.02765
df.groupby("A").agg(lambda x: x.std())
B
A
13.02765
53.02765
s = pd.Series([9, 8, 7, 5, 19, 1, 4.2, 3.3])

g = pd.Series(list("abababab"))

gb = s.groupby(g)

gb.nlargest(3)
a  4    19.0
   0     9.0
   2     7.0
b  1     8.0
   3     5.0
   7     3.3
dtype: float64


灵活的Apply操作

如果前面介绍的agg、filter、transform不能满足要求,可以考虑使用apply函数

s = pd.Series(np.random.rand(5))
s
0    0.309940
1    0.801719
2    0.225639
3    0.622519
4    0.545119
dtype: float64
def f(x):
    return pd.Series([x, x**2], index=["x", "x^2"])
s.apply(f)
xx^2
00.3099400.096063
10.8017190.642753
20.2256390.050913
30.6225190.387530
40.5451190.297155

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值