
本文介绍如何在 pandas 中将 dataframe 按倒序(从底向上)每 4 行分组,并仅保留每组首行原始值、其余行统一置为 0 的完整解决方案,涵盖多种高效写法及原理说明。
本文介绍如何在 pandas 中将 dataframe 按倒序(从底向上)每 4 行分组,并仅保留每组首行原始值、其余行统一置为 0 的完整解决方案,涵盖多种高效写法及原理说明。
在数据处理中,常需对时间序列或有序数据进行反向窗口操作——例如“从最后一行开始,每 4 行为一组,若该组最上方(即实际分组中的第一个)元素为 1,则整组仅首行保留 1,其余全部设为 0”。这看似涉及复杂 groupby 逻辑,但其实可通过更简洁、向量化的方式高效实现。
以下以原始数据为例:
import pandas as pd
import numpy as np
df = pd.DataFrame({
'day': [1, 2, 3, 4, 5, 6, 7, 8],
'first mover': [1, 1, 0, 0, 0, 1, 0, 1]
})
目标是:从下往上每 4 行分组(即 [8,7,6,5], [4,3,2,1]),每组中位置最靠前的那行(即倒序后的首行)若为 1,则该组其余行全置 0;最终结果中仅第 0 行(day=1)保留 1,其余全为 0。
✅ 推荐方案:布尔掩码 + where()(最清晰高效)
# 构建从末尾倒数的索引模运算:len-1, len-2, ..., 0 → 模 4 余 3 的位置即为每组“首行” m = np.arange(len(df) - 1, -1, -1) % 4 == 3 df['out'] = df['first mover'].where(m, 0)
-
np.arange(len(df)-1,-1,-1)生成[7,6,5,4,3,2,1,0](对应原 DataFrame 倒序索引); -
% 4 == 3标记每组第一个位置:7%4=3,3%4=3→ 即索引7和3(对应day=8和day=4); - 但注意:题目要求的是倒序分组后每组的“第一个元素”,而我们真正要保留的是每个倒序组中物理位置最靠上的那个原始值——观察目标输出可知,仅
day=1(索引 0)被保留为 1。这意味着分组应为[8,7,6,5],[4,3,2,1],而“第一组首行”是day=5(索引 4),“第二组首行”是day=1(索引 0)。因此更准确的掩码应定位索引4和0。
✅ 正确掩码逻辑(适配题设分组):
# 分组大小 N=4,倒序分组后,每组首行在原始索引中为:len-4, len-8, ..., 直到 >=0 # 即:索引 i 满足 (len - 1 - i) % 4 == 0 ← 表示 i 是某组倒序后的第一个(即正序中该组最末) # 但我们想要的是每组在**倒序视图下的第一个** → 对应原始索引:从末尾起每 4 步取一个,即:-4, -8, ... → 等价于 i % 4 == 0(当从 0 开始编号) # 实际验证:索引 [0,4] → day=[1,5] → 但目标只要 day=1 → 所以应取「从末尾起,每 4 行的第一个」→ 即索引 -4, -8... → 对应正序索引 len-4, len-8... # 更直接:取 df.iloc[::-1].iloc[::4].index → 再映射回原索引 # 终极简洁解(与答案一致): df['out'] = df['first mover'].iloc[-4::-4].reindex(df.index, fill_value=0)
该行含义:
-
df['first mover'].iloc[-4::-4]:从倒数第 4 行(索引 -4,即day=5)开始,步长 -4 向前取值 → 得到pd.Series,其 index 为[4, 0](对应day=5和day=1),值为[0, 1]; -
.reindex(df.index, fill_value=0):按原始df.index([0,1,...,7])对齐,缺失位置补0→ 自动完成“只留指定位置,其余归零”。
输出:
day first mover out 0 1 1 1 1 2 1 0 2 3 0 0 3 4 0 0 4 5 0 0 5 6 1 0 6 7 0 0 7 8 1 0
⚠️ 注意事项
-
iloc[-4::-4]中-4是起始位置(倒数第 4 行),-4步长表示向前跨 4 行;若总行数不被 4 整除(如 9 行),会自动截断至合法索引。 - 避免使用
groupby().transform()配合lambda,虽功能正确但性能差、可读性低,且易因索引错位出错。 - 若需泛化为任意
N,只需将代码中所有4替换为变量N,并确保df.iloc[-N::-N]逻辑成立。
✅ 总结
对于“倒序等距采样 + 其余置零”类任务,优先使用 iloc[::step] 结合 reindex(fill_value=0) —— 它零循环、纯向量化、语义明确,且性能远超 groupby.transform。理解 iloc 的负索引与步长行为是掌握该技巧的关键。










