哈喽,我是小白~
今儿和大家聊聊Pandas的分组和分区的一些操作!~
在机器学习实验中,Pandas的分组和分区操作可以高效地对数据进行汇总、分割和分析,从而揭示数据的分布规律与特征差异。通过分组汇总操作,如groupby()
和pivot_table()
,可以对不同类别或维度的数据进行精细的聚合分析,帮助挖掘隐藏的模式。分箱操作如cut()
和qcut()
则能够将连续型特征离散化,便于进一步特征工程和模型训练。
今天涉及到的十种Pandas的分组和分区操作有:
groupby() agg() transform() apply() filter() pivot_table() cut() qcut() rolling() expanding()
历史 Pandas 精华
1. groupby()
groupby()
是 Pandas 中最重要的操作之一,用于根据一个或多个列的值对数据进行分组。分组后,你可以对每个分组的数据进行聚合(如求平均、求和等)、转换或过滤。通常,groupby()
与聚合函数结合使用,如 mean()
、sum()
、count()
等。
操作步骤
groupby()
操作通常可以分为三个步骤:
分组:根据一个或多个列对数据进行分组。 应用(apply):在每个分组上应用某种函数或操作,如求平均值或总和。 合并(combine):将操作的结果合并为一个新的 DataFrame
。
使用场景
分析某类数据在不同类别下的变化趋势。 在机器学习中,常用于对类别特征(categorical features)的特征工程。 数据聚合(汇总统计)场景,例如计算各个类别的销售总额、平均值、最小值等。
代码实现
import pandas as pd
# 示例数据
data = {
'Category': ['A', 'B', 'A', 'B', 'C', 'A', 'C', 'B'],
'Value': [10, 20, 30, 40, 50, 60, 70, 80],
'Count': [1, 2, 3, 4, 5, 6, 7, 8]
}
df = pd.DataFrame(data)
# 按 'Category' 列进行分组,计算每个分组的 'Value' 列的平均值
grouped_mean = df.groupby('Category')['Value'].mean()
print("各类别的平均值:\n", grouped_mean)
# 按 'Category' 列进行分组,计算每个分组的 'Value' 和 'Count' 列的总和
grouped_sum = df.groupby('Category').sum()
print("\n各类别的总和:\n", grouped_sum)
# 按 'Category' 分组,并统计每个分组的大小
grouped_size = df.groupby('Category').size()
print("\n各类别的大小:\n", grouped_size)
输出
各类别的平均值:
Category
A 33.333333
B 46.666667
C 60.000000
Name: Value, dtype: float64
各类别的总和:
Value Count
Category
A 100 10
B 140 14
C 120 12
各类别的大小:
Category
A 3
B 3
C 2
dtype: int64
关键点
groupby()
返回的是一个分组对象,必须通过聚合或转换函数才能得到结果。可以根据单列或多列进行分组。 支持多种聚合函数,如 mean()
、sum()
、count()
等。分组后的操作返回的是一个新的 DataFrame
或Series
,具体取决于操作的列数和结果类型。
2. agg()
agg()
用于对 groupby()
的结果进行多重聚合操作。通过 agg()
,你可以在不同列上应用不同的聚合函数,甚至在同一列上应用多个聚合函数。
操作步骤
选择分组对象:通过 groupby()
创建分组对象。应用 agg()
聚合函数:在不同列上指定不同的聚合操作,或者对同一列应用多个函数。返回聚合结果:最终结果是一个 DataFrame
,每一列显示不同聚合操作的结果。
使用场景
在一个数据框中,对不同列进行不同的聚合操作。例如,计算某列的平均值,同时计算另一列的总和。 同时对同一列执行多个操作,如计算平均值、标准差、最大值等。
代码实现
# 按 'Category' 列分组,并对 'Value' 列进行多个聚合操作
grouped_agg = df.groupby('Category').agg({
'Value': ['mean', 'sum', 'max'],
'Count': ['min', 'count']
})
print(grouped_agg)
输出
Value Count
mean sum max min count
Category
A 33.3333 100 60 1 3
B 46.6667 140 80 2 3
C 60.0000 120 70 5 2
关键点
agg()
支持在不同列上应用不同的聚合函数,也支持在同一列上应用多个聚合函数。返回的结果是一个多级索引的 DataFrame
,每一列是对应的聚合操作。
3. transform()
transform()
函数用于将聚合操作的结果“广播”回原始数据框的每一行,而不是直接返回一个汇总后的数据框。transform()
对每个分组执行操作,并返回与输入数据相同大小的结果。
操作步骤
分组:通过 groupby()
创建分组对象。应用 `transform():对每个分组应用函数,并将结果赋值给原始数据中的每一行。 返回原始大小的 DataFrame
或 `Series。
使用场景
在机器学习中,常用于对特征进行标准化或归一化,保持分组间的一致性。
计算每个分组的统计量并将其分配回原始数据框。
代码实现
# 计算每个分组的平均值,并将其广播回原始数据框
df['Mean_Value'] = df.groupby('Category')['Value'].transform('mean')
print(df)
输出
Category Value Count Mean_Value
0 A 10 1 33.333333
1 B 20 2 46.666667
2 A 30 3 33.333333
3 B 40 4 46.666667
4 C 50 5 60.000000
5 A 60 6 33.333333
6 C 70 7 60.000000
7 B 80 8 46.666667
关键点
transform()
与agg()
不同,它返回的结果与原始数据框大小相同。常用于需要保留原始数据框结构的场景,如特征转换、标准化等。
4. apply()
apply()
是一种灵活的函数,它允许你在分组数据上应用自定义函数。apply()
可以返回标量、Series
或 DataFrame
。
操作步骤
分组:通过 groupby()
创建分组对象。应用 `apply():对每个分组应用自定义函数。 返回结果:根据自定义函数的返回值,结果可以是标量、 Series
或DataFrame
。
使用场景
对分组数据进行自定义的复杂计算。 当内置的聚合函数不满足需求时,可以使用 apply()
实现更灵活的操作。
代码实现
# 自定义函数:计算每个分组的最大值减去最小值
def range_func(x):
return x.max() - x.min()
# 按 'Category' 列分组,对 'Value' 列应用自定义函数
grouped_apply = df.groupby('Category')['Value'].apply(range_func)
print(grouped_apply)
输出
Category
A 50
B 60
C 20
Name: Value, dtype: int64
关键点
apply()
非常灵活,适用于不能通过内置函数完成的操作。返回值可以是
标量、Series
或 DataFrame
,具体取决于自定义函数的设计。
继续深入介绍 Pandas 中常用的分组和分区操作,下面是余下的六种操作,它们在数据处理和特征工程中非常实用。
5. filter()
filter()
用于根据某些条件对分组后的数据进行过滤,它可以保留符合条件的整个分组。常用于基于某些聚合结果的过滤操作。
操作步骤
分组:首先使用 groupby()
按某列或多列对数据进行分组。应用过滤条件:通过 filter()
函数过滤分组。过滤条件通常是针对每个分组的某些属性(如分组大小、分组中的某个统计量等)。返回结果:返回符合条件的分组。
使用场景
根据某些条件(如分组的总和大于某个值)筛选分组。 保留符合业务规则的分组。
代码实现
# 示例数据
data = {'Category': ['A', 'B', 'A', 'B', 'C', 'A', 'C', 'B'],
'Value': [10, 20, 30, 40, 50, 60, 70, 80]}
df = pd.DataFrame(data)
# 按 'Category' 列分组,保留 'Value' 列总和大于100的分组
filtered_df = df.groupby('Category').filter(lambda x: x['Value'].sum() > 100)
print(filtered_df)
输出
Category Value
1 B 20
3 B 40
7 B 80
5 A 60
关键点
filter()
保留或删除整个分组,而不是单独的行。使用 lambda 函数,可以灵活地设置过滤条件。
6. pivot_table()
pivot_table()
类似于 Excel 中的透视表,用于基于某些键(key)对数据进行聚合。它能够灵活地对多维数据进行聚合操作,并返回一个类似于透视表的结构化结果。
操作步骤
选择索引和列:指定行、列以及要聚合的数值列。 选择聚合函数:可以使用 mean()
、sum()
等聚合函数。生成透视表:返回一个包含聚合结果的 DataFrame
。
使用场景
当你需要以透视表的方式查看数据时, pivot_table()
非常有用。适用于多维数据聚合。
代码实现
# 示例数据
data = {
'Category': ['A', 'B', 'A', 'B', 'C', 'A', 'C', 'B'],
'SubCategory': ['X', 'X', 'Y', 'Y', 'X', 'Y', 'X', 'X'],
'Value': [10, 20, 30, 40, 50, 60, 70, 80]
}
df = pd.DataFrame(data)
# 创建透视表,按 'Category' 和 'SubCategory' 分组,并对 'Value' 进行聚合
pivot_df = df.pivot_table(values='Value', index='Category', columns='SubCategory', aggfunc='sum')
print(pivot_df)
输出
SubCategory X Y
Category
A 10.0 90.0
B 100.0 40.0
C 120.0 NaN
关键点
pivot_table()
是一种强大的数据透视工具,可以轻松处理多维数据。默认使用 mean()
作为聚合函数,但可以通过aggfunc
参数指定其他聚合函数。
7. cut()
cut()
用于将连续的数值数据分箱(binning),将数据划分为不同的区间(bins),并将每个数据点分配到对应的区间。这是特征工程中常用的操作,尤其是在处理连续型特征时。
操作步骤
定义区间:通过 cut()
函数将数值数据划分为固定区间。分配数据到区间:根据区间将数据进行分配。 返回结果:返回分配后的分箱标签。
使用场景
处理连续型特征,将其离散化为不同的区间。 在分类模型中,将连续变量转化为类别型特征。
代码实现
# 示例数据
values = [22, 25, 27, 35, 45, 55, 65, 75, 85, 95]
# 使用 cut() 将值划分为 4 个区间
bins = pd.cut(values, bins=4)
print(bins)
输出
[(21.927, 40.0], (21.927, 40.0], (21.927, 40.0], (21.927, 40.0], (40.0, 58.0], (40.0, 58.0], (58.0, 75.0], (58.0, 75.0], (75.0, 93.0], (75.0, 93.0]]
Categories (4, interval[float64, right]): [(21.927, 40.0] < (40.0, 58.0] < (58.0, 75.0] < (75.0, 93.0]]
关键点
cut()
常用于对连续数据进行分箱操作。可以手动定义区间,也可以自动生成等宽或等频区间。
8. qcut()
qcut()
是 cut()
的变体,区别在于 cut()
按照等宽区间分箱,而 qcut()
按照等频分箱(每个区间包含相同数量的数据点)。这是处理数据偏态分布时的一种有效方法。
操作步骤
定义分位数:通过 qcut()
根据指定的分位数将数据划分为等频区间。分配数据到区间:根据分位数将数据分配到区间。 返回结果:返回分配后的分箱标签。
使用场景
在数据呈现不均匀分布时,将数据分为等频区间是一个不错的选择。
代码实现
# 使用 qcut() 将数据按分位数划分为 4 个等频区间
q_bins = pd.qcut(values, q=4)
print(q_bins)
输出
[(21.999, 35.0], (21.999, 35.0], (21.999, 35.0], (21.999, 35.0], (35.0, 55.0], (55.0, 65.0], (65.0, 75.0], (75.0, 85.0], (85.0, 95.0], (85.0, 95.0]]
Categories (4, interval[float64, right]): [(21.999, 35.0] < (35.0, 55.0] < (55.0, 65.0] < (65.0, 95.0]]
关键点
qcut()
按等频区间对数据进行分箱,非常适合处理有偏分布的数据。常用于特征工程中,将连续变量转换为离散类别型变量。
9. rolling()
rolling()
用于计算移动窗口(rolling window)上的统计量。移动窗口是一种在时间序列或连续数据上进行聚合计算的工具,它可以对滑动窗口内的数据进行操作,如求平均、求和等。
操作步骤
定义窗口大小:通过 rolling()
指定窗口大小。应用聚合函数:在滚动窗口上应用如 mean()
、sum()
等聚合函数。返回结果:返回一个与原始数据大小相同的 Series
或DataFrame
。
使用场景
常用于时间序列分析,计算移动平均、移动总和等。
数据平滑处理。
代码实现
# 示例数据
ts = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# 计算滚动窗口为 3 的移动平均
rolling_mean = ts.rolling(window=3).mean()
print(rolling_mean)
输出
0 NaN
1 NaN
2 2.0
3 3.0
4 4.0
5 5.0
6 6.0
7 7.0
8 8.0
9 9.0
dtype: float64
关键点
rolling()
可以指定窗口大小和步长。常用于时间序列和信号处理领域,计算平滑的时间序列数据。
10. expanding()
expanding()
是累积窗口的版本,与 rolling()
类似,但它计算的是从开始到当前行的所有数据的聚合。它用于累计和递增计算,如累计总和、累计均值等。
操作步骤
定义窗口:通过 expanding()
创建累积窗口。应用聚合函数:在累积窗口上应用聚合操作,如 sum()
、mean()
等。返回结果:返回一个与原始数据大小相同的 Series
或DataFrame
。
使用场景
累计总和、累计平均等递增计算场景。 常用于分析序列数据中的累积趋势。
代码实现
# 计算累积平均
expanding_mean = ts.expanding().mean()
print(expanding_mean)
输出
0 1.0
1 1.5
2 2.0
3 2.5
4 3.0
5 3.5
6 4.0
7 4.5
8 5.0
9 5.5
dtype: float64
关键点
expanding()
用于从序列的开始到当前点的累计计算。适用于需要观察累积效果的数据分析任务。
完整案例
该案例会模拟一个虚拟的销售数据集,包括日期、产品类别、地区、销售额和折扣等信息。
数据分析任务:
使用 groupby()
对销售额按产品类别和地区进行分组汇总。使用 pivot_table()
创建不同产品类别和地区的销售额透视表。使用 rolling()
计算各类别产品的销售额的移动平均值,探索其时间趋势。使用 cut()
和qcut()
分析销售额的分布情况。结合上述操作,绘制多个可视化图表。
案例代码:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 设置随机种子,确保结果可复现
np.random.seed(42)
# 生成虚拟数据集
n = 500
dates = pd.date_range('2023-01-01', periods=n, freq='D')
categories = np.random.choice(['Electronics', 'Clothing', 'Furniture'], size=n)
regions = np.random.choice(['North', 'South', 'East', 'West'], size=n)
sales = np.random.randint(100, 1000, size=n)
discounts = np.random.uniform(0, 0.3, size=n)
# 构造DataFrame
df = pd.DataFrame({
'Date': dates,
'Category': categories,
'Region': regions,
'Sales': sales,
'Discount': discounts
})
# 1. 按产品类别和地区分组,计算总销售额
grouped_sales = df.groupby(['Category', 'Region'])['Sales'].sum().reset_index()
# 2. 使用 pivot_table 生成透视表
pivot_table = df.pivot_table(values='Sales', index='Category', columns='Region', aggfunc='sum')
# 3. 计算移动平均销售额,使用滚动窗口
df['Rolling_Sales'] = df.groupby('Category')['Sales'].transform(lambda x: x.rolling(window=7).mean())
# 4. 将销售额分为4个区间,使用 cut 和 qcut
df['Sales_Bins'] = pd.cut(df['Sales'], bins=4, labels=['Low', 'Medium', 'High', 'Very High'])
df['Sales_Quartiles'] = pd.qcut(df['Sales'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
# 绘制图形
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 图1: 各类别产品在不同地区的总销售额柱状图
sns.barplot(x='Category', y='Sales', hue='Region', data=grouped_sales, ax=axes[0, 0])
axes[0, 0].set_title('Total Sales by Category and Region')
axes[0, 0].set_xlabel('Category')
axes[0, 0].set_ylabel('Total Sales')
# 图2: 移动平均销售额的时间趋势
for category in df['Category'].unique():
sns.lineplot(x='Date', y='Rolling_Sales', data=df[df['Category'] == category], label=category, ax=axes[0, 1])
axes[0, 1].set_title('Rolling Mean of Sales Over Time by Category')
axes[0, 1].set_xlabel('Date')
axes[0, 1].set_ylabel('Rolling Mean Sales')
# 图3: 使用 cut 的销售额分箱结果的分布
sns.countplot(x='Sales_Bins', data=df, palette='viridis', ax=axes[1, 0])
axes[1, 0].set_title('Sales Distribution by Bins (Using cut)')
axes[1, 0].set_xlabel('Sales Bins')
axes[1, 0].set_ylabel('Count')
# 图4: 使用 qcut 的销售额四分位分布
sns.countplot(x='Sales_Quartiles', data=df, palette='coolwarm', ax=axes[1, 1])
axes[1, 1].set_title('Sales Distribution by Quartiles (Using qcut)')
axes[1, 1].set_xlabel('Sales Quartiles')
axes[1, 1].set_ylabel('Count')
# 调整图形布局
plt.tight_layout()
plt.show()
数据集生成:生成了一个包含
500
条数据的虚拟销售数据集。数据集包含日期
、产品类别
、地区
、销售额
和折扣
五个字段。分组汇总操作:使用
groupby()
按Category
和Region
分组,汇总计算Sales
销售额。透视表操作:使用
pivot_table()
生成透视表,展示不同Category
在各个Region
的总销售额。移动平均计算:使用
rolling()
对Sales
列进行移动平均计算,以探讨不同产品类别在时间序列上的销售趋势。分箱操作:使用
cut()
和qcut()
对Sales
列进行分箱,分别生成等宽和等频的销售额分布。
图1:展示不同产品类别在各个地区的总销售额分布情况,可以观察到不同地区对不同产品的偏好。 图2:展示了不同类别产品的移动平均销售趋势,可以观察各个类别的销售波动情况。 图3:通过 cut()
分箱,展示销售额的区间分布情况,了解销售额的具体分布。图4:通过 qcut()
将销售额按四分位数划分,观察销售额在不同区间的分布情况。