如何对pandas dataframe应用多条件删除或选取特定行
Pandas DataFrame 按规则筛选实现问题
原始数据
我有如下DataFrame:
id outcome 0 3 no 1 3 no 2 3 no 3 3 yes 4 3 no 5 5 no 6 5 no 7 5 yes 8 5 yes 9 6 no 10 6 no 11 6 yes 12 6 yes 13 6 yes 14 6 yes 15 6 yes 16 6 no 17 6 no 18 6 no 19 7 no 20 7 no 21 7 yes 22 7 yes 23 7 no 24 7 no 25 7 no 26 7 yes
该数据已按id分组,且按日期升序排列。
筛选要求
- 1、若当前行与下一行的outcome值相同,则删除当前行;
- 2、若某行outcome为yes,则其下一行必须是首个no;
- 3、必须保留每个id分组下最后一个yes值;
- 4、额外需要保留yes上方的最后一个no,若yes上方为连续no序列,则需保留该序列的第一个和最后一个no。
期望输出
id outcome 2 3 no 3 3 yes 4 3 no 6 5 no 8 5 yes 10 6 no 15 6 yes 16 6 no 20 7 no 22 7 yes 23 7 no 25 7 no 26 7 yes
现有实现问题
已编写如下掩码逻辑实现部分筛选,但无法保留yes上方连续no序列的第一个和最后一个值:
df = pd.DataFrame(data={'id':[3,3,3,3,3,5,5,5,5,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7], 'outcome': ['no','no','no','yes','no','no','no','yes','yes','no','no','yes','yes','yes','yes','yes','no','no','no', 'no', 'yes', 'no', 'no', 'yes']}) m1 = df['outcome'] # mask 1是outcome列 m2 = m1.groupby(df['id']).shift(-1) # 按id分组后向上偏移1位 m3 = m1.groupby(df['id']).shift().eq('yes')&m1.eq('no') # 布尔校验 df2 = df[~m1.eq(m2)|m3] m4 = df2['outcome'] m5 = m4.groupby(df2['id']).shift() df3 = df2[~m4.eq(m5)]
解决方法
可以按id分组后,先为组内连续相同的outcome序列打块标签,再针对不同类型的块按规则筛选行:
import pandas as pd # 首先构造完整的原始DataFrame df = pd.DataFrame({ 'id': [3,3,3,3,3,5,5,5,5,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7], 'outcome': ['no','no','no','yes','no','no','no','yes','yes','no','no','yes','yes','yes','yes','yes','no','no','no','no','no','yes','yes','no','no','no','yes'] }) def filter_single_group(g): # 为组内连续相同的outcome序列打块标签 g['block_id'] = g['outcome'].ne(g['outcome'].shift()).cumsum() res_frames = [] all_blocks = list(g.groupby('block_id')) block_count = len(all_blocks) for idx, (bid, block) in enumerate(all_blocks): block_val = block['outcome'].iloc[0] if block_val == 'yes': # yes块仅保留最后一行,满足保留每个id分组最后一个yes的要求 res_frames.append(block.tail(1)) else: # 判断当前no块下一个块是否是yes块 next_is_yes = idx < block_count - 1 and all_blocks[idx+1][1]['outcome'].iloc[0] == 'yes' # 判断当前no块上一个块是否是yes块 prev_is_yes = idx > 0 and all_blocks[idx-1][1]['outcome'].iloc[0] == 'yes' if next_is_yes: # 后续是yes的no块,保留首尾行 res_frames.append(block.head(1)) res_frames.append(block.tail(1)) elif prev_is_yes: # 前序是yes的no块,保留首行(yes后的第一个no) res_frames.append(block.head(1)) return pd.concat(res_frames).drop(columns='block_id') # 按id分组应用筛选规则 result = df.groupby('id', group_keys=False).apply(filter_single_group) print(result)
运行上述代码即可得到符合要求的输出结果。
内容的提问来源于stack exchange,提问作者Ze0ruso
相关产品推荐
相关产品推荐

