
本文探讨了如何使用 pandas 高效且可扩展地处理数据框中按组分配值的场景。针对传统 `iloc` 手动赋值方式的局限性,文章提出了一种基于 `groupby().transform()` 的优化方法,通过一个简洁的函数实现了按优先级和最大值限制(如每人最多100个箱子,单人组全部分配)的智能分配,极大地提升了代码的可维护性和性能。
在数据分析和处理中,我们经常需要根据特定条件对数据框中的分组数据进行值分配。一个常见场景是,在每个组内,按照一定的优先级和限制(例如,每个工作者最多分配100个箱子,但如果只有一个工作者,则分配所有箱子),将总数分配给组内的各个成员。
传统上,一些开发者可能会尝试使用 iloc 结合 if/elif 语句来手动处理每个组内成员的赋值。然而,这种方法存在显著的局限性:
本文将介绍一种更为高效和可扩展的 Pandas 解决方案,它利用 groupby().transform() 结合一个简洁的函数来优雅地解决这个问题。
首先,我们创建一个示例 Pandas 数据框,它包含商店(store)、工作者(worker)、现有箱子数量(boxes)以及待填充的优化箱子数量(optimal_boxes)。
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("原始数据框:")
print(df_stack_exchange)输出的原始数据框如下:
store worker boxes optimal_boxes 0 A 1 105 0 1 B 1 90 0 2 B 2 100 0 3 C 1 80 0 4 C 2 10 0 5 C 3 200 0 6 D 1 70 0 7 D 2 210 0 8 D 3 50 0 9 D 4 0 0
我们的目标是根据以下规则填充 optimal_boxes 列:
为了说明问题,我们简要回顾一下原始问题中提出的基于 iloc 的解决方案。虽然它能达到预期结果,但其扩展性极差。
# 这是一个低效且不推荐的解决方案,仅用于说明问题
def box_optimizer_inefficient(x):
total_boxes_in_group = x['boxes'].sum()
group_size = x['optimal_boxes'].count()
# 初始化一个与组大小相同的列表来存储分配结果
assigned_boxes = [0] * group_size
remaining_to_assign = total_boxes_in_group
if group_size == 1:
assigned_boxes[0] = total_boxes_in_group
else:
for i in range(group_size):
if remaining_to_assign >= 100:
assigned_boxes[i] = 100
remaining_to_assign -= 100
else:
assigned_boxes[i] = remaining_to_assign
remaining_to_assign = 0
# 确保不会分配负数
if remaining_to_assign < 0:
remaining_to_assign = 0
x['optimal_boxes'] = assigned_boxes
return x
# df_stack_exchange_function = df_stack_exchange.groupby('store', as_index=False, group_keys=False).apply(box_optimizer_inefficient)
# print(df_stack_exchange_function)上述代码的 if/elif 结构在原始问题中更加繁琐,需要为每个可能的组大小编写重复的代码块。这种方法不仅代码量大,而且在组大小变动时需要不断修改函数,难以维护。
为了解决上述问题,我们可以设计一个更通用的函数,并利用 Pandas 的 groupby().transform() 方法。transform() 方法的特点是它会将函数的结果广播回原始数据框的相应位置,确保输出的长度与输入组的长度一致。
我们定义一个 assign_boxes 函数,它接收一个 Pandas Series(代表一个组的 boxes 列),并返回一个与该 Series 长度相同的列表或 Series,其中包含计算出的 optimal_boxes 值。
def assign_boxes(s):
"""
根据业务规则为每个工作者组分配箱子。
参数:
s (pandas.Series): 一个商店中所有工作者的 'boxes' 值的 Series。
返回:
list: 包含每个工作者分配到的 'optimal_boxes' 值的列表。
"""
total_boxes = s.sum() # 计算当前组(商店)的总箱子数
num_workers = len(s) # 当前组(商店)的工作者数量
# 特殊情况:如果只有一个工作者,他将获得所有箱子
if num_workers == 1:
return [total_boxes]
# 计算可以分配满100个箱子的工作者数量
# min(total_boxes // 100, num_workers - 1) 是关键:
# total_boxes // 100 给出理论上能分满100箱子的工人数量
# num_workers - 1 确保至少有一个工人会获得剩余的箱子(而不是被计算为满100箱子)
# 这样,即使总箱子数超过 (num_workers - 1) * 100,最后一个工人也会得到剩余的箱子,而不是0
num_full_100_boxes_workers = min(total_boxes // 100, num_workers - 1)
# 构建分配结果列表
# 1. 前 num_full_100_boxes_workers 个工作者获得 100 个箱子
assigned = [100] * num_full_100_boxes_workers
# 2. 计算剩余箱子,分配给下一个工作者
remaining_boxes = total_boxes - 100 * num_full_100_boxes_workers
assigned.append(remaining_boxes)
# 3. 如果还有未分配的工作者,他们获得 0 个箱子(因为箱子已经分完)
# (num_workers - num_full_100_boxes_workers - 1) 是指除了那些分到100个和分到剩余箱子的工人之外的工人数量
assigned.extend([0] * (num_workers - num_full_100_boxes_workers - 1))
return assigned
# 应用函数到数据框
df_stack_exchange['optimal_boxes'] = df_stack_exchange.groupby('store')['boxes'].transform(assign_boxes)
print("\n优化后的数据框:")
print(df_stack_exchange)经过 assign_boxes 函数处理后的数据框 df_stack_exchange 如下所示:
store worker boxes optimal_boxes 0 A 1 105 105 1 B 1 90 100 2 B 2 100 90 3 C 1 80 100 4 C 2 10 100 5 C 3 200 90 6 D 1 70 100 7 D 2 210 100 8 D 3 50 100 9 D 4 0 30
这个结果与预期完全一致。
让我们深入分析 assign_boxes 函数的关键逻辑:
total_boxes = s.sum() 和 num_workers = len(s):
if num_workers == 1: return [total_boxes]:
num_full_100_boxes_workers = min(total_boxes // 100, num_workers - 1):
*`assigned = [100] num_full_100_boxes_workers`**:
*`remaining_boxes = total_boxes - 100 num_full_100_boxes_workers`**:
assigned.append(remaining_boxes):
*`assigned.extend([0] (num_workers - num_full_100_boxes_workers - 1))`**:
为了更好地理解,我们以 store 'D' 的数据为例: s = pd.Series([70, 210, 50, 0]) (原始 boxes 值)
num_workers 不等于1。
num_full_100_boxes_workers = min(330 // 100, 4 - 1)
assigned = [100] * 3 -> [100, 100, 100]
remaining_boxes = 330 - (100 * 3) = 330 - 300 = 30
assigned.append(30) -> [100, 100, 100, 30]
assigned.extend([0] * (4 - 3 - 1)) -> assigned.extend([0] * 0) -> [] (没有额外的0需要添加)
最终返回 [100, 100, 100, 30],这正是 store 'D' 的预期 optimal_boxes 值。
通过 groupby().transform() 结合一个精心设计的函数,我们成功地实现了一个可扩展且高效的数据框分组值分配方案。这种方法避免了手动 iloc 索引和冗长的 if/elif 结构,使代码更简洁、更易于维护,并能更好地适应未来数据规模和规则的变化。
注意事项:
掌握 groupby().transform() 是 Pandas 高级数据处理的关键技能之一,它在许多需要对分组数据进行计算并广播结果的场景中都非常有用。
以上就是利用 Pandas 高效分配数据框中的分组值:一种可扩展的解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号