使用df.drop(idx)出现内存不足错误,求删除记录的替代方案
批量删除符合条件的DataFrame记录的优化方案
问题场景
我有一个包含536000+条记录的DataFrame df_clean,需要筛选并删除满足以下条件的交易记录:
- 按
CustomerID、StockCode、Quantity的绝对值分组 - 分组内
Quantity绝对值的记录数为偶数,且分组内Quantity总和为0
原尝试方案及问题
方案1:groupby+filter+drop
先筛选出目标记录,再通过索引删除:
df_pairs = df_clean.groupby([df_clean.CustomerID, df_clean.StockCode, df_clean.Quantity.abs()]).filter(lambda x: (len(x.Quantity.abs()) % 2 == 0) and (x.Quantity.sum() == 0)) idx = df_pairs.index df_clean.drop(idx)
结果:len(df_pairs)显示有4016条目标记录,但执行drop时耗时极长,最终触发内存不足错误导致页面崩溃,重启内核、电脑都无法解决。
方案2:loc+~反向筛选
尝试直接用布尔索引反向保留记录:
df_clean = df_clean.loc[~((df_clean.groupby([df_clean.CustomerID, df_clean.StockCode, df_clean.Quantity.abs()]).filter(lambda x: (len(x.Quantity.abs()) % 2 == 0) and (x.Quantity.sum() == 0))))]
结果触发类型错误:
TypeError Traceback (most recent call last) C:\Users\MARTIN~1\AppData\Local\Temp/ipykernel_7792/227912236.py in <module> ----> 1 df_clean = df_clean.loc[~((df_clean.groupby([df_clean.CustomerID, df_clean.StockCode, df_clean.Quantity.abs()]).filter(lambda x: (len(x.Quantity.abs()) % 2 == 0) and (x.Quantity.sum() == 0))))] ~\anaconda3\lib\site-packages\pandas\core\generic.py in __invert__(self) 1530 return self 1531 -> 1532 new_data = self._mgr.apply(operator.invert) 1533 return self._constructor(new_data).__finalize__(self, method="__invert__") 1534 ~\anaconda3\lib\site-packages\pandas\core\internals\managers.py in apply(self, f, align_keys, ignore_failures, **kwargs) 323 try: 324 if callable(f): --> 325 applied = b.apply(f, **kwargs) 326 else: 327 applied = getattr(b, f)(**kwargs) ~\anaconda3\lib\site-packages\pandas\core\internals\blocks.py in apply(self, func, **kwargs) 379 """ 380 with np.errstate(all="ignore"): --> 381 result = func(self.values, **kwargs) 382 383 return self._split_op_result(result) TypeError: bad operand type for unary ~: 'DatetimeArray'
注:数据集为销售交易历史(每条记录对应发票行),无法使用isin()或pd.concat后drop_duplicates()的方法,数据集示例如下:
| InvoiceNo | StockCode | Description | Quantity | InvoiceDate | UnitPrice | CustomerID | TotalSales |
|---|---|---|---|---|---|---|---|
| 536365 | 85123A | WHITE HANGING HEART T-LIGHT HOLDER | 6 | 2018-11-29 08:26:00 | 2.55 | 17850 | 15.30 |
| 536365 | 71053 | WHITE METAL LANTERN | 6 | 2018-11-29 08:26:00 | 3.39 | 17850 | 20.34 |
| 536365 | 84406B | CREAM CUPID HEARTS COAT HANGER | 8 | 2018-11-29 08:26:00 | 2.75 | 17850 | 22.00 |
| 536365 | 84029G | KNITTED UNION FLAG HOT WATER BOTTLE | 6 | 2018-11-29 08:26:00 | 3.39 | 17850 | 20.34 |
| 536365 | 84029E | RED WOOLLY HOTTIE WHITE HEART. | 6 | 2018-11-29 08:26:00 | 3.39 | 17850 | 20.34 |
优化解决方案
方案1:用transform生成布尔标记(最省内存)
避免生成中间DataFrame,直接用transform给每条记录标记是否属于要删除的分组:
# 定义分组键 group_keys = ['CustomerID', 'StockCode', df_clean['Quantity'].abs()] # 用transform生成每条记录的条件标记 cond1 = df_clean.groupby(group_keys)['Quantity'].transform(lambda x: len(x) % 2 == 0) cond2 = df_clean.groupby(group_keys)['Quantity'].transform(lambda x: x.sum() == 0) # 反向筛选保留非目标记录 df_clean = df_clean.loc[~(cond1 & cond2)]
原理:transform返回和原DataFrame长度一致的Series,直接生成布尔索引,无需创建中间大对象,内存占用极低。
方案2:先筛选分组再标记记录
先在分组层面筛选出符合条件的组,再匹配原数据中的对应记录:
# 计算每个分组的统计值:记录数、Quantity总和 group_stats = df_clean.groupby(['CustomerID', 'StockCode', df_clean['Quantity'].abs()])['Quantity'].agg(['count', 'sum']) # 筛选出符合条件的分组 target_groups = group_stats[(group_stats['count'] % 2 == 0) & (group_stats['sum'] == 0)].index # 标记原数据中属于目标分组的记录 df_clean['is_target'] = df_clean.apply( lambda row: (row['CustomerID'], row['StockCode'], abs(row['Quantity'])) in target_groups, axis=1 ) # 过滤并删除临时标记列 df_clean = df_clean[~df_clean['is_target']].drop('is_target', axis=1)
原理:先在分组层面完成筛选,再匹配原数据,适合分组数量远小于总记录数的场景。
方案3:分块处理(极端内存不足时用)
如果内存实在紧张,把DataFrame按CustomerID拆分成小块处理,逐块筛选后合并:
import pandas as pd chunks = [] # 按CustomerID分块,降低单块内存负载 for _, chunk in df_clean.groupby('CustomerID'): group_keys = ['CustomerID', 'StockCode', chunk['Quantity'].abs()] cond1 = chunk.groupby(group_keys)['Quantity'].transform(lambda x: len(x) % 2 == 0) cond2 = chunk.groupby(group_keys)['Quantity'].transform(lambda x: x.sum() == 0) chunks.append(chunk[~(cond1 & cond2)]) # 合并所有处理后的块 df_clean = pd.concat(chunks, ignore_index=True)
原理:把大数据集拆分成小批量处理,避免一次性加载全量数据占用过多内存。
内容的提问来源于stack exchange,提问作者DorothyK
相关产品推荐
相关产品推荐

