讲解python中groupby()的应用及groupby案例分析

本文详细介绍了Pandas库中DataFrame的分组与聚合操作,包括单类和多类分组,时间分组,以及对不同列应用聚合函数的方法。通过实例展示了如何使用`groupby()`、`agg()`、`mean()`、`describe()`等函数,进行数据统计分析,如计算均值、标准差,按年份、月份分组等。此外,还展示了如何在分组后重命名结果,使用自定义函数,以及如何选择满足特定条件的分组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.单类分组

A.groupby('性别')

首先,我们有一个变量A,数据类型是DataFrame,想要按照【性别】进行分组,得到的结果是一个Groupby对象,还没有进行任何的运算。

describe()

描述组内数据的基本统计量

A.groupby("性别").describe().unstack()
  • 只有数字类型的列数据才会计算统计

  • 示例里面数字类型的数据有两列 【班级】和【身高】

但是,我们并不需要统计班级的均值等信息,只需要【身高】,所以做一下小的改动:

A.groupby("性别")["身高"].describe().unstack()

unstack() 索引重排

2.多类分组

A.groupby( [“班级”,“性别”])

单独用groupby,我们得到的还是一个 Groupby 对象。

mean() 组内均值计算

DataFrame的很多函数可以直接运用到Groupby对象上。

A.groupby( ["班级","性别"]).agg([np.sum, np.mean, np.std]) # 一次计算了三个

我们还可以一次运用多个函数计算

agg() 分组多个运算

# 对sales进行操作,按4列进行分组,并求 [ 'item_id' ]列的频数
group = sales.groupby(['state_id', 'store_id', 'cat_id', 'dept_id'], as_index=False)['item_id'].count()

as_index=False,保持原来的数据索引结果不变,表示分组的四列[‘state_id’, ‘store_id’, ‘cat_id’, ‘dept_id’]不做索引,而是作为dataframe的列输出。

对于数据的分组和分组运算主要是指groupby函数的应用,具体函数的规则如下:

