更快的方法来总结数据框中的所有行组合
Posted
技术标签:
【中文标题】更快的方法来总结数据框中的所有行组合【英文标题】:Faster way to sum all combinations of rows in dataframe 【发布时间】:2021-07-13 14:57:37 【问题描述】:我有一个包含 10,000 行的数据框,我试图将这些行的所有可能组合相加。根据我的数学,这大约是 5000 万个组合。我将举一个小例子来简化我的数据的样子:
df = Ratio Count Score
1 6 11
2 7 12
3 8 13
4 9 14
5 10 15
这是想要的结果:
results = Min Ratio Max Ratio Total Count Total Score
1 2 13 23
1 3 21 36
1 4 30 50
1 5 40 65
2 3 15 25
2 4 24 39
2 5 34 54
3 4 17 27
3 5 27 42
4 5 19 29
这是我用来完成计算的代码:
for i in range(len(df)):
j = i + 1
while j <= len(df):
range_to_calc = df.iloc[i:j]
total_count = range_to_calc['Count'].sum()
total_score = range_to_calc['Score'].sum()
new_row = 'Min Ratio': range_to_calc.at[range_to_calc.first_valid_index(),'Ratio'],
'Max Ratio': range_to_calc.at[range_to_calc.last_valid_index(),'Ratio'],
'Total Count': total_count,
'Total Score': total_score
results = results.append(new_row, ignore_index=True)
j = j + 1
此代码有效,但根据我的估计,运行几分钟后,需要 200 小时才能完成。我知道使用 numpy 会快很多,但我无法理解如何构建多个数组以相加。 (我认为如果我只做 1+2、2+3、3+4 等会很容易,但要困难得多,因为我需要 1+2、1+2+3、1+2+3 +4 等)是否有更有效的方法来完成此计算,以便它可以在合理的时间内运行?谢谢!
P.S.:如果您想知道我想对 5000 万行数据框做什么,我实际上并不需要在最终结果中使用它。我最终希望将结果中每一行的总分除以其总计数,以获得每个总计数的总分值,然后显示每个总计数的 1,000 个最高总分,以及每个相关的最小比率、最大值比率、总计数和总分。
【问题讨论】:
我不回答你的问题,但你真的需要预先计算和存储所有组合吗?你能不能在需要的时候不使用df[2:4].sum()
之类的东西?
我刚刚编辑了我的帖子以添加 P.S.我认为可以解决您的问题。我最终想比较每一行的值,因为我不知道哪些比率将提供最高的每次计数分数,我认为我能做到这一点的唯一方法是存储所有计算的组合,(或至少存储前 1000并在计算较高的值时剔除最低值。)
所以你不考虑单行作为一个组合?
您已经创建了一个 N=10000 的 N-combinatorics 大小的问题,您绝对需要等待一周才能完成。您可以加快速度,使用 python 矢量化从您的英特尔处理器中提取每个转速,请参阅youtube.com/watch?v=EEUXKG97YRw
@mr7 假设你的数据框中有k
条目并且没有负分,最大的分数将是df[1:k].sum().Score
,然后检查df[1:k-1].sum().Score
或df[2:k].sum().Score
是否是第二大的可能的总和并重复操作,直到找到最大的 1000 分。它将为您节省一周的计算时间。
【参考方案1】:
经过这些改进后,运行 10k 行需要 ~2 分钟。
对于总和计算,您可以预先计算cumulative sum(cumsum)
并保存。 sum(i to j)
等于 sum(0 to j) - sum(0 to i-1)
。
现在sum(0 to j)
是cumsum[j]
和sum(0 to i - 1)
是cumsum[i-1]
。
所以sum(i to j) = cumsum[j] - cumsum[i - 1]
。
这比每次针对不同组合的计算总和都有显着改进。
numpy
数组上的操作比 pandas 系列上的操作更快,因此将每个列转换为 numpy 数组,然后对其进行计算。
(来自其他答案):不是在列表中追加,而是初始化一个大小为((n*(n+1)//2) -n , 4)
的空numpy数组并使用它来保存结果。
用途:
count_cumsum = np.cumsum(df.Count.values)
score_cumsum = np.cumsum(df.Score.values)
ratios = df.Ratio.values
n = len(df)
rowInCombination = (n * (n + 1) // 2) - n
arr = np.empty(shape = (rowInCombination, 4), dtype = int)
k = 0
for i in range(len(df)):
for j in range(i + 1, len(df)):
arr[k, :] = ([
count_cumsum[j] - count_cumsum[i-1] if i > 0 else count_cumsum[j],
score_cumsum[j] - score_cumsum[i-1] if i > 0 else score_cumsum[j],
ratios[i],
ratios[j]])
k = k + 1
out = pd.DataFrame(arr, columns = ['Total_Count', 'Total_Score',
'Min_Ratio', 'Max_Ratio'])
输入:
df = pd.DataFrame('Ratio': [1, 2, 3, 4, 5],
'Count': [6, 7, 8, 9, 10],
'Score': [11, 12, 13, 14, 15])
输出:
>>>out
Min_Ratio Max_Ratio Total_Count Total_Score
0 1 2 13 23
1 1 3 21 36
2 1 4 30 50
3 1 5 40 65
4 2 3 15 25
5 2 4 24 39
6 2 5 34 54
7 3 4 17 27
8 3 5 27 42
9 4 5 19 29
【讨论】:
@AmitVikramsign 不错! +1 我可以提出提高速度的建议吗?使用 numpy 像count_cumsum = np.cumsum(df['Count'].to_numpy())
和 score_cumsum = np.cumsum(df['Score'].to_numpy())
这样的 cumsum 按照我的时间安排将时间减半。
@ScottBoston 不错的建议。 numpy 数组上的操作比 pandas 系列操作更快。 .我已经更新了我的答案。
@ScottBoston 这里似乎真正的瓶颈是在每次迭代中构造 new_row dict。
我试图通过使用 itertools 组合来消除双循环,但这没有帮助:)
看起来列表理解可以给我们 10 倍的改进。我更新了我的答案。【参考方案2】:
首先,您可以改进算法。然后,您可以使用 Numpy 矢量化/广播加快计算速度。
以下是提高算法性能的有趣点:
Pandas 的append
很慢,因为它重新创建了一个新的数据框。你永远不应该在代价高昂的循环中使用它。相反,您可以将这些行附加到 Python 列表中,甚至直接将项目写入预先分配的 Numpy 向量中。
计算部分和需要 O(n)
时间,而您可以预先计算累积和,然后在恒定时间内找到部分和。
CPython 循环非常慢,但由于广播,内部循环可以使用 Numpy 进行矢量化。
这是生成的代码:
import numpy as np
import pandas as pd
def fastImpl(df):
n = len(df)
resRowCount = (n * (n+1)) // 2
k = 0
cumCounts = np.concatenate(([0], df['Count'].astype(int).cumsum()))
cumScores = np.concatenate(([0], df['Score'].astype(int).cumsum()))
ratios = df['Ratio'].astype(int)
minRatio = np.empty(resRowCount, dtype=int)
maxRatio = np.empty(resRowCount, dtype=int)
count = np.empty(resRowCount, dtype=int)
score = np.empty(resRowCount, dtype=int)
for i in range(n):
kStart, kEnd = k, k+(n-i)
jStart, jEnd = i+1, n+1
minRatio[kStart:kEnd] = ratios[i]
maxRatio[kStart:kEnd] = ratios[i:n]
count[kStart:kEnd] = cumCounts[jStart:jEnd] - cumCounts[i]
score[kStart:kEnd] = cumScores[jStart:jEnd] - cumScores[i]
k = kEnd
assert k == resRowCount
return pd.DataFrame(
'Min Ratio': minRatio,
'Max Ratio': maxRatio,
'Total Count': count,
'Total Score': score
)
请注意,此代码给出的结果与您问题中的代码相同,但原始代码并未给出问题中所述的预期结果。另请注意,由于输入是整数,为了性能,我强制 Numpy 使用整数(尽管该算法也应该使用浮点数)。
此代码比大数据帧上的原始代码快数十万倍,它成功地在 0.7 秒内计算出 10,000 行的数据帧。
【讨论】:
感谢您的贡献!有空我会测试一下。 太棒了!想不出更好的解决方案。【参考方案3】:其他人已经解释了为什么你的算法这么慢,所以我会深入研究。
让我们对您的问题采取不同的方法。具体来说,看看Total Count
和Total Score
列是如何计算的:
由于累加和是累加的,我们只需要对第1行到第n行计算一次:
(2到n)的cumsum是(1到n)-(第1行)的cumsum (3 到 n) 的 cumsum 是 (2 to n) - (row 2) 的 cumsum 等等……换句话说,当前的 cumsum 是前一个 cumsum 减去它的第一行,然后删除第一行。
正如您所推测的那样,pandas 比 numpy 慢很多,因此我们将一切都转换为 numpy 以提高速度:
arr = df[['Ratio', 'Count', 'Score']].to_numpy() # Convert to numpy array
tmp = np.cumsum(arr[:, 1:3], axis=0) # calculate cumsum for row 1 to n
tmp = np.insert(tmp, 0, arr[0, 0], axis=1) # create the Min Ratio column
tmp = np.insert(tmp, 1, arr[:, 0], axis=1) # create the Max Ratio column
results2 = [tmp]
for i in range(1, len(arr)):
tmp = results2[-1][1:] # current cumsum is the previous cumsum without the first row
diff = results2[-1][0] # the previous cumsum's first row
tmp -= diff # adjust the current cumsum
tmp[:, 0] = arr[i, 0] # new Min Ratio
tmp[:, 1] = arr[i:, 0] # new Max Ratio
results2.append(tmp)
# Assemble the result
results2 = np.concatenate(results2).reshape(-1,4)
results2 = pd.DataFrame(results2, columns=['Min Ratio', 'Max Ratio', 'Total Count', 'Total Score'])
在我的测试期间,这会在大约 2 秒内产生 10k 行数据帧的结果。
【讨论】:
感谢您的解释!这是非常聪明的想法,我从来没有想过!【参考方案4】:抱歉,这个主题写得晚了,但我只是在寻找类似主题的解决方案。这个问题的解决方案很简单,因为组合只是成对的。这可以通过将数据帧上传到任何数据库并执行以下持续时间小于 10 秒的查询来解决:
SEL f1.*,f2.*,f1.score+f2.score
FROM table_with_data_source f1, table_with_data_source f2
where f1.ratio<>f2.ratio;
即使有 100,000 条或更多记录,数据库也会很快完成。
但是,我在答案中看到的所有算法都没有真正执行值的组合。他只成对做。当它是一个真正的组合时,问题就变得复杂了,例如:
给定:a、b、c、d 和 e 作为记录:
a
b
c
d
e
真正的组合是:
a+b
a+c
a+d
a+e
a+b+c
a+b+d
a+b+e
a+c+d
a+c+e
a+d+e
a+b+c+d
a+b+c+e
a+c+d+e
a+b+c+d+e
b+c
b+d
b+e
b+c+d
b+c+e
b+d+e
c+d
c+e
c+d+e
d+e
这是一个真正的组合,它涵盖了所有可能的组合。对于这种情况,我无法找到合适的解决方案,因为它确实会影响任何硬件的性能。任何人都知道如何使用 python 执行真正的组合?在数据库级别,它会影响数据库的总体性能。
【讨论】:
以上是关于更快的方法来总结数据框中的所有行组合的主要内容,如果未能解决你的问题,请参考以下文章