pandas使用.loc遇不匹配索引内存爆增及赋值重复轴错误问题
问题:pandas按条件拼接列时第三种方案内存占用异常问题
这是来自问题「按条件拼接pandas单元格的最Pythonic实现方式」的观测结果,我无法理解为什么第三种方案比第一种占用更多内存。
- 如果不做采样操作,第三种方案不会报运行时错误,显然存在异常问题
- 为了模拟大数据帧场景我尝试重采样,但完全没料到会遇到这类错误
背景
逻辑很容易理解,单行代码,写法很pythonic
df['city'] + (df['city'] == 'paris')*('_' + df['arr'].astype(str))
测试数据集构造代码:
s = """city,arr,final_target paris,11,paris_11 paris,12,paris_12 dallas,22,dallas miami,15,miami paris,16,paris_16""" import pandas as pd import io df = pd.read_csv(io.StringIO(s)).sample(1000000, replace=True) df
运行速度测试:
%%timeit df['city'] + (df['city'] == 'paris')*('_' + df['arr'].astype(str)) # 877 ms ± 19.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit df['final_target'] = np.where(df['city'].eq('paris'), df['city'] + '_' + df['arr'].astype(str), df['city']) # 874 ms ± 19.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
如果不做采样,没有报错且输出完全匹配
错误(已更新):仅在对数据帧采样时出现
第三种方案代码:
%%timeit df['final_target'] = df['city'] df.loc[df['city'] == 'paris', 'final_target'] += '_' + df['arr'].astype(str)
大采样量报错:
MemoryError: Unable to allocate 892. GiB for an array with shape (119671145392,) and data type int64
小采样量(采样大小100)报错:
ValueError: cannot reindex from a duplicate axis --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-5-57c5b10090b2> in <module> 1 df['final_target'] = df['city'] ----> 2 df.loc[df['city'] == 'paris', 'final_target'] += '_' + df['arr'].astype(str) ~/anaconda3/lib/python3.8/site-packages/pandas/core/ops/methods.py in f(self, other) 99 # we are updating inplace so we want to ignore is_copy 100 self._update_inplace( --> 101 result.reindex_like(self, copy=False), verify_is_copy=False 102 ) 103
每次测试我都会从头重新运行代码。
更新
我目前已经发现部分原因:
小采样量输出示例:
s = """city,arr,final_target paris,11,paris_11 paris,12,paris_12 dallas,22,dallas miami,15,miami paris,16,paris_16""" import pandas as pd import io df = pd.read_csv(io.StringIO(s)).sample(10, replace=True) df
输出:
city arr final_target 1 paris 12 paris_12 0 paris 11 paris_11 2 dallas 22 dallas 2 dallas 22 dallas 3 miami 15 miami 3 miami 15 miami 2 dallas 22 dallas 1 paris 12 paris_12 0 paris 11 paris_11 3 miami 15 miami
- 有放回采样会导致索引重复
- 因此重置索引可以解决问题,或者替换为
df.loc[df['city'] == 'paris', 'arr'].astype(str)也可以解决,即便df.arr和df.loc的尺寸本质上存在差异。 - 仍希望有人解释.loc的运行逻辑,以及索引存在重复且不匹配时内存暴增的原因?
解答
这个问题的核心是pandas的索引对齐机制和重复索引的笛卡尔积匹配逻辑:
.loc赋值时的默认行为:对左右两个Series做运算时,pandas会优先按索引标签匹配对应值,而不是按位置匹配。- 重复索引的笛卡尔积问题:你使用有放回采样后,索引存在大量重复值,比如索引
1在df里出现了1万次。你+=右边的是完整的df['arr'].astype(str),索引和整个df完全一致,同样有大量重复索引。此时左边.loc筛选出来的是所有city为paris的行,当pandas尝试对齐左右两边的索引时,会把左边每个索引标签和右边所有相同标签的行做笛卡尔积匹配,比如左边有k个标签为1的行,右边有m个标签为1的行,就会生成k*m个匹配结果。当采样量到1e6时,重复索引的笛卡尔积会直接把结果数组的长度拉到百亿级别,也就是你看到的需要分配892GiB内存的情况。 - 小样本报错的原因:小样本时笛卡尔积后的结果长度和左边
.loc筛选出来的子集长度不匹配,pandas无法直接赋值,就会抛出cannot reindex from a duplicate axis的错误。
两种解决方法的原理:
- 重置索引:
df.reset_index(drop=True)后所有索引都是唯一的,对齐时只会一一匹配,不会生成笛卡尔积。 - 右边改为筛选后的arr列:
df.loc[df['city'] == 'paris', 'arr'].astype(str)的索引和左边.loc筛选出来的子集索引完全一致,不需要额外对齐,自然不会有问题。
内容的提问来源于stack exchange,提问作者eroot163pi
相关产品推荐
相关产品推荐

