Python Pandas - GroupBy
Pandas DataFrame - GroupBy 操作
Section titled “Pandas DataFrame - GroupBy 操作”数据分组是数据分析中的一个基本操作。Pandas 的 groupby 操作允许您高效地对数据的子集进行计算。它通常包括三个步骤:
- 拆分 (Splitting):根据某些标准(例如,某列中的值)将数据分成组。
- 应用 (Applying):独立地对每个组应用一个函数(例如,计算均值、总和或自定义转换)。
- 组合 (Combining):将结果组合成新的数据结构(如 DataFrame 或 Series)。
在“应用 (Apply)”步骤中的常见应用包括:
- 聚合 (Aggregation):为每个组计算汇总统计信息(例如,计数、均值、标准差)。
- 转换 (Transformation):执行组特定的计算,返回与输入组具有相同形状的对象(例如,在每个组内标准化数据)。
- 过滤 (Filtration):根据组级别的计算或条件丢弃整个组(例如,只保留包含足够数量数据点的组)。
我们来创建一个示例 DataFrame 以说明这些操作:
# 导入 pandas 库import pandas as pdimport numpy as np
# 示例数据 (为保持一致性,将 'kings' 更正为 'Kings')ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
print(df)输出:
Team Rank Year Points0 Riders 1 2014 8761 Riders 2 2015 7892 Devils 2 2014 8633 Devils 3 2015 6734 Kings 3 2014 7415 Kings 4 2015 8126 Kings 1 2016 7567 Kings 1 2017 7888 Riders 2 2016 6949 Royals 4 2014 70110 Royals 1 2015 80411 Riders 2 2017 690将数据拆分为组
Section titled “将数据拆分为组”您可以使用 groupby() 方法将 Pandas 对象(如 DataFrame)拆分为组。指定分组标准的常见方式包括:
df.groupby('column_name'): 按单个列中的值进行分组。df.groupby(['col1', 'col2']): 按多个列中的值的组合进行分组。df.groupby(level='index_level'): 如果 DataFrame 具有 MultiIndex,则按索引级别进行分组。
单独应用 groupby() 方法并不会立即进行任何计算,而是返回一个 DataFrameGroupBy 对象,该对象包含关于组的信息。
示例:按单个列进行分组
Section titled “示例:按单个列进行分组”# 导入 pandas 库import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
# 创建一个 GroupBy 对象grouped_by_team = df.groupby('Team')
print(grouped_by_team)输出(表示形式可能因 Pandas 版本略有差异):
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x...>要查看哪些行属于哪个组,您可以检查 GroupBy 对象的 .groups 属性。它返回一个字典,其中键是组名,值是属于该组的索引标签。
# 导入 pandas 库import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
grouped_by_team = df.groupby('Team')
print(grouped_by_team.groups)输出(索引类型可能因 Pandas 版本而异,例如,Index 而不是 Int64Index):
{'Devils': [2, 3], 'Kings': [4, 5, 6, 7], 'Riders': [0, 1, 8, 11], 'Royals': [9, 10]}示例:按多个列进行分组
Section titled “示例:按多个列进行分组”按多个列分组会创建分层组。
# 导入 pandas 库import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
# 按 Team 和 Year 两列进行分组grouped_by_team_year = df.groupby(['Team', 'Year'])
print(grouped_by_team_year.groups)输出:
{('Devils', 2014): [2], ('Devils', 2015): [3], ('Kings', 2014): [4], ('Kings', 2015): [5], ('Kings', 2016): [6], ('Kings', 2017): [7], ('Riders', 2014): [0], ('Riders', 2015): [1], ('Riders', 2016): [8], ('Riders', 2017): [11], ('Royals', 2014): [9], ('Royals', 2015): [10]}您可以遍历 GroupBy 对象。每次迭代都会产生一个元组,其中包含组名(对于多列分组,可能是一个元组)和原始 DataFrame 中相应的子集。
# 导入 pandas 库import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
grouped_by_year = df.groupby('Year')
print("Iterating through groups based on Year:") # 遍历按年份分组的组:for name, group_df in grouped_by_year: print(f"\nYear: {name}") print(group_df)输出:
Iterating through groups based on Year:
Year: 2014 Team Rank Year Points0 Riders 1 2014 8762 Devils 2 2014 8634 Kings 3 2014 7419 Royals 4 2014 701
Year: 2015 Team Rank Year Points1 Riders 2 2015 7893 Devils 3 2015 6735 Kings 4 2015 81210 Royals 1 2015 804
Year: 2016 Team Rank Year Points6 Kings 1 2016 7568 Riders 2 2016 694
Year: 2017 Team Rank Year Points7 Kings 1 2017 78811 Riders 2 2017 690遍历对于执行不适合标准聚合或转换模式的复杂操作非常有用。
使用 get_group() 方法检索对应于单个特定组名的 DataFrame。
# 导入 pandas 库import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
grouped_by_year = df.groupby('Year')
# 获取 2014 年的数据group_2014 = grouped_by_year.get_group(2014)print(group_2014)输出:
Team Rank Year Points0 Riders 1 2014 8762 Devils 2 2014 8634 Kings 3 2014 7419 Royals 4 2014 701聚合 (Aggregation)
Section titled “聚合 (Aggregation)”聚合为每个组计算一个(或多个)汇总统计信息,生成一个更小的数据结构,其中索引通常对应于组名。
最常见的聚合方式是使用 aggregate() 方法(或其别名 agg())。您可以对特定列或整个组应用聚合函数。
# 导入 pandas 库import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
grouped_by_year = df.groupby('Year')
# 计算每年的平均分数# 使用 numpy 函数mean_points_np = grouped_by_year['Points'].agg(np.mean)print("Mean Points per Year (using np.mean):") # 每年的平均分数 (使用 np.mean):print(mean_points_np)
# 使用 pandas 字符串别名 (更常见)mean_points_str = grouped_by_year['Points'].agg('mean')print("\nMean Points per Year (using 'mean'):") # 每年的平均分数 (使用 'mean'):print(mean_points_str)输出:
Mean Points per Year (using np.mean):Year2014 795.252015 769.502016 725.002017 739.00Name: Points, dtype: float64
Mean Points per Year (using 'mean'):Year2014 795.252015 769.502016 725.002017 739.00Name: Points, dtype: float64size() 方法是获取每个组中行数的便捷方式:
import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
grouped_by_team = df.groupby('Team')
# 获取每个组的大小print(grouped_by_team.size())输出:
TeamDevils 2Kings 4Riders 4Royals 2dtype: int64注意:size() 会计算 NaN 值,而 count() 不会。如果您在不指定列的情况下应用 agg,它会应用于所有有效(通常是数值)列。
# 示例: 在 GroupBy 对象上使用 agg('size') (隐式应用于所有列)print("\nUsing grouped_by_team.agg('size'):") # 使用 grouped_by_team.agg('size'):# 注意: 这可能不是最直观的用法# print(grouped_by_team.agg('size')) # 如果聚合多个列,这通常会产生一个 DataFrame# 更常见的做法可能是这样计算每个组的大小:print(grouped_by_team.agg(size=('Team', 'size')))输出:
Using grouped_by_team.agg('size'): sizeTeamDevils 2Kings 4Riders 4Royals 2应用多个聚合函数
Section titled “应用多个聚合函数”您可以将函数列表(或字符串别名)传递给 agg(),以一次计算多个汇总统计信息。结果是一个具有分层列的 DataFrame。
# 导入 pandas 库import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
grouped_by_team = df.groupby('Team')
# 计算每个团队分数的总和、均值和标准差agg_stats = grouped_by_team['Points'].agg(['sum', 'mean', 'std'])print(agg_stats)输出:
sum mean stdTeamDevils 1536 768.00 134.350288Kings 3107 776.75 31.899321Riders 3049 762.25 88.567771Royals 1505 752.50 72.831998您还可以使用字典为不同列应用不同的聚合:df.groupby('Team').agg({'Points': ['mean', 'sum'], 'Rank': 'mean'})
转换 (Transformation)
Section titled “转换 (Transformation)”转换将一个函数应用于每个组,并返回一个对象(如 Series 或 DataFrame),该对象与原始对象具有相同的索引。这对于需要与原始数据对齐的组特定计算非常有用,例如在组内对数据进行中心化或按组填充缺失值。
为此使用 transform() 方法。传递给 transform 的函数必须返回一个标量值或一个与输入组块具有相同形状的 Series/DataFrame。
# 导入 pandas 库import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
grouped_by_team = df.groupby('Team')
# 示例: 计算每个团队组内 'Points' 的 z-score# z-score = (值 - 组均值) / 组标准差zscore = lambda x: (x - x.mean()) / x.std()
# 对 'Points' 列应用转换# 注意: 如果未指定列,transform 会将函数应用于每个数值列# 为了清晰起见,我们将在分组后选择特定的列transformed_scores = grouped_by_team[['Points', 'Rank']].transform(zscore)print(transformed_scores)
# 注意: 如果一个组只有一个成员,其标准差为 NaN,导致 z-score 为 NaN。# 如果一个组的标准差为零 (所有值都相同),z-score 将为 NaN 或 Inf。输出(如果使用了随机数据生成,值可能会有所不同;此处使用固定数据):
Points Rank0 1.284327 -1.3363061 0.302029 0.2672612 0.707107 -0.7071073 -0.707107 0.7071074 -1.120330 0.4811255 1.105721 1.4433766 -0.650947 -1.0825327 0.350004 -1.0825328 -0.770596 0.2672619 -0.707107 0.70710710 0.707107 -0.70710711 -0.815759 0.267261过滤 (Filtration)
Section titled “过滤 (Filtration)”过滤允许您根据在组级别评估的条件丢弃整个组。filter() 方法接受一个函数,该函数将一个组(作为 DataFrame/Series)作为输入,如果应保留该组,则返回 True,否则返回 False。
常见用例:移除未达到最小大小阈值的组。
import pandas as pdimport numpy as np
ipl_data = { 'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Kings', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals', 'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2], 'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017], 'Points': [876, 789, 863, 673, 741, 812, 756, 788, 694, 701, 804, 690]}df = pd.DataFrame(ipl_data)
# 过滤掉在数据集中出现少于 3 次的团队filtered_df = df.groupby('Team').filter(lambda group: len(group) >= 3)
print(filtered_df)输出(只有 ‘Kings’ 和 ‘Riders’ 组保留,因为它们各有 4 条记录):
Team Rank Year Points0 Riders 1 2014 8761 Riders 2 2015 7894 Kings 3 2014 7415 Kings 4 2015 8126 Kings 1 2016 7567 Kings 1 2017 7888 Riders 2 2016 69411 Riders 2 2017 690在这个示例中,lambda 函数 lambda group: len(group) >= 3 检查每个团队组的大小。只有满足此条件(大小 >= 3)的组才会包含在最终的 filtered_df 中。