Python Pandas库中的数据分组与聚合操作
Python Pandas库中的数据分组与聚合操作
数据分组基础概念
在数据分析过程中,经常需要根据某些特定的条件对数据进行分组,然后对每个分组进行独立的操作。Pandas 库提供了强大的分组(groupby)功能,它允许我们按照一个或多个键对数据进行分组,并对每个组应用相同的函数。
分组操作通常包含三个步骤:
- 拆分(split):根据指定的键将数据拆分成多个组。例如,在一个包含学生成绩的 DataFrame 中,可以根据学生所在的班级进行拆分。
- 应用(apply):对每个组独立地应用某个函数,这个函数可以是聚合函数(如求和、求平均值等),也可以是转换函数(如标准化数据)。
- 合并(combine):将应用函数后的结果合并成一个新的 DataFrame 或 Series。
创建示例数据
为了更好地理解和演示数据分组与聚合操作,我们首先创建一些示例数据。假设我们有一个包含不同城市不同日期的温度数据:
import pandas as pd
import numpy as np
data = {
'city': ['Beijing', 'Beijing', 'Shanghai', 'Shanghai', 'Guangzhou', 'Guangzhou'],
'date': ['2023-01-01', '2023-01-02', '2023-01-01', '2023-01-02', '2023-01-01', '2023-01-02'],
'temperature': [2, 4, 6, 8, 10, 12]
}
df = pd.DataFrame(data)
df['date'] = pd.to_datetime(df['date'])
print(df)
上述代码创建了一个 DataFrame,包含城市(city)、日期(date)和温度(temperature)三个列。我们将日期列转换为 datetime 类型,以便后续可能的日期相关操作。
按单个列分组
最常见的分组方式是按 DataFrame 中的单个列进行分组。例如,我们想知道每个城市的平均温度:
grouped_city = df.groupby('city')
average_temperature = grouped_city['temperature'].mean()
print(average_temperature)
在这段代码中,首先使用 groupby('city')
方法根据城市列对 DataFrame 进行分组,这一步创建了一个 GroupBy
对象。然后,我们对这个 GroupBy
对象中的 temperature
列应用 mean
函数,计算每个城市温度的平均值。最终得到的结果是一个 Series,索引为城市名称,值为对应的平均温度。
按多个列分组
有时,我们可能需要按多个列进行分组。例如,我们想知道每个城市每天的平均温度:
grouped_city_date = df.groupby(['city', 'date'])
average_temperature_city_date = grouped_city_date['temperature'].mean()
print(average_temperature_city_date)
这里使用 groupby(['city', 'date'])
按城市和日期两个列进行分组,得到的结果是一个具有多层索引(MultiIndex)的 Series,索引分别为城市和日期,值为对应的平均温度。
聚合操作
聚合操作是对分组后的数据应用统计函数,以获得汇总信息。除了前面提到的 mean
函数外,Pandas 还支持许多其他聚合函数,如 sum
、count
、min
、max
等。
多个聚合函数同时应用
我们可以对一个分组对象同时应用多个聚合函数。例如,我们想知道每个城市温度的总和、平均值、最小值和最大值:
agg_functions = {
'temperature': ['sum','mean','min','max']
}
result = df.groupby('city').agg(agg_functions)
print(result)
上述代码中,通过 agg
方法并传入一个字典,字典的键是列名,值是一个包含多个聚合函数名称的列表。这样就可以对每个城市的温度列同时应用这四个聚合函数,结果是一个 DataFrame,列名是多层索引,分别表示列名和聚合函数名称。
自定义聚合函数
除了使用内置的聚合函数,我们还可以定义自己的聚合函数。例如,我们定义一个函数来计算温度的标准差与平均值的比值:
def std_mean_ratio(x):
return x.std() / x.mean()
custom_agg = {
'temperature': std_mean_ratio
}
custom_result = df.groupby('city').agg(custom_agg)
print(custom_result)
在这个例子中,我们定义了 std_mean_ratio
函数,然后在 agg
方法中使用这个自定义函数。结果是一个 DataFrame,包含每个城市温度的标准差与平均值的比值。
转换操作
与聚合操作不同,转换操作返回的结果与原始数据具有相同的长度,它对每个组内的数据应用函数,并将结果广播(broadcast)回原始数据的索引。
例如,我们想对每个城市的温度数据进行标准化(将每个值减去该城市的平均温度,再除以该城市温度的标准差):
def standardize(x):
return (x - x.mean()) / x.std()
standardized_temperature = df.groupby('city')['temperature'].transform(standardize)
df['standardized_temperature'] = standardized_temperature
print(df)
这里定义了 standardize
函数,然后使用 transform
方法对每个城市的温度数据应用这个函数。transform
方法返回的结果长度与原始数据中 temperature
列相同,因此可以直接添加为 DataFrame 的一个新列。
过滤操作
过滤操作允许我们根据某些条件筛选出符合要求的组。例如,我们只想保留平均温度大于 5 的城市的数据:
filtered_df = df.groupby('city').filter(lambda x: x['temperature'].mean() > 5)
print(filtered_df)
在这段代码中,filter
方法接受一个函数作为参数,这个函数会应用到每个组上。只有当函数返回 True
时,对应的组才会被保留在结果中。这里使用了 lambda 函数来计算每个城市温度的平均值,并根据平均值是否大于 5 来决定是否保留该组数据。
分组后的迭代
有时,我们需要对每个分组进行更复杂的操作,这时可以通过迭代 GroupBy
对象来实现。例如,我们想打印每个城市的名称以及对应的温度数据:
for city, group in df.groupby('city'):
print(f"City: {city}")
print(group)
在这个循环中,city
是分组的键(城市名称),group
是对应组的 DataFrame。通过这种方式,我们可以对每个组进行任意自定义的操作。
数据分组与聚合的性能优化
当处理大规模数据时,数据分组与聚合操作的性能可能会成为一个问题。以下是一些优化建议:
- 选择合适的数据类型:确保 DataFrame 中的列使用最适合的数据类型。例如,如果某列只包含整数且范围较小,可以使用
np.int8
或np.int16
来减少内存占用,从而提高操作速度。 - 避免不必要的中间计算:尽量在一次操作中完成所有需要的聚合或转换,避免多次对数据进行分组和计算。
- 使用并行计算:对于非常大规模的数据,可以考虑使用 Dask 等支持并行计算的库。Dask 可以在多台机器或多个 CPU 核心上并行执行分组与聚合操作,显著提高计算速度。
应用场景
- 销售数据分析:在销售数据中,可以按地区、产品类别等进行分组,计算每个组的销售额、销售量、平均销售价格等指标,帮助企业了解不同地区、不同产品的销售情况,从而制定相应的营销策略。
- 网站流量分析:按日期、用户来源等对网站流量数据进行分组,分析不同时间段、不同来源的用户访问量、停留时间等,以便优化网站推广和用户体验。
- 医疗数据分析:在医疗数据中,按患者的年龄、性别、疾病类型等进行分组,分析每个组的治疗效果、康复时间等,为医疗决策提供依据。
分组与聚合操作的常见问题及解决方法
- 空值问题:在数据分组与聚合过程中,如果数据中存在空值(NaN),可能会影响结果。可以在分组之前使用
dropna
方法去除包含空值的行,或者在聚合函数中使用skipna=True
参数(大多数聚合函数默认skipna=True
)。
# 去除包含空值的行
df = df.dropna()
# 聚合时跳过空值(默认行为)
result = df.groupby('city')['temperature'].mean()
- 索引问题:在进行多列分组后,结果可能具有多层索引,这在后续操作中可能会带来一些不便。可以使用
reset_index
方法将多层索引转换为普通列。
grouped_result = df.groupby(['city', 'date'])['temperature'].mean()
result_without_multiindex = grouped_result.reset_index()
- 数据类型不一致问题:如果分组依据的列数据类型不一致,可能会导致分组错误。在分组之前,确保相关列的数据类型一致。例如,如果某列应该是数值类型但包含了字符串,需要先进行数据类型转换。
# 将某列转换为数值类型
df['column_name'] = pd.to_numeric(df['column_name'], errors='coerce')
结合其他 Pandas 功能
- 与排序功能结合:在分组聚合后,我们通常希望对结果进行排序。例如,我们想按平均温度从高到低对城市进行排序:
average_temperature = df.groupby('city')['temperature'].mean()
sorted_average_temperature = average_temperature.sort_values(ascending=False)
print(sorted_average_temperature)
- 与数据筛选功能结合:我们可以先对数据进行筛选,然后再进行分组聚合。例如,我们只考虑日期在 2023 年 1 月 1 日之后的数据,并计算每个城市的平均温度:
filtered_df = df[df['date'] > pd.to_datetime('2023-01-01')]
average_temperature = filtered_df.groupby('city')['temperature'].mean()
print(average_temperature)
通过深入理解和灵活运用 Pandas 库中的数据分组与聚合操作,我们能够高效地处理和分析各种类型的数据,从复杂的数据集中提取有价值的信息,为决策提供有力支持。无论是简单的统计分析还是复杂的业务逻辑实现,这些操作都是数据分析过程中不可或缺的工具。