空无以求全
宁静以致远
当前位置:首页 > .html

Python 数据处理(三十八)—— groupby(聚合与转换)

作者:大熊空间发布时间:2022-02-19 17:09分类: 浏览:253评论:0


导读:4 聚合 对数据分组完后,可以使用一些函数对分组数据进行计算 最常用的就是 aggregate()(等于 agg()) 方法 In [67]: grouped = df.group...

4 聚合

对数据分组完后,可以使用一些函数对分组数据进行计算

最常用的就是 aggregate()(等于 agg()) 方法

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

In [68]: grouped.aggregate(np.sum)
Out[68]: 
            C         D
A                      
bar  0.392940  1.732707
foo -1.796421  2.824590

In [69]: grouped = df.groupby(["A", "B"])

In [70]: grouped.aggregate(np.sum)
Out[70]: 
                  C         D
A   B                        
bar one    0.254161  1.511763
    three  0.215897 -0.990582
    two   -0.077118  1.211526
foo one   -0.983776  1.614581
    three -0.862495  0.024580
    two    0.049851  1.185429

如你所见,聚合的结果将以组名作为分组轴的新索引。

在有多个分组键的情况下,结果默认是一个 MultiIndex,可以使用 as_index 参数来改变这一行为

In [71]: grouped = df.groupby(["A", "B"], as_index=False)

In [72]: grouped.aggregate(np.sum)
Out[72]: 
     A      B         C         D
0  bar    one  0.254161  1.511763
1  bar  three  0.215897 -0.990582
2  bar    two -0.077118  1.211526
3  foo    one -0.983776  1.614581
4  foo  three -0.862495  0.024580
5  foo    two  0.049851  1.185429

In [73]: df.groupby("A", as_index=False).sum()
Out[73]: 
     A         C         D
0  bar  0.392940  1.732707
1  foo -1.796421  2.824590

当然,也可以使用 reset_index 达到相同的效果

In [74]: df.groupby(["A", "B"]).sum().reset_index()
Out[74]: 
     A      B         C         D
0  bar    one  0.254161  1.511763
1  bar  three  0.215897 -0.990582
2  bar    two -0.077118  1.211526
3  foo    one -0.983776  1.614581
4  foo  three -0.862495  0.024580
5  foo    two  0.049851  1.185429

另一个简单的例子是,计算每个分组的大小。

In [75]: grouped.size()
Out[75]: 
     A      B  size
0  bar    one     1
1  bar  three     1
2  bar    two     1
3  foo    one     2
4  foo  three     1
5  foo    two     2

计算每个分组的基本统计信息

In [76]: grouped.describe()
Out[76]: 
      C                                                    ...         D                                                  
  count      mean       std       min       25%       50%  ...       std       min       25%       50%       75%       max
0   1.0  0.254161       NaN  0.254161  0.254161  0.254161  ...       NaN  1.511763  1.511763  1.511763  1.511763  1.511763
1   1.0  0.215897       NaN  0.215897  0.215897  0.215897  ...       NaN -0.990582 -0.990582 -0.990582 -0.990582 -0.990582
2   1.0 -0.077118       NaN -0.077118 -0.077118 -0.077118  ...       NaN  1.211526  1.211526  1.211526  1.211526  1.211526
3   2.0 -0.491888  0.117887 -0.575247 -0.533567 -0.491888  ...  0.761937  0.268520  0.537905  0.807291  1.076676  1.346061
4   1.0 -0.862495       NaN -0.862495 -0.862495 -0.862495  ...       NaN  0.024580  0.024580  0.024580  0.024580  0.024580
5   2.0  0.024925  1.652692 -1.143704 -0.559389  0.024925  ...  1.462816 -0.441652  0.075531  0.592714  1.109898  1.627081

[6 rows x 16 columns]

可以使用 nunique 计算每个分组中唯一值的数量,与 value_counts 类似,但是它只计算唯一值