df.groupby([df[属性],df[属性]) (指分类的属性,数据的限定定语,可以有多个 ).mean()(对于数据的计算方式——函数名称)

分组计算后重命名
对分组计算进行for循环
分组后调用自定义函数
# 聚合后重命名,ak_dict是'filled_ntf'列经过CapacityAys.weibull_para计算后的结果
# 'filled_ntf_mean'是'filled_ntf'列经过mean()计算后的结果
ws_ym_mean_ak = temp.groupby(by=['year', 'month'], as_index=False)\
    .agg(ak_dict=('filled_ntf', CapacityAys.weibull_para), filled_ntf_mean=('filled_ntf', 'mean'))
    
for date, df in temp0.groupby(['year', 'month']): # date=('year', 'month')
    date # 'year', month数据,通过date[0]和date[1]获取
    df#分组后的数据

# as_index=True 就是要把['year', 'month']合并起来作为索引,调用自定义类函数CapacityAys().weibull_para
ak_value = temp.groupby(['year', 'month'], as_index=True)['filled_风速_mean_ntf'].agg(CapacityAys().weibull_para)

3.时间分组

时间序列可以直接作为index,或者有一列是时间序列,差别不是很大。 这里仅仅演示,某一列为时间序列。

为 A 新增一列【生日】,由于分隔符 “/” 的问题,我们查看列属性,【生日】的属性并不是日期类型

(1) 按照【生日】的【年份】进行分组,看看有多少人是同龄?

A["生日"] = pd.to_datetime(A["生日"],format ="%Y/%m/%d")  # 转化为时间格式

A.groupby(A["生日"].apply(lambda x:x.year)).count()  # 按照【生日】的【年份】分组

(2) 同一年作为一个小组,小组内生日靠前的那一位作为小队长:

A.sort_values("生日", inplace=True) # 按时间排序

A.groupby(A["生日"].apply(lambda x:x.year),as_index=False).first()

as_index=False  # 保持原来的数据索引结果不变

first() 保留第一个数据

Tail(n=1) 保留最后n个数据

再进一步:

(3) 想要找到哪个月只有一个人过生日

A.groupby(A["生日"].apply(lambda x:x.month),as_index=False) # 到这里是按月分组

A.groupby(A["生日"].apply(lambda x:x.month),as_index=False).filter(lambda x: len(x)==1)
  • filter() 对分组进行过滤,保留满足()条件的分组
  • 用 first(),tail()截取每组前后几个数据
  • 用 apply()对每组进行(自定义)函数运算
  • 用 filter()选取满足特定条件的分组

4. groupby之后对不同列运用聚合函数

4.1 方法1

这种方法会导致多级索引

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': lambda x: x.max() - x.min()})

              a                   b         c         d
            sum       max      mean       sum  <lambda>
group                                                  
0      0.864569  0.446069  0.466054  0.969921  0.341399
1      1.478872  0.843026  0.687672  1.754877  0.672401

4.2 方法2

def f(x):
    d = {}
    d['a_sum'] = x['a'].sum()
    d['a_max'] = x['a'].max()
    d['b_mean'] = x['b'].mean()
    d['c_d_prodsum'] = (x['c'] * x['d']).sum()
    return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum'])

df.groupby('group').apply(f)

         a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.864569  0.446069  0.466054     0.173711
1      1.478872  0.843026  0.687672     0.630494

这种方法很好。在groupby之后的每个子DF,可以运用聚合函数;
这里自己构建完成的Series
列索引是平铺的,比较直观;
** 也可以这样简写:**

df.groupby('group') \
  .apply(lambda x: pd.Series({
      'a_sum'       : x['a'].sum(),
      'a_max'       : x['a'].max(),
      'b_mean'      : x['b'].mean(),
      'c_d_prodsum' : (x['c'] * x['d']).sum()
  })
)

          a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.530559  0.374540  0.553354     0.488525
1      1.433558  0.832443  0.460206     0.053313

4.3 方法3

In [26]: f = {'A':['sum','mean'], 'B':['prod']}

In [27]: df.groupby('GRP').agg(f)
Out[27]:
            A                   B
          sum      mean      prod
GRP
0    0.719580  0.359790  0.102004
1    0.454824  0.227412  0.034060

这其实是一种变形,把agg变成一个map单独传入
结果对于列来说,仍然是二级索引;

4.4 方法4

df.groupby('group').agg(
             a_sum=('a', 'sum'),
             a_mean=('a', 'mean'),
             b_mean=('b', 'mean'),
             c_sum=('c', 'sum'),
             d_range=('d', lambda x: x.max() - x.min())
)

          a_sum    a_mean    b_mean     c_sum   d_range
group                                                  
0      0.947337  0.473668  0.871939  0.838150  0.320543
1      0.604149  0.302074  0.656902  0.542985  0.057681

这种方法更加直观,我比较喜欢;

有个变种可以这样写:

4.5 最后,回顾一下单个列的写法

 In [84]: animals.groupby("kind").height.agg(
       ....:     min_height='min',
       ....:     max_height='max',
       ....: )
       ....: 
    Out[84]: 
          min_height  max_height
    kind                        
    cat          9.1         9.5
    dog          6.0        34.0

或者这样:

# 聚合单列
grouped_single = df.groupby('Team').agg({'Age': ['mean', 'min', 'max']})
grouped_single.columns = ['age_mean', 'age_min', 'age_max']
grouped_single = grouped_single.reset_index()

# 聚合多列
grouped_multiple = df.groupby(['Team', 'Pos']).agg({'Age': ['mean', 'min', 'max']})
grouped_multiple.columns = ['age_mean', 'age_min', 'age_max']
grouped_multiple = grouped_multiple.reset_index()

5. 其它应用案例

data_all_profile.groupby('is_turn').size()
Out[3]: 
is_turn
0     6470
1    38194
3    78764
dtype: int64
data_all_profile.groupby('is_turn').size().reset_index(name='count')
Out[4]: 
   is_turn  count
0        0   6470
1        1  38194
2        3  78764

5.2 案例2

data_count_by_bin_1027 = data_1027_unique['extreme_wind_bin'].value_counts()
valid_bins_1027 = data_count_by_bin_1027[data_count_by_bin_1027 >= 3].index
data_1027_unique.groupby('extreme_wind_bin')['V50-TI_category'].apply(lambda x: (x == 'high').mean()).loc[
        valid_bins_1027]

这段代码的含义可以分为以下几个部分进行解释:

  1. data_1027_unique.groupby('extreme_wind_bin'):

    • 这个部分是对 data_1027_unique 数据框按照 extreme_wind_bin 列进行分组。每个组对应一个 extreme_wind_bin(极端风速区间)。
  2. ['V50-TI_category']:

    • 这是选取每个分组中的 V50-TI_category 列,它包含了"high"和"low"两个类别,表示每个数据点是否属于"高湍流强度"类别。
  3. apply(lambda x: (x == 'high').mean()):

    • apply() 方法对每个分组应用一个自定义的函数。这里的 lambda x: (x == 'high').mean() 是一个匿名函数:
      • 它检查每个分组中的 V50-TI_category 列中值是否为 “high”(x == 'high')。
      • 这会生成一个布尔数组,其中 “high” 为 True,非 “high” 为 False
      • mean() 方法计算 True 值的比例(True 被视为 1,False 被视为 0),也就是计算每个风速区间中 “high” 的比例。
  4. .loc[valid_bins_1027]:

    • 这个部分是基于 valid_bins_1027 对结果进行筛选,只保留 valid_bins_1027 中的风速区间。valid_bins_1027 是之前筛选出的符合特定条件(如数据量大于等于100)的有效区间。

总结
这段代码的作用是:

  • data_1027_unique 数据进行按 extreme_wind_bin 列分组。
  • 计算每个风速区间中 V50-TI_category 列为 “high” 的比例。
  • 最终,返回符合 valid_bins_1027 中的风速区间的结果。
5.2.1 举例

下面用一个简单的例子来解释这段代码 apply(lambda x: (x == 'high').mean())

假设有一个数据框 df,其中一列表示某个分组(例如 extreme_wind_bin),另一列是 V50-TI_category,表示是否属于“high”类别。

import pandas as pd

# 创建一个简单的示例数据框
data = {
    'extreme_wind_bin': ['bin1', 'bin1', 'bin1', 'bin2', 'bin2', 'bin3', 'bin3', 'bin3', 'bin3'],
    'V50-TI_category': ['high', 'low', 'high', 'low', 'low', 'high', 'high', 'low', 'high']
}
df = pd.DataFrame(data)

print(df)

输出结果:

  extreme_wind_bin V50-TI_category
0             bin1             high
1             bin1              low
2             bin1             high
3             bin2              low
4             bin2              low
5             bin3             high
6             bin3             high
7             bin3              low
8             bin3             high

分组后应用 apply(lambda x: (x == 'high').mean())

result = df.groupby('extreme_wind_bin')['V50-TI_category'].apply(lambda x: (x == 'high').mean())
print(result)

解释:

  1. 分组操作

    • 根据 extreme_wind_bin 列将数据分为 3 组:bin1, bin2, 和 bin3
    • bin1 包含三行,bin2 包含两行,bin3 包含四行。
  2. lambda x: (x == 'high').mean()

    • 对每个分组的 V50-TI_category 列应用这个 lambda 函数。它的意思是:检查每个分组中 V50-TI_category 是否等于 “high”,并计算 “high” 出现的比例。

    逐个分组解释

    • bin1

      • ['high', 'low', 'high'] 转换为 [True, False, True],True 表示 “high”。
      • 计算平均值:(1 + 0 + 1) / 3 = 0.6667,所以 bin1 的 “high” 比例是 66.67%。
    • bin2

      • ['low', 'low'] 转换为 [False, False],都不是 “high”。
      • 计算平均值:(0 + 0) / 2 = 0.0,所以 bin2 的 “high” 比例是 0%。
    • bin3

      • ['high', 'high', 'low', 'high'] 转换为 [True, True, False, True]
      • 计算平均值:(1 + 1 + 0 + 1) / 4 = 0.75,所以 bin3 的 “high” 比例是 75%。
  3. 结果

extreme_wind_bin
bin1    0.666667
bin2    0.000000
bin3    0.750000
Name: V50-TI_category, dtype: float64

总结

apply(lambda x: (x == 'high').mean())

  • V50-TI_category 列中的 “high” 转换为布尔值 True,其它值为 False
  • 通过 mean() 计算分组内 “high” 出现的比例,即 True 的平均值。

在这个例子中,bin1 的 “high” 出现比例是 66.67%,bin2 是 0%,bin3 是 75%。
参考链接:
[1]链接1

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值