Python 3.6.4中使用df.duplicated()报MemoryError的问题求助
解决Pandas中df.duplicated()触发MemoryError的问题
我之前处理过类似的大内存数据问题,咱们来拆解一下原因和解决方案:
问题根源
你用分块加载后concat得到了完整的DataFrame,但df.duplicated()需要对所有列的所有行做逐行比较,这会在内存中生成大量临时数据——15万行×208列的数据量,加上比较过程中的额外内存开销,很容易触发内存不足的错误。
解决方案推荐
1. 分块阶段先去重,再合并(最推荐)
与其等到所有数据加载完再处理重复,不如在每个分块加载时就先去掉内部重复,这样能大幅减少最终合并后DataFrame的大小,后续的重复检查压力也会小很多:
myList = [] for chunk in pd.read_csv(filename, header=0, low_memory=False, chunksize=20000): # 先移除当前分块内的重复行 chunk = chunk.drop_duplicates() myList.append(chunk) # 合并分块后,再处理跨分块的重复行 dfMain = pd.concat(myList, axis=0).drop_duplicates() dfMain.index.name = 'Index' print(dfMain.shape)
这个方法能在数据加载的过程中就“瘦身”,从源头减少内存占用。
2. 用行哈希替代全列比较(保留所有行时使用)
如果需要保留所有行只是标记重复,咱们可以先给每行计算一个唯一哈希值(内存占用远小于整行数据),再通过哈希值判断重复:
def compute_row_hash(chunk): # 计算每行的哈希值,pandas内置方法高效且稳定 return pd.util.hash_pandas_object(chunk, index=False) all_hashes = [] myList = [] for chunk in pd.read_csv(filename, header=0, low_memory=False, chunksize=20000): myList.append(chunk) # 计算当前分块的行哈希 all_hashes.append(compute_row_hash(chunk)) # 合并数据和哈希值 dfMain = pd.concat(myList, axis=0) dfMain['row_hash'] = pd.concat(all_hashes, axis=0).values # 基于哈希值判断重复,内存开销大幅降低 duplicated_mask = dfMain.duplicated(subset='row_hash')
如果担心哈希碰撞,可以对标记为重复的行再做一次全列精确校验,不过绝大多数场景下哈希值的唯一性足够可靠。
3. 优化数据类型,降低整体内存占用
你的DataFrame有208列,很多列的类型可能可以进一步压缩:
- 数值列:把
int64/float64向下转换为更小的类型(比如int32/float32,只要数据范围允许) - 字符串列:如果唯一值占比低(比如低于50%),转换成
category类型,内存占用会大幅减少
代码示例:
def optimize_chunk_dtypes(chunk): # 优化数值列 for col in chunk.select_dtypes(include=['int64']).columns: chunk[col] = pd.to_numeric(chunk[col], downcast='integer') for col in chunk.select_dtypes(include=['float64']).columns: chunk[col] = pd.to_numeric(chunk[col], downcast='float') # 优化字符串列 for col in chunk.select_dtypes(include=['object']).columns: unique_ratio = len(chunk[col].unique()) / len(chunk[col]) if unique_ratio < 0.5: chunk[col] = chunk[col].astype('category') return chunk myList = [] for chunk in pd.read_csv(filename, header=0, low_memory=False, chunksize=20000): chunk = optimize_chunk_dtypes(chunk) myList.append(chunk) dfMain = pd.concat(myList, axis=0) dfMain.index.name = 'Index' # 现在再执行duplicated()就不会轻易爆内存了 duplicated_mask = dfMain.duplicated()
小提示
你可以先执行dfMain.memory_usage(deep=True).sum()查看优化前后的内存占用,能直观看到效果。
内容的提问来源于stack exchange,提问作者Jerry
相关产品推荐
相关产品推荐