In [77]: ll = [['foo', 1], ['foo', 2], ['foo', 2], ['bar', 1], ['bar', 1]]

In [78]: df4 = pd.DataFrame(ll, columns=["A", "B"])

In [79]: df4
Out[79]: 
     A  B
0  foo  1
1  foo  2
2  foo  2
3  bar  1
4  bar  1

In [80]: df4.groupby("A")["B"].nunique()
Out[80]: 
A
bar    1
foo    2
Name: B, dtype: int64

聚合函数可以对数据进行降维,下面是一些常用的聚合函数:

上面的函数都会忽略 NA 值。任何一个能够将 Series 转化为标量值的函数都可以作为聚合函数

4.1 同时应用多个函数

对于一个分组的 Series,可以传入一个函数列表或者字典,并输出一个 DataFrame

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

In [82]: grouped["C"].agg([np.sum, np.mean, np.std])
Out[82]: 
          sum      mean       std
A                                
bar  0.392940  0.130980  0.181231
foo -1.796421 -0.359284  0.912265

如果分组是 DataFrame,传入一个函数列表或字典,将会得到一个层次索引

In [83]: grouped.agg([np.sum, np.mean, np.std])
Out[83]: 
            C                             D                    
          sum      mean       std       sum      mean       std
A                                                              
bar  0.392940  0.130980  0.181231  1.732707  0.577569  1.366330
foo -1.796421 -0.359284  0.912265  2.824590  0.564918  0.884785

对于 Series 分组返回结果的列名默认是函数名,可以使用链式操作修改列名

In [84]: (
   ....:     grouped["C"]
   ....:     .agg([np.sum, np.mean, np.std])
   ....:     .rename(columns={"sum": "foo", "mean": "bar", "std": "baz"})
   ....: )
   ....: 
Out[84]: 
          foo       bar       baz
A                                
bar  0.392940  0.130980  0.181231
foo -1.796421 -0.359284  0.912265

对于 DataFrame 分组,操作类似

In [85]: (
   ....:     grouped.agg([np.sum, np.mean, np.std]).rename(
   ....:         columns={"sum": "foo", "mean": "bar", "std": "baz"}
   ....:     )
   ....: )
   ....: 
Out[85]: 
            C                             D                    
          foo       bar       baz       foo       bar       baz
A                                                              
bar  0.392940  0.130980  0.181231  1.732707  0.577569  1.366330
foo -1.796421 -0.359284  0.912265  2.824590  0.564918  0.884785

注意

通常,输出的列名是唯一的,不能将同一个函数或两个同名函数应用于同一列

In [86]: grouped["C"].agg(["sum", "sum"])
Out[86]: 
          sum       sum
A                      
bar  0.392940  0.392940
foo -1.796421 -1.796421

如果传入的是多个 lambda 函数,pandas 会自动为这些函数重命名为 <lambda_i>

In [87]: grouped["C"].agg([lambda x: x.max() - x.min(), lambda x: x.median() - x.mean()])
Out[87]: 
     <lambda_0>  <lambda_1>
A                          
bar    0.331279    0.084917
foo    2.337259   -0.215962
4.2 命名聚合

GroupBy.agg() 中接受一种特殊的语法,用于控制输出的列名以及特定列的聚合操作,即命名聚合

  • 关键字就是输出的列名
  • 值是元组的形式,第一个元素是要选择的列,第二个元素为对该列执行的操作。pandas 提供了 pandas.NamedAgg 命名元组,其字段为 ['column', 'aggfunc'],是参数设置更加清晰

通常,聚合函数可以是可调用函数或字符串函数名

In [88]: 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],
   ....:     }
   ....: )
   ....: 

In [89]: animals
Out[89]: 
  kind  height  weight
0  cat     9.1     7.9
1  dog     6.0     7.5
2  cat     9.5     9.9
3  dog    34.0   198.0

In [90]: 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=np.mean),
   ....: )
   ....: 
