使用具有不匹配索引的 pandas .loc 时内存爆炸 + 分配给出重复轴错误
Explosion of memory when using pandas .loc with umatching indices + assignment giving duplicate axis error
这是来自 的观察
我无法理解为什么第三种解决方案比第一种占用更多内存。
如果我不采样第三个解决方案不会给出 运行时间错误,显然有些东西很奇怪
为了模拟大型数据帧,我尝试重新采样,但没想到 运行 会出现这种错误
背景
非常自我解释,一行,看起来 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.arr 和 df.loc 具有本质上不同的大小或替换为 df.loc[df['city'] == 'paris', 'arr'].astype(str)
也可以解决问题,因此重置索引可以解决问题。正如2e0byo指出的那样。
仍然有人可以解释 .loc 的工作原理以及当索引中有重复且不匹配时内存爆炸吗?!
我对此肯定是错的,但这不是因为 df["arr"]
与 df.loc[df["city"] == "paris"]
的形状不同吗?所以在 Pandas' 内部重采样中发生了一些有趣的事情。
如果我自己明确截断数据框,它会起作用:
df['final_target'] = df['city']
df.loc[df['city'] == 'paris', 'final_target'] += "_" + df.loc[df['city'] == 'paris', 'arr'].astype(str)
在这种情况下,答案将是 'because internally pandas has an algorithm for reshaping dataframes when adding different sizes which is inefficient in this case'。
我不知道这是否可以作为答案,因为我没有更深入地研究 pandas。
@2e0byo 一语中的是 pandas 在这种情况下算法“效率低下”。
就.loc
而言,它并没有真正做任何了不起的事情。它在这里的使用类似于用相同形状的布尔数组索引 numpy 数组,并添加了对特定列的类似 dict-key 的访问——也就是说,df['city'] == 'paris'
本身就是一个数据框,具有相同的数字的行数和与 df
相同的索引,只有一列布尔值。 df.loc[df['city'] == 'paris']
然后给出一个数据框,只包含 df['city'] == 'paris'
中为真的行(在 'city' 列中有 'paris')。添加额外的参数 'final_target' 然后只是 returns 只有那些行的 'final_target' 列,而不是所有三列(因为它只有一列,它在技术上是一个 Series
对象- df['arr']
).
也是如此
内存爆炸发生在pandas实际尝试添加两个系列时。正如 @2e0byo 指出的那样,它必须重塑系列才能做到这一点,它通过调用第一个系列的 align()
方法来实现。在 align
操作期间,函数 pandas.core.reshape.merge.get_join_indexers()
使用三个参数调用 pandas._libs.join.full_outer_join()
(line 155):left
、right
和 max_groups
(澄清点:这些是它们的名称在函数full_outer_join
中)。 left
和right
是整数数组,包含两个Series对象的索引(索引列中的值),max_groups
是任一[=34=中唯一元素的最大数量] 或 right
(在我们的例子中,这是五个,对应于 s
中的五个原始行)。
full_outer_join
立即转向并调用 pandas._libs.algos.groupsort_indexer()
(line 194),一次以 left
和 max_groups
作为参数,一次以 right
和 max_groups
。 groupsort_indexer
returns 两个数组——一般来说,indexer
和 counts
(对于 [=34= 的调用,它们被称为 left_sorter
和 left_count
,相应地 right
)。 counts
的长度为max_groups + 1
,每个元素(除了第一个未使用的元素)包含相应索引组在输入数组中出现的次数。因此,对于我们的例子,max_groups = 5
,count
数组的形状为 (6,)
,元素 1-5 表示 5 个唯一索引值出现在 left
中的次数,并且right
.
另一个数组 indexer
的构造使得用它索引原始输入数组 returns 所有元素按升序分组 - 因此是“排序器”。在为 left
和 right
完成此操作后,full_outer_join
将两个分类器切碎并将它们串起来。 full_outer_join
returns 两个相同大小的数组,left_idx
和 right_idx
- 这些数组变得非常大并引发错误。排序器中元素的顺序决定了它们在最后两个输出数组中的出现顺序,count
数组决定了每个元素出现的频率。由于 left
先行,它的元素保持在一起 - 在 left_idx
中,left_sorter
中的第一个 left_count[1]
元素每个重复 right_count[1]
次(aaabbbccc ...) .在 right_idx
中的同一位置,前 right_count[1]
个元素连续重复 left_count[1]
次(abcabcabc...)。 (方便的是,由于 s
中的 0
行是 'paris'
行,left_count[1]
和 right_count[1]
总是相等的,所以你得到 x 重复次数 x 开始的次数)。然后 left_sorter
的下一个 left_count[2]
元素被重复 right_count[2]
次,依此类推...如果任何 counts
元素为零,则 idx
数组用 -1 填充,稍后将被屏蔽(例如,right_count[i] = 0
表示 right_idx
中的元素为 -1,反之亦然 - left_count[3]
始终如此和 left_count[4]
,因为 s
中的行 2
和 3
是非 'paris'
)。
最后,_idx
个数组的元素个数等于N_elements
,可以这样计算:
left_nonzero = (left_count[1:] != 0)
right_nonzero = (right_count[1:] != 0)
left_repeats = left_count[1:]*left_nonzero + np.ones(len(left_counts)-1)*(1 - left_nonzero)
right_repeats = right_count[1:]*right_nonzero + np.ones(len(right_counts)-1)*(1 - right_nonzero)
N_elements = sum(left_repeats*right_repeats)
将count
数组对应的元素相乘(所有0替换为1),相加得到N_elements
.
您可以看到这个数字增长得非常快 (O(n^2)
)。对于具有 1,000,000 个采样行的原始数据帧,每个采样行的出现大致相同,那么 count
数组看起来像:
left_count = array([0, 2e5, 2e5, 0, 0, 2e5])
right_count = array([0, 2e5, 2e5, 2e5, 2e5, 2e5])
总长度约为 1.2e11
。通常对于初始样本 N
(df = pd.read_csv(io.StringIO(s)).sample(N, replace=True)
),最终大小约为 0.12*N**2
一个例子
查看一个小示例可能有助于了解 full_outer_join
和 groupsort_indexer
在制作这些庞大的数组时试图做什么。我们将从只有 10 行的小样本开始,然后跟随各种数组直到最终输出 left_idx
和 right_idx
。我们将从定义初始数据帧开始:
df = pd.read_csv(io.StringIO(s)).sample(10, replace=True)
df['final_target'] = df['city'] # this line doesn't change much, but meh
看起来像:
city arr final_target
3 miami 15 miami
1 paris 11 paris
0 paris 12 paris
0 paris 12 paris
0 paris 12 paris
1 paris 11 paris
2 dallas 22 dallas
3 miami 15 miami
2 dallas 22 dallas
4 paris 16 paris
df.loc[df['city'] == 'paris', 'final_target']
看起来像:
1 paris
0 paris
0 paris
0 paris
1 paris
4 paris
和df['arr'].astype(str)
:
3 15
1 11
0 12
0 12
0 12
1 11
2 22
3 15
2 22
4 16
然后,在对 full_outer_join
的调用中,我们的参数如下所示:
left = array([1,0,0,0,1,4]) # indexes of df.loc[df['city'] == 'paris', 'final_target']
right = array([3,1,0,0,0,1,2,3,2,4]) # indexes of df['arr'].astype(str)
max_groups = 5 # the max number of unique elements in either left or right
函数调用groupsort_indexer(left, max_groups)
returns下面两个数组:
left_sorter = array([1, 2, 3, 0, 4, 5])
left_count = array([0, 3, 2, 0, 0, 1])
left_count
保存每个唯一值在 left
中出现的次数 - 第一个元素未使用,但随后有 3 个零、2 个一、0 个二、0 个三和 1 left
.
中的四个
left_sorter
这样 left[left_sorter] = array([0, 0, 0, 1, 1, 4])
- 一切都井井有条。
现在right
:groupsort_indexer(right, max_groups)
returns
right_sorter = array([2, 3, 4, 1, 5, 6, 8, 0, 7, 9])
right_count = array([0, 3, 2, 2, 2, 1])
再次,right_count
包含每个计数出现的次数:未使用的第一个元素,然后是3个零、2个一、2个二、2个三和1个四(注意元素1,两个 count
数组的 2 和 5 是相同的:这些是 s
和 'city' = 'paris'
中的行)。另外,right[right_sorter] = array([0, 0, 0, 1, 1, 2, 2, 3, 3, 4])
在计算了两个 count
数组后,我们可以计算出 idx
数组的大小(实际数字比上面的公式更简单):
N_total = 3*3 + 2*2 + 2 + 2 + 1*1 = 18
3
是两个 counts
数组的元素 1,因此我们可以预期 [1,1,1,2,2,2,3,3,3]
之类的东西开始 left_idx
,因为 [1,2,3]
开始 left_sorter
,[2,3,4,2,3,4,2,3,4]
开始 right_idx
,因为 right_sorter
以 [2,3,4]
开始。然后我们有两个,所以 [0,0,4,4]
代表 left_idx
,[1,5,1,5]
代表 right_idx
。然后 left_count
有两个零,right_count
有两个二,所以接下来去 left_idx
中的 4 -1
和 right_sorter
中接下来的四个元素进入right_idx
:[6,8,0,7]
。 count
都以 1 结尾,因此 sorters
中的最后一个元素都进入 idx
:5
for left_idx
and 9
对于 right_idx
,留下:
left_idx = array([1, 1, 1, 2, 2, 2, 3, 3, 3, 0, 0, 4, 4,-1, -1, -1, -1, 5])
right_idx = array([2, 3, 4, 2, 3, 4, 2, 3, 4, 1, 5, 1, 5, 6, 8, 0 , 7, 9])
确实是18个元素
在两个索引数组形状相同的情况下,pandas 可以从我们的原始数组构造两个相同形状的 Series 来执行它需要的任何操作,然后它可以屏蔽这些数组以获得排序后的索引.使用一个简单的布尔过滤器来查看我们如何使用输出对 left
和 right
进行排序,我们得到:
left[left_idx[left_idx != -1]] = array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 4])
right[right_idx[right_idx != -1]] = array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 4])
回溯所有函数调用和模块后,此时加法结果为:
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
1 paris_11
1 paris_11
1 paris_11
1 paris_11
2 NaN
2 NaN
3 NaN
3 NaN
4 paris_16
result = op(self, other)
行pandas.core.generic.NDFrame._inplace_method
中的result
(line 11066),op = pandas.core.series.Series.__add__
和self
和other
我们添加之前的两个系列。
因此,据我所知,pandas 基本上 尝试 对相同索引行的每个组合执行操作(例如,任何和所有行第一个系列中的索引 1
应与其他系列中的所有行索引 1
一起操作)。如果其中一个系列具有另一个系列没有的索引,则这些行将被屏蔽掉。在这种情况下,具有相同索引的每一行都是相同的。只要您不需要在 就地 做任何事情,它就可以工作(尽管是多余的)——当 pandas 试图重新索引这个结果时,小数据帧的问题就出现了回到原始数据框的形状 df
.
分割线(较小的数据帧通过的线,但较大的数据框没有)是上面的线 result = op(self, other)
。稍后在同一个函数中(调用,注意,_inplace_method
),程序在 self._update_inplace(result.reindex_like(self, copy=False), verify_is_copy=False)
处退出。它尝试重新索引 result
使其看起来像 self
,因此它可以用 result
替换 self
(self
是原始系列,第一个在另外,df.loc[df['city'] == 'paris', 'final_target']
)。这就是小案例失败的地方,因为,显然,result
有一堆重复的索引,而 pandas 不想在删除其中一些时丢失任何信息。
最后一件事
可能值得一提的是,这种行为并不是这里的加法运算所特有的。每当您尝试对具有大量重复索引的两个大型数据帧进行算术运算时,都会发生这种情况 - 例如,尝试以与第一个完全相同的方式定义第二个数据帧,df2 = pd.read_csv(io.StringIO(s)).sample(1000000, replace=True)
,然后尝试 运行 df.arr*df2.arr
。你会得到同样的内存错误。
有趣的是,逻辑运算符和比较运算符可以防止这样做 - 它们需要相同的索引,并在调用它们的运算符方法之前检查它。
我在 pandas 1.2.4、python 3.7.10 中完成了所有工作,但我提供了指向 pandas Github 的链接,这是当前版本为 1.3.3。据我所知,差异不会影响结果。
这是来自
如果我不采样第三个解决方案不会给出 运行时间错误,显然有些东西很奇怪
为了模拟大型数据帧,我尝试重新采样,但没想到 运行 会出现这种错误
背景
非常自我解释,一行,看起来 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.arr 和 df.loc 具有本质上不同的大小或替换为
df.loc[df['city'] == 'paris', 'arr'].astype(str)
也可以解决问题,因此重置索引可以解决问题。正如2e0byo指出的那样。仍然有人可以解释 .loc 的工作原理以及当索引中有重复且不匹配时内存爆炸吗?!
我对此肯定是错的,但这不是因为 df["arr"]
与 df.loc[df["city"] == "paris"]
的形状不同吗?所以在 Pandas' 内部重采样中发生了一些有趣的事情。
如果我自己明确截断数据框,它会起作用:
df['final_target'] = df['city']
df.loc[df['city'] == 'paris', 'final_target'] += "_" + df.loc[df['city'] == 'paris', 'arr'].astype(str)
在这种情况下,答案将是 'because internally pandas has an algorithm for reshaping dataframes when adding different sizes which is inefficient in this case'。
我不知道这是否可以作为答案,因为我没有更深入地研究 pandas。
@2e0byo 一语中的是 pandas 在这种情况下算法“效率低下”。
就.loc
而言,它并没有真正做任何了不起的事情。它在这里的使用类似于用相同形状的布尔数组索引 numpy 数组,并添加了对特定列的类似 dict-key 的访问——也就是说,df['city'] == 'paris'
本身就是一个数据框,具有相同的数字的行数和与 df
相同的索引,只有一列布尔值。 df.loc[df['city'] == 'paris']
然后给出一个数据框,只包含 df['city'] == 'paris'
中为真的行(在 'city' 列中有 'paris')。添加额外的参数 'final_target' 然后只是 returns 只有那些行的 'final_target' 列,而不是所有三列(因为它只有一列,它在技术上是一个 Series
对象- df['arr']
).
内存爆炸发生在pandas实际尝试添加两个系列时。正如 @2e0byo 指出的那样,它必须重塑系列才能做到这一点,它通过调用第一个系列的 align()
方法来实现。在 align
操作期间,函数 pandas.core.reshape.merge.get_join_indexers()
使用三个参数调用 pandas._libs.join.full_outer_join()
(line 155):left
、right
和 max_groups
(澄清点:这些是它们的名称在函数full_outer_join
中)。 left
和right
是整数数组,包含两个Series对象的索引(索引列中的值),max_groups
是任一[=34=中唯一元素的最大数量] 或 right
(在我们的例子中,这是五个,对应于 s
中的五个原始行)。
full_outer_join
立即转向并调用 pandas._libs.algos.groupsort_indexer()
(line 194),一次以 left
和 max_groups
作为参数,一次以 right
和 max_groups
。 groupsort_indexer
returns 两个数组——一般来说,indexer
和 counts
(对于 [=34= 的调用,它们被称为 left_sorter
和 left_count
,相应地 right
)。 counts
的长度为max_groups + 1
,每个元素(除了第一个未使用的元素)包含相应索引组在输入数组中出现的次数。因此,对于我们的例子,max_groups = 5
,count
数组的形状为 (6,)
,元素 1-5 表示 5 个唯一索引值出现在 left
中的次数,并且right
.
另一个数组 indexer
的构造使得用它索引原始输入数组 returns 所有元素按升序分组 - 因此是“排序器”。在为 left
和 right
完成此操作后,full_outer_join
将两个分类器切碎并将它们串起来。 full_outer_join
returns 两个相同大小的数组,left_idx
和 right_idx
- 这些数组变得非常大并引发错误。排序器中元素的顺序决定了它们在最后两个输出数组中的出现顺序,count
数组决定了每个元素出现的频率。由于 left
先行,它的元素保持在一起 - 在 left_idx
中,left_sorter
中的第一个 left_count[1]
元素每个重复 right_count[1]
次(aaabbbccc ...) .在 right_idx
中的同一位置,前 right_count[1]
个元素连续重复 left_count[1]
次(abcabcabc...)。 (方便的是,由于 s
中的 0
行是 'paris'
行,left_count[1]
和 right_count[1]
总是相等的,所以你得到 x 重复次数 x 开始的次数)。然后 left_sorter
的下一个 left_count[2]
元素被重复 right_count[2]
次,依此类推...如果任何 counts
元素为零,则 idx
数组用 -1 填充,稍后将被屏蔽(例如,right_count[i] = 0
表示 right_idx
中的元素为 -1,反之亦然 - left_count[3]
始终如此和 left_count[4]
,因为 s
中的行 2
和 3
是非 'paris'
)。
最后,_idx
个数组的元素个数等于N_elements
,可以这样计算:
left_nonzero = (left_count[1:] != 0)
right_nonzero = (right_count[1:] != 0)
left_repeats = left_count[1:]*left_nonzero + np.ones(len(left_counts)-1)*(1 - left_nonzero)
right_repeats = right_count[1:]*right_nonzero + np.ones(len(right_counts)-1)*(1 - right_nonzero)
N_elements = sum(left_repeats*right_repeats)
将count
数组对应的元素相乘(所有0替换为1),相加得到N_elements
.
您可以看到这个数字增长得非常快 (O(n^2)
)。对于具有 1,000,000 个采样行的原始数据帧,每个采样行的出现大致相同,那么 count
数组看起来像:
left_count = array([0, 2e5, 2e5, 0, 0, 2e5])
right_count = array([0, 2e5, 2e5, 2e5, 2e5, 2e5])
总长度约为 1.2e11
。通常对于初始样本 N
(df = pd.read_csv(io.StringIO(s)).sample(N, replace=True)
),最终大小约为 0.12*N**2
一个例子
查看一个小示例可能有助于了解 full_outer_join
和 groupsort_indexer
在制作这些庞大的数组时试图做什么。我们将从只有 10 行的小样本开始,然后跟随各种数组直到最终输出 left_idx
和 right_idx
。我们将从定义初始数据帧开始:
df = pd.read_csv(io.StringIO(s)).sample(10, replace=True)
df['final_target'] = df['city'] # this line doesn't change much, but meh
看起来像:
city arr final_target
3 miami 15 miami
1 paris 11 paris
0 paris 12 paris
0 paris 12 paris
0 paris 12 paris
1 paris 11 paris
2 dallas 22 dallas
3 miami 15 miami
2 dallas 22 dallas
4 paris 16 paris
df.loc[df['city'] == 'paris', 'final_target']
看起来像:
1 paris
0 paris
0 paris
0 paris
1 paris
4 paris
和df['arr'].astype(str)
:
3 15
1 11
0 12
0 12
0 12
1 11
2 22
3 15
2 22
4 16
然后,在对 full_outer_join
的调用中,我们的参数如下所示:
left = array([1,0,0,0,1,4]) # indexes of df.loc[df['city'] == 'paris', 'final_target']
right = array([3,1,0,0,0,1,2,3,2,4]) # indexes of df['arr'].astype(str)
max_groups = 5 # the max number of unique elements in either left or right
函数调用groupsort_indexer(left, max_groups)
returns下面两个数组:
left_sorter = array([1, 2, 3, 0, 4, 5])
left_count = array([0, 3, 2, 0, 0, 1])
left_count
保存每个唯一值在 left
中出现的次数 - 第一个元素未使用,但随后有 3 个零、2 个一、0 个二、0 个三和 1 left
.
left_sorter
这样 left[left_sorter] = array([0, 0, 0, 1, 1, 4])
- 一切都井井有条。
现在right
:groupsort_indexer(right, max_groups)
returns
right_sorter = array([2, 3, 4, 1, 5, 6, 8, 0, 7, 9])
right_count = array([0, 3, 2, 2, 2, 1])
再次,right_count
包含每个计数出现的次数:未使用的第一个元素,然后是3个零、2个一、2个二、2个三和1个四(注意元素1,两个 count
数组的 2 和 5 是相同的:这些是 s
和 'city' = 'paris'
中的行)。另外,right[right_sorter] = array([0, 0, 0, 1, 1, 2, 2, 3, 3, 4])
在计算了两个 count
数组后,我们可以计算出 idx
数组的大小(实际数字比上面的公式更简单):
N_total = 3*3 + 2*2 + 2 + 2 + 1*1 = 18
3
是两个 counts
数组的元素 1,因此我们可以预期 [1,1,1,2,2,2,3,3,3]
之类的东西开始 left_idx
,因为 [1,2,3]
开始 left_sorter
,[2,3,4,2,3,4,2,3,4]
开始 right_idx
,因为 right_sorter
以 [2,3,4]
开始。然后我们有两个,所以 [0,0,4,4]
代表 left_idx
,[1,5,1,5]
代表 right_idx
。然后 left_count
有两个零,right_count
有两个二,所以接下来去 left_idx
中的 4 -1
和 right_sorter
中接下来的四个元素进入right_idx
:[6,8,0,7]
。 count
都以 1 结尾,因此 sorters
中的最后一个元素都进入 idx
:5
for left_idx
and 9
对于 right_idx
,留下:
left_idx = array([1, 1, 1, 2, 2, 2, 3, 3, 3, 0, 0, 4, 4,-1, -1, -1, -1, 5])
right_idx = array([2, 3, 4, 2, 3, 4, 2, 3, 4, 1, 5, 1, 5, 6, 8, 0 , 7, 9])
确实是18个元素
在两个索引数组形状相同的情况下,pandas 可以从我们的原始数组构造两个相同形状的 Series 来执行它需要的任何操作,然后它可以屏蔽这些数组以获得排序后的索引.使用一个简单的布尔过滤器来查看我们如何使用输出对 left
和 right
进行排序,我们得到:
left[left_idx[left_idx != -1]] = array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 4])
right[right_idx[right_idx != -1]] = array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 4])
回溯所有函数调用和模块后,此时加法结果为:
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
0 paris_12
1 paris_11
1 paris_11
1 paris_11
1 paris_11
2 NaN
2 NaN
3 NaN
3 NaN
4 paris_16
result = op(self, other)
行pandas.core.generic.NDFrame._inplace_method
中的result
(line 11066),op = pandas.core.series.Series.__add__
和self
和other
我们添加之前的两个系列。
因此,据我所知,pandas 基本上 尝试 对相同索引行的每个组合执行操作(例如,任何和所有行第一个系列中的索引 1
应与其他系列中的所有行索引 1
一起操作)。如果其中一个系列具有另一个系列没有的索引,则这些行将被屏蔽掉。在这种情况下,具有相同索引的每一行都是相同的。只要您不需要在 就地 做任何事情,它就可以工作(尽管是多余的)——当 pandas 试图重新索引这个结果时,小数据帧的问题就出现了回到原始数据框的形状 df
.
分割线(较小的数据帧通过的线,但较大的数据框没有)是上面的线 result = op(self, other)
。稍后在同一个函数中(调用,注意,_inplace_method
),程序在 self._update_inplace(result.reindex_like(self, copy=False), verify_is_copy=False)
处退出。它尝试重新索引 result
使其看起来像 self
,因此它可以用 result
替换 self
(self
是原始系列,第一个在另外,df.loc[df['city'] == 'paris', 'final_target']
)。这就是小案例失败的地方,因为,显然,result
有一堆重复的索引,而 pandas 不想在删除其中一些时丢失任何信息。
最后一件事
可能值得一提的是,这种行为并不是这里的加法运算所特有的。每当您尝试对具有大量重复索引的两个大型数据帧进行算术运算时,都会发生这种情况 - 例如,尝试以与第一个完全相同的方式定义第二个数据帧,df2 = pd.read_csv(io.StringIO(s)).sample(1000000, replace=True)
,然后尝试 运行 df.arr*df2.arr
。你会得到同样的内存错误。
有趣的是,逻辑运算符和比较运算符可以防止这样做 - 它们需要相同的索引,并在调用它们的运算符方法之前检查它。
我在 pandas 1.2.4、python 3.7.10 中完成了所有工作,但我提供了指向 pandas Github 的链接,这是当前版本为 1.3.3。据我所知,差异不会影响结果。