Pandas DataFrame分组条件赋值教程:实现灵活的箱子分配策略

心靈之曲
发布: 2025-12-04 12:19:02
原创
599人浏览过

Pandas DataFrame分组条件赋值教程:实现灵活的箱子分配策略

本教程详细阐述了如何在pandas dataframe中高效、可扩展地实现复杂的组内条件赋值逻辑。通过利用`groupby().transform()`结合自定义函数,我们解决了根据商店对工人进行箱子分配的问题,其中包含最大分配量限制和单人商店特殊规则。此方法避免了手动迭代和硬编码`iloc`索引,极大地提升了代码的灵活性和维护性。

1. 问题背景与挑战

在数据处理中,我们经常需要根据特定分组(如本例中的“商店”)对数据进行复杂的条件计算和赋值。本教程的核心任务是为每个商店的工人分配“最优箱子数”(optimal_boxes),遵循以下规则:

  1. 工人优先级: 按worker列的数值顺序分配。
  2. 最大分配量: 每位工人最多分配100个箱子。
  3. 单人商店特例: 如果一个商店只有一名工人,则该工人获得该商店所有箱子的总和,即使超过100个。
  4. 剩余分配: 当所有优先工人已分配满100个箱子后,剩余的箱子将分配给下一个优先的工人。
  5. 可扩展性: 解决方案必须能够处理任意数量的工人分组,避免为每个分组大小编写重复的逻辑。

我们从以下示例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)
登录后复制

期望的输出结果如下:

   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
登录后复制

2. 非可扩展的初始尝试

最初的解决方案可能倾向于使用groupby().apply()结合一系列if/elif语句来处理不同数量工人的情况。例如:

# 这是一个不可扩展的示例,仅用于说明问题
def box_optimizer_unscalable(x):
    if x['optimal_boxes'].count() == 1:
        x['optimal_boxes'].iloc[0] = x['boxes'].sum()
        return x
    elif x['optimal_boxes'].count() == 2:
        # 简化逻辑,实际问题中会有更复杂的累加
        total_boxes = x['boxes'].sum()
        assigned = 0
        if total_boxes > 100:
            x['optimal_boxes'].iloc[0] = 100
            assigned += 100
        else:
            x['optimal_boxes'].iloc[0] = total_boxes
            assigned += total_boxes

        remaining = total_boxes - assigned
        x['optimal_boxes'].iloc[1] = min(100, remaining) # 假设只剩一个工人
        return x
    # ... 更多的 elif 条件来处理 count() == 3, 4, ...
    return x # 返回未修改的x以防万一

# df_stack_exchange.groupby('store', as_index=False, group_keys=False).apply(box_optimizer_unscalable)
登录后复制

这种方法的主要缺点在于:

  • 缺乏可扩展性: 每次组内工人数量增加时,都需要手动添加新的elif条件和相应的iloc赋值逻辑。
  • 代码冗余: 相似的逻辑在不同的elif分支中重复出现。
  • 维护困难: 随着业务规则的变化,修改和测试变得复杂。

3. 使用 groupby().transform() 实现可扩展解决方案

为了克服上述挑战,我们可以利用Pandas的groupby().transform()方法。transform()的强大之处在于它允许我们对每个组应用一个函数,并返回一个与原始DataFrame具有相同索引和长度的Series或DataFrame,这使得直接将结果赋值回原始DataFrame成为可能。

核心思想是创建一个自定义函数,该函数接收一个组的Series(例如,boxes列的一个子集),并返回一个表示该组内optimal_boxes分配结果的列表或Series。

蚂蚁PPT
蚂蚁PPT

AI在线智能生成PPT

蚂蚁PPT 113
查看详情 蚂蚁PPT

3.1 自定义分配函数 assign_boxes

