Pandas中强大的数据分组与聚合:GroupBy和Agg函数详解
Pandas是Python中最流行的数据处理库之一,它提供了强大的数据操作和分析工具。在处理大型数据集时,我们经常需要对数据进行分组和聚合操作,以便更好地理解和分析数据。Pandas的GroupBy和Agg函数就是为此而生的,它们能够帮助我们轻松地对数据进行分组、聚合和统计分析。本文将深入探讨Pandas中GroupBy和Agg函数的使用方法、常见应用场景以及一些高级技巧。
1. GroupBy函数简介
GroupBy函数是Pandas中用于数据分组的核心功能。它允许我们根据一个或多个列的值将数据分成不同的组,然后对每个组进行独立的操作和分析。GroupBy的基本语法如下:
import pandas as pd
# 创建示例数据
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 按城市分组
grouped = df.groupby('city')
在这个例子中,我们创建了一个包含姓名、年龄、城市和薪资信息的DataFrame,然后使用groupby('city')
按城市对数据进行分组。这个操作会返回一个GroupBy对象,我们可以在这个对象上进行进一步的操作。
2. 常见的GroupBy操作
2.1 计算组内统计量
GroupBy对象提供了许多内置的统计函数,如mean()、sum()、count()等,可以直接应用于分组后的数据。
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 计算每个城市的平均薪资
avg_salary = df.groupby('city')['salary'].mean()
print("Average salary by city:", avg_salary)
# 计算每个城市的员工数量
employee_count = df.groupby('city').size()
print("Employee count by city:", employee_count)
Output:
在这个例子中,我们首先计算了每个城市的平均薪资,然后计算了每个城市的员工数量。groupby('city')['salary'].mean()
会返回一个Series,其中索引是城市名,值是该城市的平均薪资。groupby('city').size()
则返回每个城市的员工数量。
2.2 多列分组
GroupBy支持同时按多个列进行分组,这在处理复杂的数据结构时非常有用。
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'],
'age': [25, 30, 35, 28, 32, 40],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London', 'New York'],
'department': ['IT', 'HR', 'Finance', 'IT', 'HR', 'Finance'],
'salary': [50000, 60000, 70000, 55000, 65000, 75000]
})
# 按城市和部门分组,计算平均薪资
avg_salary = df.groupby(['city', 'department'])['salary'].mean()
print("Average salary by city and department:", avg_salary)
Output:
在这个例子中,我们按城市和部门两个维度进行分组,然后计算每个组的平均薪资。结果是一个多级索引的Series,其中第一级索引是城市,第二级索引是部门。
2.3 应用自定义函数
除了使用内置的统计函数,我们还可以使用apply()
方法将自定义函数应用于每个分组。
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 定义自定义函数
def salary_range(group):
return pd.Series({
'min_salary': group['salary'].min(),
'max_salary': group['salary'].max(),
'range': group['salary'].max() - group['salary'].min()
})
# 应用自定义函数
salary_stats = df.groupby('city').apply(salary_range)
print("Salary statistics by city:", salary_stats)
在这个例子中,我们定义了一个salary_range
函数,它计算每个组的最低薪资、最高薪资和薪资范围。然后我们使用apply()
方法将这个函数应用于按城市分组后的数据。结果是一个DataFrame,其中包含了每个城市的薪资统计信息。
3. Agg函数详解
Agg函数是GroupBy操作的一个强大扩展,它允许我们在一次操作中对多个列应用多个聚合函数。Agg函数的灵活性使得它成为数据分析中不可或缺的工具。
3.1 基本用法
Agg函数的基本语法如下:
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 使用agg函数计算多个统计量
stats = df.groupby('city').agg({
'age': ['mean', 'max'],
'salary': ['mean', 'min', 'max']
})
print("Statistics by city:", stats)
Output:
在这个例子中,我们使用agg函数同时计算了每个城市的平均年龄、最大年龄、平均薪资、最低薪资和最高薪资。结果是一个多级列的DataFrame,其中第一级列是原始列名,第二级列是应用的聚合函数。
3.2 使用自定义函数
Agg函数也支持使用自定义函数进行聚合操作。
import pandas as pd
import numpy as np
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 定义自定义函数
def range_func(x):
return x.max() - x.min()
# 使用自定义函数和内置函数
stats = df.groupby('city').agg({
'age': ['mean', range_func],
'salary': ['mean', 'std', range_func]
})
print("Advanced statistics by city:", stats)
Output:
在这个例子中,我们定义了一个range_func
函数来计算数据的范围(最大值减最小值)。然后我们在agg函数中同时使用了这个自定义函数和内置的统计函数。
3.3 重命名聚合结果
当使用agg函数时,结果的列名可能会变得复杂和难以理解。我们可以通过传递一个包含自定义名称的字典来重命名聚合结果。
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 使用自定义名称
stats = df.groupby('city').agg({
'age': [('avg_age', 'mean'), ('max_age', 'max')],
'salary': [('avg_salary', 'mean'), ('min_salary', 'min'), ('max_salary', 'max')]
})
print("Renamed statistics by city:", stats)
Output:
在这个例子中,我们为每个聚合操作指定了一个自定义名称。结果DataFrame的列名将使用这些自定义名称,使得结果更易读和理解。
4. 高级GroupBy和Agg技巧
4.1 过滤分组
有时我们可能只对满足某些条件的组感兴趣。Pandas提供了filter()
方法来实现这一功能。
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'],
'age': [25, 30, 35, 28, 32, 40],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London', 'New York'],
'salary': [50000, 60000, 70000, 55000, 65000, 75000]
})
# 只保留平均薪资大于60000的城市
filtered_df = df.groupby('city').filter(lambda x: x['salary'].mean() > 60000)
print("Filtered DataFrame:", filtered_df)
Output:
在这个例子中,我们使用filter()
方法只保留了平均薪资超过60000的城市的数据。
4.2 转换分组数据
transform()
方法允许我们对分组数据进行转换,并将结果广播回原始DataFrame的形状。
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 计算每个城市的平均薪资,并添加到原始DataFrame
df['city_avg_salary'] = df.groupby('city')['salary'].transform('mean')
print("DataFrame with city average salary:", df)
Output:
在这个例子中,我们使用transform()
方法计算了每个城市的平均薪资,并将结果添加为原始DataFrame的一个新列。
4.3 动态聚合
有时我们可能需要根据数据的特征动态选择聚合函数。我们可以使用字典推导式来实现这一点。
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
'salary': [50000, 60000, 70000, 55000, 65000],
'department': ['IT', 'HR', 'Finance', 'IT', 'HR']
})
# 动态选择聚合函数
agg_funcs = {
'age': 'mean',
'salary': ['mean', 'max'] if df['salary'].mean() > 60000 else 'mean'
}
result = df.groupby('city').agg(agg_funcs)
print("Dynamic aggregation result:", result)
Output:
在这个例子中,我们根据整个DataFrame的平均薪资来决定是否对薪资列应用多个聚合函数。
4.4 分组排序
我们可以在分组后对每个组内的数据进行排序。
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'],
'age': [25, 30, 35, 28, 32, 40],
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London', 'New York'],
'salary': [50000, 60000, 70000, 55000, 65000, 75000]
})
# 在每个城市内按薪资降序排序
sorted_df = df.groupby('city').apply(lambda x: x.sort_values('salary', ascending=False))
print("Sorted DataFrame within groups:", sorted_df)
在这个例子中,我们使用apply()
方法对每个城市内的数据按薪资降序排序。
4.5 分组窗口函数
Pandas提供了强大的窗口函数,可以在分组数据上执行滑动窗口计算。
import pandas as pd
df = pd.DataFrame({
'date': pd.date_range(start='2023-01-01', periods=10),
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'] * 2,
'sales': [100, 150, 200, 120, 180, 90, 160, 210, 130, 170]
})
# 计算每个城市的3天滑动平均销售额
df['rolling_avg_sales'] = df.groupby('city')['sales'].rolling(window=3, min_periods=1).mean().reset_index(level=0, drop=True)
print("DataFrame with rolling average sales:", df)
Output:
在这个例子中,我们使用rolling()
函数计算了每个城市的3天滑动平均销售额。min_periods=1
参数确保即使不足3天的数据也能计算平均值。
5. GroupBy和Agg的性能优化
在处理大型数据集时,GroupBy和Agg操作可能会变得很慢。以下是一些提高性能的技巧:
5.1 使用categoricals
如果分组键是字符串,将其转换为categorical类型可以显著提高性能。
import pandas as pd
import numpy as np
# 创建一个大型DataFrame
df = pd.DataFrame({
'group': np.random.choice(['A', 'B', 'C', 'D'], size=1000000),
'value': np.random.randn(1000000)
})
# 将group列转换为categorical
df['group'] = df['group'].astype('category')
# 执行GroupBy操作
result = df.groupby('group')['value'].mean()
print("Result with categorical group:", result)
在这个例子中,我们将’group’列转换为categorical类型,这可以加速后续的GroupBy操作。
5.2 使用numba加速自定义聚合函数
对于复杂的自定义聚合函数,可以使用numba来加速计算。
import pandas as pd
import numpy as np
from numba import jit
@jit(nopython=True)
def custom_agg(values):
return np.mean(values) * np.median(values)
df = pd.DataFrame({
'group': np.random.choice(['A', 'B', 'C', 'D'], size=1000000),
'value': np.random.randn(1000000)
})
result = df.groupby('group')['value'].agg(custom_agg)
print("Result with numba-accelerated custom aggregation:", result)
在这个例子中,我们使用numba的@jit
装饰器来编译自定义聚合函数,这可以显著提高大规模数据的处理速度。
5.3 使用Dask进行并行处理
对于非常大的数据集,可以考虑使用Dask库来进行并行处理。
import pandas as pd
import dask.dataframe as dd
# 假设我们有一个大型CSV文件
df = dd.read_csv('large_file.csv')
# 执行GroupBy和Agg操作
result = df.groupby('group')['value'].mean().compute()
print("Result with Dask parallel processing:", result)
这个例子展示了如何使用Dask来处理大型CSV文件,并执行GroupBy和Agg操作。Dask会自动将操作分散到多个核心上并行执行。
6. 实际应用案例
让我们通过一些实际应用案例来深入理解GroupBy和Agg的强大功能。
6.1 销售数据分析
import pandas as pd
import numpy as np
# 创建销售数据
sales_data = pd.DataFrame({
'date': pd.date_range(start='2023-01-01', end='2023-12-31', freq='D'),
'product': np.random.choice(['A', 'B', 'C'], size=365),
'region': np.random.choice(['North', 'South', 'East', 'West'], size=365),
'sales': np.random.randint(100, 1000, size=365)
})
# 按月份和产品分组,计算销售总额和平均销售额
monthly_sales = sales_data.groupby([sales_data['date'].dt.to_period('M'), 'product']).agg({
'sales': ['sum', 'mean']
})
print("Monthly sales by product:", monthly_sales)
# 计算每个地区的销售占比
total_sales = sales_data['sales'].sum()
sales_proportion = sales_data.groupby('region')['sales'].sum() / total_sales * 100
print("Sales proportion by region:", sales_proportion)
Output:
这个例子展示了如何分析销售数据,包括计算月度销售统计和各地区的销售占比。
6.2 客户行为分析
import pandas as pd
import numpy as np
# 创建客户行为数据
customer_data = pd.DataFrame({
'customer_id': np.repeat(range(1, 101), 10),
'purchase_date': pd.date_range(start='2023-01-01', periods=1000),
'product_category': np.random.choice(['Electronics', 'Clothing', 'Books', 'Home'], size=1000),
'purchase_amount': np.random.randint(10, 500, size=1000)
})
# 计算每个客户的总购买金额和平均购买金额
customer_stats = customer_data.groupby('customer_id').agg({
'purchase_amount': ['sum', 'mean', 'count']
})
customer_stats.columns = ['total_amount', 'avg_amount', 'purchase_count']
print("Customer purchase statistics:", customer_stats.head())
# 找出购买次数最多的前10个客户
top_customers = customer_stats.nlargest(10, 'purchase_count')
print("Top 10 customers by purchase count:", top_customers)
# 分析每个产品类别的销售情况
category_sales = customer_data.groupby('product_category').agg({
'purchase_amount': ['sum', 'mean', 'count']
})
category_sales.columns = ['total_sales', 'avg_sale', 'sale_count']
print("Sales statistics by product category:", category_sales)
Output:
这个例子展示了如何分析客户购买行为,包括计算客户统计数据、识别最活跃的客户以及分析产品类别的销售情况。
7. 常见问题和解决方案
在使用GroupBy和Agg函数时,可能会遇到一些常见问题。以下是一些问题及其解决方案:
7.1 处理缺失值
import pandas as pd
import numpy as np
df = pd.DataFrame({
'group': ['A', 'A', 'B', 'B', 'C'],
'value': [1, np.nan, 3, 4, 5]
})
# 默认情况下,聚合函数会忽略NaN值
result1 = df.groupby('group')['value'].mean()
print("Result ignoring NaN:", result1)
# 使用skipna=False来包含NaN值
result2 = df.groupby('group')['value'].mean(skipna=False)
print("Result including NaN:", result2)
这个例子展示了如何处理分组数据中的缺失值。默认情况下,大多数聚合函数会忽略NaN值,但我们可以通过设置skipna=False
来改变这一行为。
7.2 处理多级索引
import pandas as pd
df = pd.DataFrame({
'date': pd.date_range(start='2023-01-01', periods=10),
'city': ['New York', 'London'] * 5,
'product': ['A', 'B', 'A', 'B', 'C'] * 2,
'sales': [100, 150, 200, 120, 180, 90, 160, 210, 130, 170]
})
# 按日期和城市分组
grouped = df.groupby(['date', 'city'])
# 计算每组的销售总额
sales_sum = grouped['sales'].sum()
print("Sales sum with multi-level index:", sales_sum)
# 重置索引以便于进一步处理
sales_sum_reset = sales_sum.reset_index()
print("Sales sum with reset index:", sales_sum_reset)
Output:
这个例子展示了如何处理GroupBy操作后产生的多级索引。我们可以使用reset_index()
方法将多级索引转换为普通列。
7.3 处理大型数据集的内存问题
当处理非常大的数据集时,可能会遇到内存不足的问题。一种解决方案是使用chunksize
参数分批处理数据。
import pandas as pd
# 假设我们有一个大型CSV文件
filename = 'large_file.csv'
chunk_size = 100000 # 每次读取的行数
# 初始化结果DataFrame
result = pd.DataFrame()
# 分批读取和处理数据
for chunk in pd.read_csv(filename, chunksize=chunk_size):
# 对每个chunk进行GroupBy和Agg操作
chunk_result = chunk.groupby('group')['value'].agg(['mean', 'sum'])
result = result.add(chunk_result, fill_value=0)
# 计算最终结果
result['mean'] = result['sum'] / result['count']
print("Result from processing large file in chunks:", result)
这个例子展示了如何使用chunksize
参数分批读取和处理大型CSV文件,从而避免内存溢出问题。
8. 总结
Pandas的GroupBy和Agg函数是数据分析中不可或缺的工具。它们提供了强大而灵活的方法来对数据进行分组、聚合和统计分析。通过本文的详细介绍和丰富的示例,我们深入探讨了这些函数的基本用法、高级技巧以及实际应用案例。
关键要点包括:
1. GroupBy函数允许我们根据一个或多个列的值将数据分成不同的组。
2. Agg函数扩展了GroupBy的功能,允许我们在一次操作中对多个列应用多个聚合函数。
3. 我们可以使用自定义函数进行更复杂的聚合操作。
4. 性能优化技巧,如使用categoricals和numba,可以显著提高大规模数据处理的效率。
5. 实际应用中,GroupBy和Agg函数可以用于各种数据分析任务,如销售数据分析和客户行为分析。
掌握这些技能将使你能够更有效地处理和分析复杂的数据集,从而做出更好的数据驱动决策。随着数据规模和复杂性的不断增加,熟练运用这些工具将成为数据分析师和数据科学家的重要技能。