Out[90]: 
      min_height  max_height  average_weight
kind                                        
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

pandas.NamedAgg 只是 namedtuple,与直接传入元组等价

In [91]: animals.groupby("kind").agg(
   ....:     min_height=("height", "min"),
   ....:     max_height=("height", "max"),
   ....:     average_weight=("weight", np.mean),
   ....: )
   ....: 
Out[91]: 
      min_height  max_height  average_weight
kind                                        
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

如果你的列名不是有效的 Python 关键字,可以构建一个字典并解包

In [92]: animals.groupby("kind").agg(
   ....:     **{
   ....:         "total weight": pd.NamedAgg(column="weight", aggfunc=sum)
   ....:     }
   ....: )
   ....: 
Out[92]: 
      total weight
kind              
cat           17.8
dog          205.5

注意:在 Python 3.5 或更早的版本,**kwargs 不会保留键的顺序

Series 也可以使用命名聚合,因为 Series 不需要选择列,所以值就只是函数或字符串函数名

In [93]: animals.groupby("kind").height.agg(
   ....:     min_height="min",
   ....:     max_height="max",
   ....: )
   ....: 
Out[93]: 
      min_height  max_height
kind                        
cat          9.1         9.5
dog          6.0        34.0
4.3 为列应用不同的函数

通过对 aggregate 传递一个字典,你可以为不同的列应用不同的函数

In [94]: grouped.agg({"C": np.sum, "D": lambda x: np.std(x, ddof=1)})
Out[94]: 
            C         D
A                      
bar  0.392940  1.366330
foo -1.796421  0.884785

函数名也可以是字符串,但是使用前必须定义了该函数

In [95]: grouped.agg({"C": "sum", "D": "std"})
Out[95]: 
            C         D
A                      
bar  0.392940  1.366330
foo -1.796421  0.884785

5 转换

transform 方法返回一个与分组对象索引相同(大小相同)的对象,该函数必须:

  • 返回一个与分组大小相同或可广播到分组大小的结果。例如,一个标量,grouped.transform(lambda x: x.iloc[-1])
  • 在组上逐列操作
  • 不能执行原地修改操作

例如,对数据进行分组标准化

In [98]: index = pd.date_range("10/1/1999", periods=1100)

In [99]: ts = pd.Series(np.random.normal(0.5, 2, 1100), index)

In [100]: ts = ts.rolling(window=100, min_periods=100).mean().dropna()

In [101]: ts.head()
Out[101]: 
2000-01-08    0.779333
2000-01-09    0.778852
2000-01-10    0.786476
2000-01-11    0.782797
2000-01-12    0.798110
Freq: D, dtype: float64

In [102]: ts.tail()
Out[102]: 
2002-09-30    0.660294
2002-10-01    0.631095
2002-10-02    0.673601
2002-10-03    0.709213
2002-10-04    0.719369
Freq: D, dtype: float64

In [103]: transformed = ts.groupby(lambda x: x.year).transform(
   .....:     lambda x: (x - x.mean()) / x.std()
   .....: )
   .....: 

标准化之后,均值为 0,方差为 1

# 原始数据
In [104]: grouped = ts.groupby(lambda x: x.year)

In [105]: grouped.mean()
Out[105]: 
2000    0.442441
2001    0.526246
2002    0.459365
dtype: float64

In [106]: grouped.std()
Out[106]: 
2000    0.131752
2001    0.210945
2002    0.128753
dtype: float64

# 转换后的数据
In [107]: grouped_trans = transformed.groupby(lambda x: x.year)

In [108]: grouped_trans.mean()
Out[108]: 
2000    1.167126e-15
2001    2.190637e-15
2002    1.088580e-15
dtype: float64

In [109]: grouped_trans.std()
Out[109]: 
2000    1.0
2001    1.0
2002    1.0
dtype: float64

我们可以对比一下转换前后的数据分布

