0

0

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

心靈之曲

心靈之曲

发布时间:2025-12-04 12:19:02

|

628人浏览过

|

来源于php中文网

原创

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。

Frase
Frase

Frase是一款出色的长篇 AI 写作工具,快速创建seo优化的内容。

下载

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组内条件赋值解决方案,完美应对了复杂的业务规则。

相关专题

更多
Python 时间序列分析与预测
Python 时间序列分析与预测

本专题专注讲解 Python 在时间序列数据处理与预测建模中的实战技巧,涵盖时间索引处理、周期性与趋势分解、平稳性检测、ARIMA/SARIMA 模型构建、预测误差评估,以及基于实际业务场景的时间序列项目实操,帮助学习者掌握从数据预处理到模型预测的完整时序分析能力。

52

2025.12.04

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

751

2023.08.22

append用法
append用法

append是一个常用的命令行工具,用于将一个文件的内容追加到另一个文件的末尾。想了解更多append用法相关内容,可以阅读本专题下面的文章。

343

2023.10.25

python中append的用法
python中append的用法

在Python中,append()是列表对象的一个方法,用于向列表末尾添加一个元素。想了解更多append的更多内容,可以阅读本专题下面的文章。

1073

2023.11.14

python中append的含义
python中append的含义

本专题整合了python中append的相关内容,阅读专题下面的文章了解更多详细内容。

175

2025.09.12

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

11

2026.01.19

微信聊天记录删除恢复导出教程汇总
微信聊天记录删除恢复导出教程汇总

本专题整合了微信聊天记录相关教程大全,阅读专题下面的文章了解更多详细内容。

79

2026.01.18

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

109

2026.01.16

全民K歌得高分教程大全
全民K歌得高分教程大全

本专题整合了全民K歌得高分技巧汇总,阅读专题下面的文章了解更多详细内容。

153

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 6.3万人学习

Rust 教程
Rust 教程

共28课时 | 4.6万人学习

Git 教程
Git 教程

共21课时 | 2.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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