数据库

Python 数据处理(三十九)—— groupby(过滤)

2021-03-17  本文已影响0人  名本无名

6 过滤

filter 方法可以返回原始对象的子集.

例如,我们想提取分组内的和大于 3 的所有分组的元素

In [136]: sf = pd.Series([1, 1, 2, 3, 3, 3])

In [137]: sf.groupby(sf).filter(lambda x: x.sum() > 2)
Out[137]: 
3    3
4    3
5    3
dtype: int64

filter 的参数必须是一个函数,函数参数是每个分组,并且返回 TrueFalse

例如,提取元素个数大于 2 的分组

In [138]: dff = pd.DataFrame({"A": np.arange(8), "B": list("aabbbbcc")})

In [139]: dff.groupby("B").filter(lambda x: len(x) > 2)
Out[139]: 
   A  B
2  2  b
3  3  b
4  4  b
5  5  b

另外,我们也可以过滤掉不满足条件的组,而是返回一个类似索引对象。在这个对象中,没有通过的分组的元素被 NaN 填充

In [140]: dff.groupby("B").filter(lambda x: len(x) > 2, dropna=False)
Out[140]: 
     A    B
0  NaN  NaN
1  NaN  NaN
2  2.0    b
3  3.0    b
4  4.0    b
5  5.0    b
6  NaN  NaN
7  NaN  NaN

对于具有多列的 DataFrames,过滤器应明确指定一列作为过滤条件

In [141]: dff["C"] = np.arange(8)

In [142]: dff.groupby("B").filter(lambda x: len(x["C"]) > 2)
Out[142]: 
   A  B  C
2  2  b  2
3  3  b  3
4  4  b  4
5  5  b  5

7 分派实例方法

在进行聚合或转换时,你可能想对每个分组调用一个实例方法,例如

In [144]: grouped = df.groupby("A")

In [145]: grouped.agg(lambda x: x.std())
Out[145]: 
            C         D
A                      
bar  0.181231  1.366330
foo  0.912265  0.884785

但是,如果需要传递额外的参数时,它会变得很冗长。我们可以直接使用分派到组对象上的方法

In [146]: grouped.std()
Out[146]: 
            C         D
A                      
bar  0.181231  1.366330
foo  0.912265  0.884785

实际上这生成了一个函数包装器,在调用时,它接受所有传递的参数,并在每个分组上进行调用。

然后,这个结果可以和 aggtransform 结合在一起使用

In [147]: tsdf = pd.DataFrame(
   .....:     np.random.randn(1000, 3),
   .....:     index=pd.date_range("1/1/2000", periods=1000),
   .....:     columns=["A", "B", "C"],
   .....: )
   .....: 

In [148]: tsdf.iloc[::2] = np.nan

In [149]: grouped = tsdf.groupby(lambda x: x.year)

In [150]: grouped.fillna(method="pad")
Out[150]: 
                   A         B         C
2000-01-01       NaN       NaN       NaN
2000-01-02 -0.353501 -0.080957 -0.876864
2000-01-03 -0.353501 -0.080957 -0.876864
2000-01-04  0.050976  0.044273 -0.559849
2000-01-05  0.050976  0.044273 -0.559849
...              ...       ...       ...
2002-09-22  0.005011  0.053897 -1.026922
2002-09-23  0.005011  0.053897 -1.026922
2002-09-24 -0.456542 -1.849051  1.559856
2002-09-25 -0.456542 -1.849051  1.559856
2002-09-26  1.123162  0.354660  1.128135

[1000 rows x 3 columns]

在上面的例子中,我们按照年份分组,然后对每个分组中使用 fillna 补缺失值

nlargestnsmallest 可以在 Series 类型的 groupby 上使用

In [151]: s = pd.Series([9, 8, 7, 5, 19, 1, 4.2, 3.3])

In [152]: g = pd.Series(list("abababab"))

In [153]: gb = s.groupby(g)

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

In [155]: gb.nsmallest(3)
Out[155]: 
a  6    4.2
   2    7.0
   0    9.0
b  5    1.0
   7    3.3
   3    5.0
dtype: float64

8 灵活的 apply

对分组数据的某些操作可能并不适合聚合或转换。或者说,你可能只是想让 GroupBy 来推断如何合并结果

我们可以使用 apply 函数,例如

In [156]: df
Out[156]: 
     A      B         C         D
0  foo    one -0.575247  1.346061
1  bar    one  0.254161  1.511763
2  foo    two -1.143704  1.627081
3  bar  three  0.215897 -0.990582
4  foo    two  1.193555 -0.441652
5  bar    two -0.077118  1.211526
6  foo    one -0.408530  0.268520
7  foo  three -0.862495  0.024580

In [157]: grouped = df.groupby("A")

# 也可以直接使用 .describe()
In [158]: grouped["C"].apply(lambda x: x.describe())
Out[158]: 
A         
bar  count    3.000000
     mean     0.130980
     std      0.181231
     min     -0.077118
     25%      0.069390
                ...   
