我有许多腌制的熊猫数据框,每个框都有相当数量的行(〜10k)。数据帧的其中一列是浮点数的numpy ndarray(是的,我特别选择将数组数据存储在单个单元格中-我读过这通常不是正确的方法,例如here ,但在这种情况下,单个值是没有意义的,只有值的完整列表才具有意义,因此在这种情况下,我认为这是有意义的。我需要计算框架中每对行之间的欧式距离。我有为此工作的代码,但我希望我可以做一些事情来改善它的性能,因为现在它告诉我较小的数据集将需要一个月以上的时间,但是我敢肯定它会花费我所有的记忆都在那之前很久。
代码如下:
import pandas as pd
import sys
import getopt
import math
from scipy.spatial import distance
from timeit import default_timer as timer
from datetime import timedelta
id_column_1 = 'id1'
id_column_2 = 'id2'
distance_column = 'distance'
val_column = 'val'
# where n is the size of the set
# and k is the number of elements per combination
def combination_count(n, k):
if k > n:
return 0
else:
# n! / (k! * (n - k)!)
return math.factorial(n)/(math.factorial(k) * math.factorial(n - k))
def progress(start, current, total, id1, id2):
if current == 0:
print('Processing combination #%d of #%d, (%d, %d)' % (current, total, id1, id2))
else:
percent_complete = 100 * float(current)/float(total)
elapsed_time = timer() - start
avg_time = elapsed_time / current
remaining = total - current
remaining_time = timedelta(seconds=remaining * avg_time)
print('Processing combination #%d of #%d, (%d, %d). %.2f%% complete, ~%.2f s/combination, ~%s remaining' % (current, total, id1, id2, percent_complete, avg_time, remaining_time))
def check_distances(df):
indexes = df.index
total_combinations = combination_count(len(indexes), 2)
current_combination = 0
print('There are %d possible inter-message relationships to compute' % total_combinations)
distances = pd.DataFrame(columns=[id_column_1, id_column_2, distance_column])
distances.set_index([id_column_1, id_column_2], inplace=True)
start = timer()
for id1 in indexes:
for id2 in indexes:
# id1 is always < id2
if id1 >= id2:
continue
progress(start, current_combination, total_combinations, id1, id2)
distances.loc[(id1, id2), distance_column] = distance.euclidean(df.loc[id1, embeddings_column], df.loc[id2, embeddings_column])
current_combination+=1
(我排除了main()函数,该函数仅提取args并基于它们将其加载到腌制的文件中)
我最近才真正开始使用Python来完成此任务,所以我很可能会错过一些简单的东西,有没有很好的方法来解决这个问题?
答案 0 :(得分:1)
在纯python中有一些用于并行计算数据帧的选项。
最完整的可能是dask
一个更简单但更容易的选项是pandaral-lel
答案 1 :(得分:0)
因此解决方案最终是并行化,但是我无法使用Panda特定的并行化库弄清楚这一点,因为预期结果不是对现有单元格内容的转换,而是从另一个数据帧派生的新值。 / p>
我抓住了joblib library并采取了以下步骤:
首先,我创建了一个函数,给定两个id,该函数可以返回该索引的行(由于单独的工作程序无法在主流程中更改数据框,因此,我们必须转到首先生成所有数据的范例,然后构建数据框):
def get_distances(df):
indexes = df.index
total_combinations = combination_count(len(indexes), 2)
current_combination = 0
print('There are %d possible inter-message relationships to compute' % total_combinations)
data = Parallel(n_jobs=-1)(delayed(get_distance)(df, min(ids), max(ids)) for ids in combinations(indexes, 2))
distances = pd.DataFrame(data, columns=[id_column_1, id_column_2, distance_column])
distances.set_index([id_column_1, id_column_2], inplace=True)
return distances
并对其应用joblib并行化:
def get_distance(id1, id2, embed1, embed2):
return [id1, id2, distance.euclidean(embed1, embed2)]
# ...later, in get_distances()...
data = Parallel(n_jobs=-1)(delayed(get_distance)(min(ids), max(ids), df.loc[ids[0], embeddings_column], df.loc[ids[1], embeddings_column]) for ids in combinations(indexes, 2))
在预期的时间内,这从几个月到几天都有了立即的改善,但是我怀疑传递完整的数据帧会产生大量的开销。
将功能修改为仅传递所需的值后,又获得了不到一天(约20小时)的立即改进:
data = Parallel(n_jobs=-1, backend='multiprocessing')(delayed(get_distance)(min(ids), max(ids), df.loc[ids[0], embeddings_column], df.loc[ids[1], embeddings_column]) for ids in tqdm(combinations(indexes, 2), total=total_combinations))
最后,基于joblib's docs以及大量数据仍在传输给工作人员这一事实,我交换到多处理后端,并看到预期的时间进一步降至约1.5小时。 (我还添加了tqdm库,因此我可以比joblib提供更好的进度想法)
{{1}}
希望这可以帮助其他人首次涉足Python并行化!