
本教程介绍如何在pandas dataframe中高效地为分组数据分配值,尤其是在需要根据组内总和和优先级进行条件分配时。针对传统`groupby().apply()`结合手动`iloc`操作的低效和不可伸缩性,本文提出了一种利用`transform`函数与列表操作相结合的优雅解决方案,实现了高度可伸缩和易于维护的代码,有效处理了复杂的分组分配逻辑。
在数据处理中,我们经常需要对DataFrame进行分组操作,并根据组内的特定逻辑为每个元素分配新的值。一个常见的场景是,我们有一个包含不同“门店”(store)和“员工”(worker)的DataFrame,每个员工拥有一定数量的“箱子”(boxes)。我们的目标是计算每个员工“最优箱子数”(optimal_boxes),分配规则如下:
原始的实现方式通常会使用groupby().apply()结合条件语句(如if/elif)和手动索引(iloc)来更新每个分组的值。然而,这种方法在分组大小不确定或较大时,会导致代码冗长、难以维护且不可伸缩。
为了演示,我们创建一个示例DataFrame:
import pandas
import numpy
data_stack_exchange = {'store': ['A','B', 'B', 'C', 'C', 'C', 'D', 'D', 'D', 'D'],
'worker': [1,1,2,1,2,3,1,2,3,4],
'boxes': [105, 90, 100, 80, 10, 200, 70, 210, 50, 0],
'optimal_boxes': [0,0,0,0,0,0,0,0,0,0]}
df_stack_exchange = pandas.DataFrame(data_stack_exchange)
print("原始DataFrame:")
print(df_stack_exchange)期望输出的optimal_boxes列:
| store | worker | boxes | optimal_boxes |
|---|---|---|---|
| A | 1 | 105 | 105 |
| B | 1 | 90 | 100 |
| B | 2 | 100 | 90 |
| C | 1 | 80 | 100 |
| C | 2 | 10 | 100 |
| C | 3 | 200 | 90 |
| D | 1 | 70 | 100 |
| D | 2 | 210 | 100 |
| D | 3 | 50 | 100 |
| D | 4 | 0 | 30 |
原始问题中提供了一个使用groupby().apply()和一系列elif语句来处理不同分组大小的函数。
def box_optimizer(x):
# ... 省略了详细的if/elif代码块 ...
if x['optimal_boxes'].count() == 1:
x['optimal_boxes'].iloc[0] = x['boxes'].sum()
return x
elif x['optimal_boxes'].count() == 2:
# 手动更新 iloc[0], iloc[1]
pass # 实际代码会更复杂
# ... 更多 elif 语句 ...
return x # 返回修改后的分组DataFrame这种方法的主要问题在于:
为了解决上述问题,我们可以利用groupby().transform()函数,它允许我们将一个函数应用于每个分组,并返回一个与原始DataFrame具有相同索引的Series或DataFrame,从而实现高效的列更新。
核心思想是为每个分组设计一个通用的分配逻辑函数,该函数不依赖于分组的大小,而是动态计算每个员工应得的箱子数。
我们定义一个assign_boxes函数,它接收一个分组的boxes Series作为输入,并返回一个表示optimal_boxes的列表。
def assign_boxes(s):
"""
根据分配规则为每个分组的员工分配最优箱子数。
s: 一个Pandas Series,代表一个'store'分组的'boxes'列。
"""
total = s.sum() # 计算当前分组(门店)的箱子总和
# 计算有多少员工可以分到完整的100个箱子
# min(total // 100, len(s) - 1) 是关键:
# - total // 100: 最多能有几个员工分到100个箱子
# - len(s) - 1: 除了最后一个员工,还有多少个员工
# 这样可以确保:
# 1. 如果只有一个员工,d为0,该员工将获得所有箱子(total - 100*0)
# 2. 避免给超过实际员工数的员工分配100个箱子
d = min(total // 100, len(s) - 1)
# 构建分配列表
# 前 d 个员工获得 100 箱
# 第 d+1 个员工获得剩余所有箱子
# 剩余员工(如果有)获得 0 箱
assigned_list = ([100] * d # 前 d 个员工获得 100 箱
+ [total - 100 * d] # 第 d+1 个员工获得剩余箱子
+ [0] * (len(s) - d - 1)) # 剩余员工获得 0 箱
return assigned_list
# 应用函数到DataFrame
df_stack_exchange['optimal_boxes'] = df_stack_exchange.groupby('store')['boxes'].transform(assign_boxes)
print("\n更新后的DataFrame:")
print(df_stack_exchange)让我们通过几个具体的例子来理解assign_boxes函数的运作方式。
示例 1: Store D 的箱子分配 (s = pd.Series([70, 210, 50, 0]))
假设一个分组的boxes Series为 s = pd.Series([70, 210, 50, 0]) (对应原始DataFrame中Store D的boxes值)。
这与期望的Store D的分配结果一致:第一个员工100,第二个100,第三个100,第四个30。
示例 2: Store B 的箱子分配 (s = pd.Series([90, 100]))
假设一个分组的boxes Series为 s = pd.Series([90, 100]) (对应原始DataFrame中Store B的boxes值)。
这与期望的Store B的分配结果一致:第一个员工100,第二个90。
示例 3: Store A 的箱子分配 (s = pd.Series([105]))
假设一个分组的boxes Series为 s = pd.Series([105]) (对应原始DataFrame中Store A的boxes值)。
这与期望的Store A的分配结果一致:单个员工获得所有105箱。
通过groupby().transform()结合一个通用分配函数,我们实现了:
这种模式在处理各种分组内条件性数据转换时都非常有用,是Pandas数据处理中的一个强大工具。在设计分组操作时,应优先考虑transform或向量化操作,以提升代码质量和执行效率。
以上就是Pandas DataFrame分组动态分配值:避免手动iloc的灵活方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号