foo  min     -1.143704
     25%     -0.862495
     50%     -0.575247
     75%     -0.408530
     max      1.193555
Name: C, Length: 16, dtype: float64

改变返回结果的维度

In [159]: grouped = df.groupby('A')['C']

In [160]: def f(group):
   .....:     return pd.DataFrame({'original': group,
   .....:                          'demeaned': group - group.mean()})
   .....: 

In [161]: grouped.apply(f)
Out[161]: 
   original  demeaned
0 -0.575247 -0.215962
1  0.254161  0.123181
2 -1.143704 -0.784420
3  0.215897  0.084917
4  1.193555  1.552839
5 -0.077118 -0.208098
6 -0.408530 -0.049245
7 -0.862495 -0.503211

Series 上使用 apply 类似

In [162]: def f(x):
   .....:     return pd.Series([x, x ** 2], index=["x", "x^2"])
   .....: 

In [163]: s = pd.Series(np.random.rand(5))

In [164]: s
Out[164]: 
0    0.321438
1    0.493496
2    0.139505
3    0.910103
4    0.194158
dtype: float64

In [165]: s.apply(f)
Out[165]: 
          x       x^2
0  0.321438  0.103323
1  0.493496  0.243538
2  0.139505  0.019462
3  0.910103  0.828287
4  0.194158  0.037697

9 其他有用的特征

9.1 自动排除某些列

对于之前的示例数据

In [166]: df
Out[166]: 
     A      B         C         D
0  foo    one -0.575247  1.346061
1  bar    one  0.254161  1.511763
2  foo    two -1.143704  1.627081
3  bar  three  0.215897 -0.990582
4  foo    two  1.193555 -0.441652
5  bar    two -0.077118  1.211526
6  foo    one -0.408530  0.268520
7  foo  three -0.862495  0.024580

假设,我们想按 A 分组并计算组内的标准差,但是 B 列的数据我们并不关心。

如果我们的函数不能应用于某些列,则会隐式的删除这些列,所以

In [167]: df.groupby("A").std()
Out[167]: 
            C         D
A                      
bar  0.181231  1.366330
foo  0.912265  0.884785

直接计算标准差并不会报错

9.2 使用有序因子进行分组

可以使用分类变量进行分组,分组的顺序会按照分类变量的顺序

In [177]: data = pd.Series(np.random.randn(100))

In [178]: factor = pd.qcut(data, [0, 0.25, 0.5, 0.75, 1.0])

In [179]: data.groupby(factor).mean()
Out[179]: 
(-2.645, -0.523]   -1.362896
(-0.523, 0.0296]   -0.260266
(0.0296, 0.654]     0.361802
(0.654, 2.21]       1.073801
dtype: float64
9.3 使用 grouper 分组

可以使用 pd.Grouper 控制分组,对于如下数据

In [180]: import datetime

In [181]: df = pd.DataFrame(
   .....:     {
   .....:         "Branch": "A A A A A A A B".split(),
   .....:         "Buyer": "Carl Mark Carl Carl Joe Joe Joe Carl".split(),
   .....:         "Quantity": [1, 3, 5, 1, 8, 1, 9, 3],
   .....:         "Date": [
   .....:             datetime.datetime(2013, 1, 1, 13, 0),
   .....:             datetime.datetime(2013, 1, 1, 13, 5),
   .....:             datetime.datetime(2013, 10, 1, 20, 0),
   .....:             datetime.datetime(2013, 10, 2, 10, 0),
   .....:             datetime.datetime(2013, 10, 1, 20, 0),
   .....:             datetime.datetime(2013, 10, 2, 10, 0),
   .....:             datetime.datetime(2013, 12, 2, 12, 0),
   .....:             datetime.datetime(2013, 12, 2, 14, 0),
   .....:         ],
   .....:     }
   .....: )
   .....: 

In [182]: df
Out[182]: 
  Branch Buyer  Quantity                Date
0      A  Carl         1 2013-01-01 13:00:00
1      A  Mark         3 2013-01-01 13:05:00
2      A  Carl         5 2013-10-01 20:00:00
3      A  Carl         1 2013-10-02 10:00:00
4      A   Joe         8 2013-10-01 20:00:00
5      A   Joe         1 2013-10-02 10:00:00
6      A   Joe         9 2013-12-02 12:00:00
7      B  Carl         3 2013-12-02 14:00:00

可以按照一定的频率对特定列进行分组,就像重抽样一样

In [183]: df.groupby([pd.Grouper(freq="1M", key="Date"), "Buyer"]).sum()
Out[183]: 
                  Quantity
Date       Buyer          
2013-01-31 Carl          1
           Mark          3
2013-10-31 Carl          6
           Joe           9
2013-12-31 Carl          3
           Joe           9

