遍历ID列表统计手术块数:寻求高效替代双for循环方案
嘿,我懂你不想用低效的两层for循环来统计手术块的心情——Pandas其实有一套非常高效的向量化操作方案,完全能搞定这个需求,而且代码简洁速度还快!
先明确下需求:我们要给每个患者统计连续手术('o')的块数,比如患者1的手术记录是2015-06-03、2015-07-01(这是一个块),然后2016-03-16、2016-04-13(第二个块),所以总共是2个手术块,对吧?
下面是具体的实现步骤,我会一步步解释:
第一步:准备数据(还原你的示例DataFrame)
首先我们先把你给出的示例数据转换成标准的Pandas DataFrame,确保格式正确:
import pandas as pd data = [ ['1,l', '2015-03-30', 'c'], ['1,l', '2015-06-03', 'o'], ['1,l', '2015-07-01', 'o'], ['1,l', '2015-07-20', 'c'], ['1,l', '2016-03-16', 'o'], ['1,l', '2016-04-13', 'o'], ['1,l', '2016-05-09', 'c'], ['2,l', '2014-12-23', 'c'], ['2,l', '2015-01-21', 'o'], ['2,l', '2015-03-16', 'c'], ['2,l', '2015-11-23', 'o'], ] AllPat = pd.DataFrame(data, columns=['Pat', 'Date', 'Visit']) # 把Date列转换成日期类型,确保后续排序和判断的准确性 AllPat['Date'] = pd.to_datetime(AllPat['Date'])
第二步:核心逻辑实现
我们可以利用Pandas的groupby、shift和cumsum来高效计算手术块:
# 1. 先按患者ID和就诊日期排序,保证就诊顺序严格按时间先后 AllPat_sorted = AllPat.sort_values(['Pat', 'Date']) # 2. 标记当前行是否为手术记录 AllPat_sorted['is_operation'] = AllPat_sorted['Visit'] == 'o' # 3. 对每个患者,判断当前行是否是一个新手术块的起始: # 规则是:当前是手术,且上一行不是手术;或者是该患者的第一条记录且是手术 AllPat_sorted['block_start'] = AllPat_sorted.groupby('Pat')['is_operation'].apply( lambda x: x & ~x.shift(fill_value=False) ) # 4. 按患者分组求和,得到每个患者的手术块总数 operation_blocks = AllPat_sorted.groupby('Pat')['block_start'].sum().reset_index() operation_blocks.columns = ['Pat', 'Operation_Blocks']
运行这段代码后,operation_blocks的结果完全符合预期:
| Pat | Operation_Blocks |
|---|---|
| 1,l | 2 |
| 2,l | 2 |
为什么这个方法比两层循环高效?
- 这里用的都是Pandas的向量化操作,底层是用C实现的,比Python层面的循环快得多——尤其是当你的DataFrame有几万甚至几十万行的时候,性能差距会非常明显。
- 代码逻辑清晰,每一步都有明确的意义,也更容易维护和调试。
额外小提示
如果你的原始数据已经是按患者和日期排序好的,那第一步的sort_values可以省略,但为了代码的鲁棒性,我还是建议加上,避免因为数据顺序混乱导致结果错误。
内容的提问来源于stack exchange,提问作者AO_30
相关产品推荐
相关产品推荐

