Pandas中使用groupby和aggregate对多列数据进行高效分组聚合
参考:pandas groupby aggregate multiple columns
Pandas是Python中强大的数据处理库,其中groupby和aggregate功能为处理大型数据集提供了高效的解决方案。本文将详细介绍如何在Pandas中使用groupby和aggregate对多列数据进行分组聚合,并通过多个示例展示这些功能的实际应用。
1. Pandas中的groupby基础
groupby是Pandas中用于数据分组的核心功能。它允许我们根据一个或多个列的值将数据分成不同的组,然后对每个组应用各种操作。
1.1 基本用法
让我们从一个简单的例子开始:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 按name分组并计算sales的平均值
result = df.groupby('name')['sales'].mean()
print(result)
Output:
在这个例子中,我们创建了一个包含名字、城市、销售额和访问次数的DataFrame。然后,我们使用groupby按’name’列分组,并计算每个人的平均销售额。
1.2 多列分组
Pandas允许我们同时按多个列进行分组:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 按name和city分组,计算sales的总和
result = df.groupby(['name', 'city'])['sales'].sum()
print(result)
Output:
这个例子展示了如何同时按’name’和’city’列进行分组,然后计算每个组的销售总额。
2. aggregate函数的使用
aggregate函数(通常简写为agg)允许我们在分组后的数据上应用多个聚合函数。
2.1 单列多函数聚合
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 按name分组,对sales列应用多个聚合函数
result = df.groupby('name')['sales'].agg(['mean', 'sum', 'max'])
print(result)
Output:
在这个例子中,我们按’name’列分组,然后对’sales’列同时计算平均值、总和和最大值。
2.2 多列多函数聚合
aggregate函数的真正威力在于它可以对多个列应用不同的聚合函数:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 对不同列应用不同的聚合函数
result = df.groupby('name').agg({
'sales': ['mean', 'sum'],
'visits': ['max', 'min']
})
print(result)
Output:
这个例子展示了如何对’sales’列计算平均值和总和,同时对’visits’列计算最大值和最小值。
3. 自定义聚合函数
除了使用Pandas内置的聚合函数,我们还可以定义自己的聚合函数:
import pandas as pd
import numpy as np
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 定义自定义聚合函数
def range_diff(x):
return x.max() - x.min()
# 使用自定义函数进行聚合
result = df.groupby('name').agg({
'sales': ['mean', range_diff],
'visits': ['sum', 'count']
})
print(result)
Output:
在这个例子中,我们定义了一个名为range_diff的自定义函数,用于计算最大值和最小值的差。然后,我们将这个函数与内置函数一起应用于分组后的数据。
4. 重命名聚合结果列
当使用多个聚合函数时,结果的列名可能会变得复杂。我们可以通过以下方式重命名结果列:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 使用自定义名称进行聚合
result = df.groupby('name').agg({
'sales': [('avg_sales', 'mean'), ('total_sales', 'sum')],
'visits': [('max_visits', 'max'), ('min_visits', 'min')]
})
print(result)
Output:
这个例子展示了如何在聚合时为结果列指定自定义名称,使输出更加清晰易读。
5. 使用transform进行组内转换
有时,我们需要在保持原始数据结构的同时进行组内计算。这时可以使用transform方法:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 使用transform计算每个人的平均销售额
df['avg_sales'] = df.groupby('name')['sales'].transform('mean')
print(df)
Output:
这个例子展示了如何使用transform方法为每个人计算平均销售额,并将结果添加为新列。
6. 处理缺失值
在进行分组聚合时,我们可能会遇到缺失值。Pandas提供了多种处理缺失值的方法:
import pandas as pd
import numpy as np
# 创建包含缺失值的示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, np.nan, 300, 150, 250],
'visits': [5, 10, np.nan, 8, 12]
}
df = pd.DataFrame(data)
# 忽略缺失值进行聚合
result = df.groupby('name').agg({
'sales': 'mean',
'visits': 'sum'
})
print(result)
# 使用填充值处理缺失数据
result_filled = df.fillna({'sales': 0, 'visits': 0}).groupby('name').agg({
'sales': 'mean',
'visits': 'sum'
})
print(result_filled)
Output:
这个例子展示了两种处理缺失值的方法:一种是在聚合时自动忽略缺失值,另一种是在聚合前用特定值填充缺失数据。
7. 高级分组技巧
7.1 使用函数进行分组
除了使用列名,我们还可以使用函数来定义分组规则:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 40, 45],
'sales': [100, 200, 300, 400, 500],
'visits': [5, 10, 15, 20, 25]
}
df = pd.DataFrame(data)
# 使用函数进行分组
def age_group(age):
if age < 30:
return 'Young'
elif age < 40:
return 'Middle'
else:
return 'Senior'
result = df.groupby(age_group(df['age'])).agg({
'sales': 'mean',
'visits': 'sum'
})
print(result)
这个例子展示了如何使用自定义函数将年龄分成不同的组,然后基于这些组进行聚合。
7.2 分组后的过滤
有时我们可能只对满足某些条件的组感兴趣:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob', 'Charlie'],
'city': ['New York', 'London', 'Paris', 'New York', 'London', 'Paris'],
'sales': [100, 200, 300, 150, 250, 350],
'visits': [5, 10, 15, 8, 12, 18]
}
df = pd.DataFrame(data)
# 分组后过滤
result = df.groupby('name').filter(lambda x: x['sales'].mean() > 200)
print(result)
Output:
这个例子展示了如何只保留平均销售额超过200的组。
8. 时间序列数据的分组
对于时间序列数据,Pandas提供了特殊的分组方法:
import pandas as pd
import numpy as np
# 创建时间序列数据
dates = pd.date_range('20230101', periods=100)
df = pd.DataFrame({
'date': dates,
'sales': np.random.randint(100, 1000, 100),
'visits': np.random.randint(10, 100, 100)
})
# 按周分组
weekly = df.groupby(pd.Grouper(key='date', freq='W')).agg({
'sales': 'sum',
'visits': 'mean'
})
print(weekly)
Output:
这个例子展示了如何使用Grouper对象按周对时间序列数据进行分组和聚合。
9. 多级索引的处理
groupby操作通常会产生多级索引(MultiIndex)的结果。我们可以使用unstack方法来重塑这些结果:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob', 'Charlie'],
'city': ['New York', 'London', 'Paris', 'London', 'Paris', 'New York'],
'sales': [100, 200, 300, 150, 250, 350],
'visits': [5, 10, 15, 8, 12, 18]
}
df = pd.DataFrame(data)
# 多级分组
result = df.groupby(['name', 'city']).agg({
'sales': 'sum',
'visits': 'mean'
})
# 使用unstack重塑结果
reshaped = result.unstack(level='city')
print(reshaped)
Output:
这个例子展示了如何对多级索引的结果进行重塑,使其更易于阅读和分析。
10. 性能优化技巧
当处理大型数据集时,groupby操作可能会变得很慢。以下是一些优化性能的技巧:
10.1 使用categoricals
对于包含重复值的列,将其转换为categorical类型可以显著提高性能:
import pandas as pd
import numpy as np
# 创建大型示例数据
n = 1000000
df = pd.DataFrame({
'id': np.random.randint(0, 1000, n),
'value': np.random.randn(n)
})
# 将id列转换为categorical
df['id'] = df['id'].astype('category')
# 进行分组操作
result = df.groupby('id')['value'].mean()
print(result.head())
这个例子展示了如何将分组列转换为categorical类型,这对于包含大量重复值的列特别有效。
10.2 使用numba加速
对于自定义聚合函数,我们可以使用numba来加速计算:
import pandas as pd
import numpy as np
from numba import jit
# 创建示例数据
df = pd.DataFrame({
'group': np.random.choice(['A', 'B', 'C'], 1000000),
'value': np.random.randn(1000000)
})
# 使用numba加速的自定义函数
@jit(nopython=True)
def custom_agg(values):
return values.mean() * np.std(values)
# 使用加速后的函数进行聚合
result = df.groupby('group')['value'].agg(custom_agg)
print(result)
这个例子展示了如何使用numba的@jit装饰器来加速自定义聚合函数的执行。
11. 处理大型数据集
当处理非常大的数据集时,我们可能需要使用一些特殊的技巧来避免内存不足的问题:
11.1 使用chunksize
对于无法一次性加载到内存中的数据,我们可以使用chunksize参数分块处理:
import pandas as pd
# 假设我们有一个大型CSV文件
chunk_size = 100000
result = pd.DataFrame()
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
# 对每个chunk进行分组聚合
chunk_result = chunk.groupby('category')['value'].sum()
result = result.add(chunk_result, fill_value=0)
print(result)
这个例子展示了如何使用chunksize参数分块读取大型CSV文件,并对每个块进行分组聚合,最后合并结果。
11.2 使用dask
对于超大型数据集,我们可以考虑使用dask库,它提供了与pandas类似的API,但支持并行计算和out-of-core处理:
import dask.dataframe as dd
# 读取大型数据集
df = dd.read_csv('very_large_file.csv')
# 进行分组聚合
result = df.groupby('category')['value'].mean().compute()
print(result)
这个例子展示了如何使用dask来处理非常大的数据集,dask会自动处理数据分片和并行计算。
12. 高级聚合技巧
12.1 使用named aggregation
Pandas 0.25.0版本引入了named aggregation,它提供了一种更清晰的方式来指定聚合操作:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 使用named aggregation
result = df.groupby('name').agg(
avg_sales=pd.NamedAgg(column='sales', aggfunc='mean'),
total_visits=pd.NamedAgg(column='visits', aggfunc='sum'),
cities=pd.NamedAgg(column='city', aggfunc='nunique')
)
print(result)
Output:
这个例子展示了如何使用named aggregation来指定聚合操作,这种方式使代码更加清晰易读。
12.2 使用groupby和apply组合
有时,我们可能需要对每个组应用更复杂的操作。这时可以使用groupby和apply的组合:
import pandas as pd
# 创建示例数据
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'city': ['New York', 'London', 'Paris', 'New York', 'London'],
'sales': [100, 200, 300, 150, 250],
'visits': [5, 10, 15, 8, 12]
}
df = pd.DataFrame(data)
# 定义复杂的组操作
def group_operation(group):
return pd.Series({
'avg_sales': group['sales'].mean(),
'total_visits': group['visits'].sum(),
'sales_per_visit': group['sales'].sum() / group['visits'].sum()
})
# 应用复杂操作
result = df.groupby('name').apply(group_operation)
print(result)
这个例子展示了如何使用apply方法对每个组应用复杂的操作,这种方法非常灵活,可以执行几乎任何你想要的组内计算。
13. 处理时间序列数据
Pandas对时间序列数据的处理有特别强大的支持:
13.1 按时间间隔重采样
import pandas as pd
import numpy as np
# 创建时间序列数据
dates = pd.date_range('20230101', periods=1000, freq='H')
df = pd.DataFrame({
'timestamp': dates,
'value': np.random.randn(1000)
})
# 按天重采样并计算平均值
daily_avg = df.set_index('timestamp').resample('D')['value'].mean()
print(daily_avg)
这个例子展示了如何将小时级别的数据重采样为日级别,并计算每天的平均值。
13.2 滚动窗口计算
import pandas as pd
import numpy as np
# 创建时间序列数据
dates = pd.date_range('20230101', periods=1000, freq='H')
df = pd.DataFrame({
'timestamp': dates,
'value': np.random.randn(1000)
})
# 计算7天滚动平均
df['rolling_avg'] = df.set_index('timestamp')['value'].rolling('7D').mean()
print(df.head(10))
这个例子展示了如何计算7天的滚动平均值,这在分析时间序列数据的趋势时非常有用。
14. 处理分类数据
对于分类数据,Pandas提供了一些特殊的处理方法:
import pandas as pd
# 创建包含分类数据的DataFrame
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
'category': ['A', 'B', 'A', 'C', 'B'],
'value': [1, 2, 3, 4, 5]
}
df = pd.DataFrame(data)
df['category'] = pd.Categorical(df['category'])
# 按分类进行分组和聚合
result = df.groupby('category')['value'].agg(['mean', 'count'])
print(result)
# 计算每个分类的比例
result['proportion'] = result['count'] / result['count'].sum()
print(result)
这个例子展示了如何处理分类数据,包括将普通列转换为分类类型,以及计算每个分类的比例。
15. 总结
Pandas的groupby和aggregate功能为数据分析提供了强大而灵活的工具。通过本文的详细介绍和多个示例,我们展示了如何使用这些功能来处理各种复杂的数据分析任务。从基本的分组聚合到高级的自定义函数,从处理时间序列数据到优化大型数据集的性能,Pandas都提供了丰富的解决方案。
在实际应用中,选择合适的分组和聚合方法可以大大提高数据处理的效率和结果的可读性。同时,对于大型数据集,合理使用性能优化技巧也是非常重要的。
随着数据分析需求的不断增长和复杂化,熟练掌握Pandas的这些功能将使你能够更加高效地处理各种数据分析任务,从而在数据科学和商业分析领域中脱颖而出。