可以分别对列或索引进行分组

In [184]: df = df.set_index("Date")

In [185]: df["Date"] = df.index + pd.offsets.MonthEnd(2)

In [186]: df.groupby([pd.Grouper(freq="6M", key="Date"), "Buyer"]).sum()
Out[186]: 
                  Quantity
Date       Buyer          
2013-02-28 Carl          1
           Mark          3
2014-02-28 Carl          9
           Joe          18

In [187]: df.groupby([pd.Grouper(freq="6M", level="Date"), "Buyer"]).sum()
Out[187]: 
                  Quantity
Date       Buyer          
2013-01-31 Carl          1
           Mark          3
2014-01-31 Carl          9
           Joe          18
9.4 获取分组的第一行

类似于 SeriesDataFrame,可以使用 headtail 获取分组前后几行

In [188]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=["A", "B"])

In [189]: df
Out[189]: 
   A  B
0  1  2
1  1  4
2  5  6

In [190]: g = df.groupby("A")

In [191]: g.head(1)
Out[191]: 
   A  B
0  1  2
2  5  6

In [192]: g.tail(1)
Out[192]: 
   A  B
1  1  4
2  5  6
9.5 获取每组的第 n 行

SeriesDataFrame 中可以使用 nth() 来获取第 n 个元素,也可以用于获取每个分组的某一行

In [193]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=["A", "B"])

In [194]: g = df.groupby("A")

In [195]: g.nth(0)
Out[195]: 
     B
A     
1  NaN
5  6.0

In [196]: g.nth(-1)
Out[196]: 
     B
A     
1  4.0
5  6.0

In [197]: g.nth(1)
Out[197]: 
     B
A     
1  4.0

如果你要选择非空项,可以使用关键字参数 dropna,如果是 DataFrame,需要指定为 anyall(类似于 DataFrame.dropna(how='any|all'))

# nth(0) 与 g.first() 等价
In [198]: g.nth(0, dropna="any")
Out[198]: 
     B
A     
1  4.0
5  6.0

In [199]: g.first()
Out[199]: 
     B
A     
1  4.0
5  6.0

# nth(-1) 与 g.last() 等价
In [200]: g.nth(-1, dropna="any")  # NaNs denote group exhausted when using dropna
Out[200]: 
     B
A     
1  4.0
5  6.0

In [201]: g.last()
Out[201]: 
     B
A     
1  4.0
5  6.0

In [202]: g.B.nth(0, dropna="all")
Out[202]: 
A
1    4.0
5    6.0
Name: B, dtype: float64

与其他方法一样,使用 as_index=False 分组名将不会作为索引

In [203]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=["A", "B"])

In [204]: g = df.groupby("A", as_index=False)

In [205]: g.nth(0)
Out[205]: 
   A    B
0  1  NaN
2  5  6.0

In [206]: g.nth(-1)
Out[206]: 
   A    B
1  1  4.0
2  5  6.0

你也可以传入一个整数列表,一次性选取多行

In [207]: business_dates = pd.date_range(start="4/1/2014", end="6/30/2014", freq="B")

In [208]: df = pd.DataFrame(1, index=business_dates, columns=["a", "b"])

# 选取每月的第 1、4 和最后一天
In [209]: df.groupby([df.index.year, df.index.month]).nth([0, 3, -1])
Out[209]: 
        a  b
2014 4  1  1
     4  1  1
     4  1  1
     5  1  1
     5  1  1
     5  1  1
     6  1  1
     6  1  1
     6  1  1
9.6 枚举分组项

使用 cumcount 方法,可以查看每行在分组中出现的顺序

In [210]: dfg = pd.DataFrame(list("aaabba"), columns=["A"])

In [211]: dfg
Out[211]: 
   A
0  a
1  a
2  a
3  b
4  b
5  a

In [212]: dfg.groupby("A").cumcount()
Out[212]: 
0    0
1    1
2    2
3    0
4    1
5    3
dtype: int64

In [213]: dfg.groupby("A").cumcount(ascending=False)
Out[213]: 
0    3
1    2
2    1
3    1
4    0
5    0
dtype: int64
9.7 枚举分组

可以使用 ngroup() 查看分组的顺序,该顺序与 cumcount 的顺序相反。

注意:该顺序与迭代时的分组顺序一样,并不是第一次观测到的顺序

In [214]: dfg = pd.DataFrame(list("aaabba"), columns=["A"])

In [215]: dfg
Out[215]: 
   A
0  a
1  a
2  a
3  b
4  b
5  a

In [216]: dfg.groupby("A").ngroup()
Out[216]: 
0    0
1    0
2    0
3    1
4    1
5    0
dtype: int64

In [217]: dfg.groupby("A").ngroup(ascending=False)
Out[217]: 
0    1
1    1
2    1
3    0
4    0
5    1
dtype: int64
上一篇 下一篇

猜你喜欢

热点阅读