从Pandas Dataframe并行/矢量化组合计算

时间:2019-04-04 02:51:26

标签: python pandas numpy

我有许多腌制的熊猫数据框,每个框都有相当数量的行(〜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来完成此任务,所以我很可能会错过一些简单的东西,有没有很好的方法来解决这个问题?

2 个答案:

答案 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并行化!