如何加速 pandas 循环?
How to speed up a pandas loop?
我有一个名为 'matrix' 的 pandas 数据框,它看起来像这样:
antecedent_sku consequent_sku similarity
0 001 002 0.3
1 001 003 0.2
2 001 004 0.1
3 001 005 0.4
4 002 001 0.4
5 002 003 0.5
6 002 004 0.1
在这个数据框之外,我想创建一个相似矩阵以进一步聚类。我分两步完成。
第 1 步:创建一个空的相似度矩阵 ('similarity')
set_name = set(matrix['antecedent_sku'].values)
similarity = pd.DataFrame(index = list(set_name), columns = list(set_name))
第 2 步:用 'matrix':
中的值填充它
for ind in tqdm(list(similarity.index)):
for col in list(similarity.columns):
if ind==col:
similarity.loc[ind, col] = 1
elif len(matrix.loc[(matrix['antecedent_sku'].values==f'{ind}') & (matrix['consequent_sku'].values==f'{col}'), 'similarity'].values) < 1:
similarity.loc[ind, col] = 0
else:
similarity.loc[ind, col] = matrix.loc[(matrix['antecedent_sku'].values==f'{ind}') & (matrix['consequent_sku'].values==f'{col}'), 'similarity'].values[0]
问题:填充形状为 (3000,3000) 的矩阵需要 4 个小时。
问题:我做错了什么?我的目标是用 Cython/Numba 之类的东西来加速代码,还是问题出在我的方法的架构上,我应该使用内置函数或其他一些聪明的方法将 'matrix' 转换为 'similarity' 而不是双循环?
P.S。我运行Python3.8.7
众所周知,使用 loc
遍历 pandas 数据帧非常慢。众所周知,CPython 解释器也很慢(通常是循环)。每个 pandas 操作都有很高的开销。但是,要点是您迭代了 3000x3000 个元素,以便为每个元素调用诸如 matrix['antecedent_sku'].values==f'{ind}'
之类的东西,它肯定会迭代 3000 个项目,这些项目是字符串,也被认为是一种低效的数据类型(因为处理器需要解析一个 variable-length UTF-8 多字符序列)。由于每次迭代执行两次并且您为每次比较解析一个新整数,这意味着将执行 3000*3000*3000*2 = 54_000_000_000
个字符串比较,总共 3000*3000*3000*2*2*3 = 324_000_000_000
个字符进行(低效)比较! 这不可能很快,因为效率很低。更不用说每个 9_000_000 迭代 creates/delete 几个临时数组和 Pandas 对象。
首先要做的是由于一些预计算减少重新计算操作的数量。实际上,您可以将 matrix['antecedent_sku'].values==f'{ind}'
的值(因为 Numpy 数组,因为 pandas 系列效率低下)存储在由 ind
索引的字典中,以便在循环中更快地获取它。这应该使这部分快 3000 倍(因为应该只有 3000 个项目)。更好的是:您可以使用 groupby 来更有效地做到这一点。
此外,您可以将列转换为整数(即antecedent_sku
和consequent_sku
)以避免许多昂贵的字符串比较。
然后你可以像matrix.loc[..., 'similarity'].values
一样删除无用的操作。事实上,由于你只想知道结果的长度,你可以只使用二进制 numpy 数组的 np.sum
。事实上,你甚至可以使用 np.any
因为你检查长度是否小于 1.
然后您可以避免使用预分配缓冲区创建临时 Numpy 数组,并通过在 Numpy 操作中指定输出缓冲区。例如,您可以使用 np.logical_and(A, B, out=your_preallocated_buffer)
而不仅仅是 A & B
.
最后,如果(且仅当)所有前面的步骤都不足以使整体计算速度提高数百或数千倍,您可以通过先将数据帧转换为 Numpy 数组来使用 Numba(因为 Numba 不支持数据框)。如果这还不够,您可以使用 prange
(而不是 range
)和 Numba 的标志 parallel=True
以便使用多线程。
请注意 Pandas 并不是真正设计用于操作 3000 列的数据帧,因此肯定不会很快。 Numpy 更适合处理矩阵。
在 Jerome 的指导下,我完成了以下工作:
第一步:创建字典
matrix_dict = matrix.copy()
matrix_dict = matrix_dict.set_index(['antecedent_sku', 'consequent_sku'])['similarity'].to_dict()
matrix_dict 看起来像这样:
{(001, 002): 0.3}
第 2 步:用 matrix_dict
中的值填充相似度
for ind in tqdm(list(similarity.index)):
for col in list(similarity.columns):
if ind==col:
similarity.loc[ind, col] = 1
else:
similarity.loc[ind, col] = matrix_dict.get((int(ind), int(col)))
第 3 步:用零填充
similarity = similarity.fillna(0)
结果:x35 性能(4 小时 20 分钟到 7 分钟)
我有一个名为 'matrix' 的 pandas 数据框,它看起来像这样:
antecedent_sku consequent_sku similarity
0 001 002 0.3
1 001 003 0.2
2 001 004 0.1
3 001 005 0.4
4 002 001 0.4
5 002 003 0.5
6 002 004 0.1
在这个数据框之外,我想创建一个相似矩阵以进一步聚类。我分两步完成。
第 1 步:创建一个空的相似度矩阵 ('similarity')
set_name = set(matrix['antecedent_sku'].values)
similarity = pd.DataFrame(index = list(set_name), columns = list(set_name))
第 2 步:用 'matrix':
中的值填充它for ind in tqdm(list(similarity.index)):
for col in list(similarity.columns):
if ind==col:
similarity.loc[ind, col] = 1
elif len(matrix.loc[(matrix['antecedent_sku'].values==f'{ind}') & (matrix['consequent_sku'].values==f'{col}'), 'similarity'].values) < 1:
similarity.loc[ind, col] = 0
else:
similarity.loc[ind, col] = matrix.loc[(matrix['antecedent_sku'].values==f'{ind}') & (matrix['consequent_sku'].values==f'{col}'), 'similarity'].values[0]
问题:填充形状为 (3000,3000) 的矩阵需要 4 个小时。
问题:我做错了什么?我的目标是用 Cython/Numba 之类的东西来加速代码,还是问题出在我的方法的架构上,我应该使用内置函数或其他一些聪明的方法将 'matrix' 转换为 'similarity' 而不是双循环?
P.S。我运行Python3.8.7
众所周知,使用 loc
遍历 pandas 数据帧非常慢。众所周知,CPython 解释器也很慢(通常是循环)。每个 pandas 操作都有很高的开销。但是,要点是您迭代了 3000x3000 个元素,以便为每个元素调用诸如 matrix['antecedent_sku'].values==f'{ind}'
之类的东西,它肯定会迭代 3000 个项目,这些项目是字符串,也被认为是一种低效的数据类型(因为处理器需要解析一个 variable-length UTF-8 多字符序列)。由于每次迭代执行两次并且您为每次比较解析一个新整数,这意味着将执行 3000*3000*3000*2 = 54_000_000_000
个字符串比较,总共 3000*3000*3000*2*2*3 = 324_000_000_000
个字符进行(低效)比较! 这不可能很快,因为效率很低。更不用说每个 9_000_000 迭代 creates/delete 几个临时数组和 Pandas 对象。
首先要做的是由于一些预计算减少重新计算操作的数量。实际上,您可以将 matrix['antecedent_sku'].values==f'{ind}'
的值(因为 Numpy 数组,因为 pandas 系列效率低下)存储在由 ind
索引的字典中,以便在循环中更快地获取它。这应该使这部分快 3000 倍(因为应该只有 3000 个项目)。更好的是:您可以使用 groupby 来更有效地做到这一点。
此外,您可以将列转换为整数(即antecedent_sku
和consequent_sku
)以避免许多昂贵的字符串比较。
然后你可以像matrix.loc[..., 'similarity'].values
一样删除无用的操作。事实上,由于你只想知道结果的长度,你可以只使用二进制 numpy 数组的 np.sum
。事实上,你甚至可以使用 np.any
因为你检查长度是否小于 1.
然后您可以避免使用预分配缓冲区创建临时 Numpy 数组,并通过在 Numpy 操作中指定输出缓冲区。例如,您可以使用 np.logical_and(A, B, out=your_preallocated_buffer)
而不仅仅是 A & B
.
最后,如果(且仅当)所有前面的步骤都不足以使整体计算速度提高数百或数千倍,您可以通过先将数据帧转换为 Numpy 数组来使用 Numba(因为 Numba 不支持数据框)。如果这还不够,您可以使用 prange
(而不是 range
)和 Numba 的标志 parallel=True
以便使用多线程。
请注意 Pandas 并不是真正设计用于操作 3000 列的数据帧,因此肯定不会很快。 Numpy 更适合处理矩阵。
在 Jerome 的指导下,我完成了以下工作:
第一步:创建字典
matrix_dict = matrix.copy()
matrix_dict = matrix_dict.set_index(['antecedent_sku', 'consequent_sku'])['similarity'].to_dict()
matrix_dict 看起来像这样:
{(001, 002): 0.3}
第 2 步:用 matrix_dict
中的值填充相似度for ind in tqdm(list(similarity.index)):
for col in list(similarity.columns):
if ind==col:
similarity.loc[ind, col] = 1
else:
similarity.loc[ind, col] = matrix_dict.get((int(ind), int(col)))
第 3 步:用零填充
similarity = similarity.fillna(0)
结果:x35 性能(4 小时 20 分钟到 7 分钟)