def assign_boxes(s: pandas.Series) -> list:
    """
    根据给定的箱子系列,分配最优箱子数。
    遵循每人最多100个箱子,单人商店全部分配的规则。

    参数:
        s (pandas.Series): 某个商店中所有工人的 'boxes' 列值。
                           索引顺序即为工人优先级。

    返回:
        list: 一个列表,包含按优先级分配给每个工人的 'optimal_boxes' 值。
    """
    total_boxes_in_store = s.sum()  # 计算当前商店的箱子总数
    num_workers_in_store = len(s)   # 当前商店的工人数量

    # 确定可以分配满100个箱子的工人数量 (d)
    # 如果是单人商店 (num_workers_in_store == 1),则 len(s)-1 = 0,d 会是 0。
    # 这样确保了单人商店的工人会通过 'total_boxes_in_store - 100*d' 获得所有箱子。
    d = min(total_boxes_in_store // 100, num_workers_in_store - 1)

    # 构建分配结果列表
    # 1. 前 d 个工人每人分配 100 个箱子
    assigned_list = [100] * d

    # 2. 剩余的箱子分配给第 d+1 个工人
    remaining_boxes = total_boxes_in_store - (100 * d)
    assigned_list.append(remaining_boxes)

    # 3. 如果还有多余的工人,但没有箱子可分配,则分配 0
    # len(s) - d - 1 是指:总工人数 - 已分配满100箱子的工人 - 获得剩余箱子的工人
    assigned_list.extend([0] * (num_workers_in_store - d - 1))

    return assigned_list
登录后复制

3.2 应用 groupby().transform()

将assign_boxes函数应用到DataFrame上:

# 初始化DataFrame
df = pandas.DataFrame(data_stack_exchange)

# 对 'store' 列进行分组,然后对 'boxes' 列应用 assign_boxes 函数
# transform 会确保返回的 Series 与原始 df 的索引对齐
df['optimal_boxes'] = df.groupby('store')['boxes'].transform(assign_boxes)

print("\n优化后的DataFrame:")
print(df)
登录后复制

运行上述代码将得到期望的输出结果,并且该方案对不同数量工人的商店具有完全的可扩展性。

4. 详细代码解析与示例

我们来深入理解 assign_boxes 函数的逻辑,并通过几个示例进行说明。

4.1 函数逻辑分解

  • total_boxes_in_store = s.sum(): 计算当前组(即当前商店)中所有工人拥有的箱子总数。
  • num_workers_in_store = len(s): 获取当前组的工人数量。
  • d = min(total_boxes_in_store // 100, num_workers_in_store - 1): 这是核心逻辑之一。
    • total_boxes_in_store // 100: 计算总箱子数可以分配给多少个“满100箱子”的工人。
    • num_workers_in_store - 1: 表示除了最后一个工人之外,有多少个工人可以被分配100个箱子。
    • min(...): 取两者中的较小值。
      • 如果商店只有一名工人 (num_workers_in_store == 1),那么 num_workers_in_store - 1 为 0。此时 d 必然为 0。
      • 如果箱子总数不足以分配给所有工人每人100个,d 将由 total_boxes_in_store // 100 决定。
      • 如果箱子总数足够多,d 将由 num_workers_in_store - 1 决定,即除了最后一个工人,所有优先工人都会分到100个。
  • assigned_list = [100] * d: 创建一个列表,包含 d 个 100,表示前 d 个工人每人分到100个箱子。
  • remaining_boxes = total_boxes_in_store - (100 * d): 计算分配完前 d 个工人后,还剩下多少箱子。
  • assigned_list.append(remaining_boxes): 将剩余的箱子分配给下一个工人(即第 d+1 个工人)。
    • 单人商店特例处理: 如果 d 为 0(单人商店),remaining_boxes 将等于 total_boxes_in_store - (100 * 0),即 total_boxes_in_store。这确保了单人商店的工人得到所有箱子。
  • assigned_list.extend([0] * (num_workers_in_store - d - 1)): 如果在分配完前 d 个工人(每人100个)和第 d+1 个工人(获得剩余箱子)之后,还有其他工人,但已经没有箱子可分配,那么这些工人将获得 0 个箱子。
    • num_workers_in_store - d - 1 计算的是剩余未分配箱子的工人数量。

4.2 示例演练

示例 1: 商店 A (单人商店)

  • s = pd.Series([105])
  • total_boxes_in_store = 105
  • num_workers_in_store = 1
  • d = min(105 // 100, 1 - 1) = min(1, 0) = 0
  • assigned_list = [100] * 0 = []
  • remaining_boxes = 105 - (100 * 0) = 105
  • assigned_list.append(105) = [105]
  • assigned_list.extend([0] * (1 - 0 - 1)) = assigned_list.extend([0] * 0) = []
  • 返回: [105] (正确)

示例 2: 商店 D (多工人,箱子充足)

  • s = pd.Series([70, 210, 50, 0]) (注意,这里的s是原始boxes值,不是optimal_boxes的中间结果)
  • total_boxes_in_store = 70 + 210 + 50 + 0 = 330
  • num_workers_in_store = 4
  • d = min(330 // 100, 4 - 1) = min(3, 3) = 3
  • assigned_list = [100] * 3 = [100, 100, 100]
  • remaining_boxes = 330 - (100 * 3) = 30
  • assigned_list.append(30) = [100, 100, 100, 30]
  • assigned_list.extend([0] * (4 - 3 - 1)) = assigned_list.extend([0] * 0) = []
  • 返回: [100, 100, 100, 30] (正确)

5. 总结与注意事项

  • groupby().transform() 的优势: 这种方法是处理组内计算并返回与原始DataFrame相同形状结果的理想选择。它避免了显式循环,提高了代码的执行效率和可读性。
  • 函数式编程思维: 将复杂的业务逻辑封装在一个纯函数中,该函数只接受一个组的数据并返回该组的结果,这使得代码更模块化、更易于测试。
  • 索引对齐: transform 自动处理了结果与原始DataFrame的索引对齐问题,无需手动管理。
  • 优先级处理: 在本例中,worker列的数值顺序隐式地定义了优先级。groupby()操作默认会保持组内元素的原始顺序,因此s Series的顺序就是工人的优先级顺序。

通过采用这种基于groupby().transform()的策略,我们成功地实现了一个既高效又高度可扩展的Pandas DataFrame组内条件赋值解决方案,完美应对了复杂的业务规则。

以上就是Pandas DataFrame分组条件赋值教程:实现灵活的箱子分配策略的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号