In [110]: compare = pd.DataFrame({"Original": ts, "Transformed": transformed})

In [111]: compare.plot()

如果转换函数返回的是维度更低的结果,则将会把值进行广播,使其与输入数组大小一样

In [112]: ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min())
Out[112]: 
2000-01-08    0.623893
2000-01-09    0.623893
2000-01-10    0.623893
2000-01-11    0.623893
2000-01-12    0.623893
                ...   
2002-09-30    0.558275
2002-10-01    0.558275
2002-10-02    0.558275
2002-10-03    0.558275
2002-10-04    0.558275
Freq: D, Length: 1001, dtype: float64

也可以使用内置函数,生成一样的结果

In [113]: max = ts.groupby(lambda x: x.year).transform("max")

In [114]: min = ts.groupby(lambda x: x.year).transform("min")

In [115]: max - min
Out[115]: 
2000-01-08    0.623893
2000-01-09    0.623893
2000-01-10    0.623893
2000-01-11    0.623893
2000-01-12    0.623893
                ...   
2002-09-30    0.558275
2002-10-01    0.558275
2002-10-02    0.558275
2002-10-03    0.558275
2002-10-04    0.558275
Freq: D, Length: 1001, dtype: float64

另一种常用的数据转换是,使用组内均值对缺失值进行填补。

例如,有如下数据

>>> data_df = pd.DataFrame(np.random.normal(0.5, 2, size=(1000, 3)), columns=list('ABC'))
>>> data_df.loc[np.random.randint(0, 1000, 100), 'C'] = np.NaN
>>> data_df

            A         B         C
0   -1.807563  0.742651  0.582211
1   -0.004608 -0.252184 -0.599312
2   -0.682971  2.702668  1.314856
3    1.074685  0.203833 -2.223385
4    1.296123  2.436668  2.844688
..        ...       ...       ...
995 -2.413651  3.576030  0.209219
996 -0.501723 -0.510921  0.247469
997 -0.944480 -0.244293 -1.765085
998 -0.121340 -0.633210 -2.152916
999 -0.699248 -3.046279  1.562404

[1000 rows x 3 columns]

补缺失值

In [117]: countries = np.array(["US", "UK", "GR", "JP"])

In [118]: key = countries[np.random.randint(0, 4, 1000)]

In [119]: grouped = data_df.groupby(key)

# Non-NA count in each group
In [120]: grouped.count()
Out[120]: 
      A    B    C
GR  248  248  236
JP  243  243  213
UK  269  269  234
US  240  240  220

In [121]: transformed = grouped.transform(lambda x: x.fillna(x.mean()))

我们可以进行验证,转换前后均值并没有发生变化,但是,转换后已经不包含缺失值了

In [122]: grouped_trans = transformed.groupby(key)

In [123]: grouped.mean()  # 原始数据的分组均值
Out[123]: 
           A         B         C
GR  0.744238  0.682563  0.671818
JP  0.637438  0.406635  0.476871
UK  0.343826  0.649000  0.489756
US  0.430899  0.276287  0.624809

In [124]: grouped_trans.mean()  # 转换后均值不变
Out[124]: 
           A         B         C
GR  0.744238  0.682563  0.671818
JP  0.637438  0.406635  0.476871
UK  0.343826  0.649000  0.489756
US  0.430899  0.276287  0.624809

In [125]: grouped.count()  # 转换前,行数不一致
Out[125]: 
      A    B    C
GR  248  248  236
JP  243  243  213
UK  269  269  234
US  240  240  220

In [126]: grouped_trans.count()  # 转换后,行数一致
Out[126]: 
      A    B    C
GR  248  248  248
JP  243  243  243
UK  269  269  269
US  240  240  240

In [127]: grouped_trans.size()  # count 与 size 的结果一致,不存在缺失值
Out[127]: 
GR    248
JP    243
UK    269
US    240
dtype: int64

发表评论: