Pandas对groupby分组值应用函数后新增列 优化异常值剔除方案
问题描述
我有如下所示的DataFrame:
最小可复现示例如下:
d = {'Subject': [1,1,1,1,2,2,3,3,3,3,3,3,3], 'Pattern': [1,1,2,2,3,3,2,2,2,2,2,2,2], 'Time': [0.85, 0.92, 1.03, 1.06, 0.89, 0.85, 1.20, 1.03, 1.25, 100.03, 1.97,0.23,0.64]} df = pd.DataFrame(data=d)
其中Subject取值范围为1到8,Pattern取值范围为1到3。我希望先按Subject和Pattern分组,对每组对应的Time值列表应用异常值剔除函数后,将结果存储为新列。当前我已有可正常运行的解决方案,但想了解是否有更优雅的实现方式,以便更好地掌握DataFrame操作。示例对应的预期输出如下:
Subject Pattern Time Time_2 0 1 1 0.85 0.85 1 1 1 0.92 0.92 2 1 2 1.03 1.03 3 1 2 1.06 1.06 4 2 3 0.89 0.89 5 2 3 0.85 0.85 6 3 2 1.20 1.20 7 3 2 1.03 1.03 8 3 2 1.25 1.25 9 3 2 100.03 0.00 # <--- 10 3 2 1.97 1.97 11 3 2 0.23 0.23 12 3 2 0.64 0.64
我当前使用的代码如下:
def remove_outliers(arr): elements = np.array(arr) mean = np.mean(elements) sd = np.std(elements) return [x if (mean - 2 * sd < x < mean + 2 * sd) else 0 for x in arr] df_g = df.groupby(['Subject', 'Pattern'])['Time'] times = [] keys = list(df_g.groups.keys()) for i, l in enumerate(df_g.apply(list)): times.append((keys[i], remove_outliers(l))) df['Time_2'] = 0 for k, l in times: vals = df[(df['Subject'] == k[0]) & (df['Pattern'] == k[1])].index.values df['Time_2'].iloc[vals] = l
解决方案
更优雅的实现可以直接用groupby.transform完成,不需要手动遍历分组、匹配索引,代码量大幅减少,性能也更高:
步骤1:优化异常值处理函数(可选但推荐)
改成适配pandas Series的向量化实现,比列表推导效率更高:
def remove_outliers(s: pd.Series) -> pd.Series: mean = s.mean() sd = s.std() # 不符合条件的值直接替换为0 return s.where((s > mean - 2 * sd) & (s < mean + 2 * sd), 0)
步骤2:一行代码生成新列
df['Time_2'] = df.groupby(['Subject', 'Pattern'])['Time'].transform(remove_outliers)
实现说明
transform方法会对每个分组内的Series应用处理函数后,返回和原始分组长度一致、索引对齐的结果,直接赋值到新列即可,不需要手动处理索引匹配- 全程都是pandas内置的向量化操作,数据量越大,性能优势越明显
- 输出结果和预期完全一致,异常值100.03会被替换为0,其余值正常保留
内容的提问来源于stack exchange,提问作者Sanimys
相关产品推荐
相关产品推荐

