【问题标题】:Multiprocessing writing to pandas dataframe多处理写入熊猫数据框
【发布时间】:2016-02-06 15:03:19
【问题描述】:

所以我想用下面的代码做的是读取列表列表并将它们放入名为checker 的函数中,然后让log_result 处理函数checker 的结果。我正在尝试使用多线程来执行此操作,因为变量名称 rows_to_parse 实际上有数百万行,因此使用多个内核应该会大大加快这个过程。

目前的代码不起作用并导致 Python 崩溃。

我的顾虑和问题:

  1. 希望保存在变量df 中的现有df 来维护 索引整个过程,否则log_result 将得到 对需要更新哪一行感到困惑。
  2. 我很确定apply_async 不合适 多处理功能来执行这项职责,因为我相信 计算机读取和写入df的顺序可能会损坏它???
  3. 我认为可能需要设置一个队列来读写df 但我不确定我将如何去做。

感谢您的帮助。

import pandas as pd
import multiprocessing
from functools import partial

def checker(a,b,c,d,e):
    match = df[(df['a'] == a) & (df['b'] == b) & (df['c'] == c) & (df['d'] == d) & (df['e'] == e)]
    index_of_match = match.index.tolist()
    if len(index_of_match) == 1: #one match in df
        return index_of_match
    elif len(index_of_match) > 1: #not likely because duplicates will be removed prior to: if "__name__" == __main__:
        return [index_of_match[0]]
    else: #no match, returns a result which then gets processed by the else statement in log_result. this means that [a,b,c,d,e] get written to the df
        return [a,b,c,d,e]



def log_result(result, dataf):
    if len(result) == 1: #
        dataf.loc[result[0]]['e'] += 1 
    else: #append new row to exisiting df
        new_row = pd.DataFrame([result],columns=cols)
        dataf = dataf.append(new_row,ignore_index=True)


def apply_async_with_callback(parsing_material, dfr):
    pool = multiprocessing.Pool()
    for var_a, var_b, var_c, var_d, var_e in parsing_material:
        pool.apply_async(checker, args = (var_a, var_b, var_c, var_d, var_e), callback = partial(log_result,dataf=dfr))
    pool.close()
    pool.join()



if __name__ == '__main__':
    #setting up main dataframe
    cols = ['a','b','c','d','e']
    existing_data = [["YES","A","16052011","13031999",3],
                    ["NO","Q","11022003","15081999",3],
                    ["YES","A","22082010","03012001",9]]

    #main dataframe
    df = pd.DataFrame(existing_data,columns=cols)

    #new data
    rows_to_parse = [['NO', 'A', '09061997', '06122003', 5],
                    ['YES', 'W', '17061992', '26032012', 6],
                    ['YES', 'G', '01122006', '07082014', 2],
                    ['YES', 'N', '06081992', '21052008', 9],
                    ['YES', 'Y', '18051995', '24011996', 6],
                    ['NO', 'Q', '11022003', '15081999', 3],
                    ['NO', 'O', '20112004', '28062008', 0],
                    ['YES', 'R', '10071994', '03091996', 8],
                    ['NO', 'C', '09091998', '22051992', 1],
                    ['YES', 'Q', '01051995', '02012000', 3],
                    ['YES', 'Q', '26022015', '26092007', 5],
                    ['NO', 'F', '15072002', '17062001', 8],
                    ['YES', 'I', '24092006', '03112003', 2],
                    ['YES', 'A', '22082010', '03012001', 9],
                    ['YES', 'I', '15072016', '30092005', 7],
                    ['YES', 'Y', '08111999', '02022006', 3],
                    ['NO', 'V', '04012016', '10061996', 1],
                    ['NO', 'I', '21012003', '11022001', 6],
                    ['NO', 'P', '06041992', '30111993', 6],
                    ['NO', 'W', '30081992', '02012016', 6]]


    apply_async_with_callback(rows_to_parse, df)

【问题讨论】:

  • 还有什么:#no match,给它参数以写入 df 应该做的事情?我认为如果你 return [a, b, c, d, e] 你的代码实际上会完成,但你会有其他问题,你也永远不会在任何地方使用 dataf
  • 感谢您指出这一点,我已经修改了代码。所以[a,b,c,d,e] 被写入函数log_result 中的df。
  • partial(log_result,dataf=dfr)log_results 的签名不匹配

标签: python multithreading python-2.7 pandas multiprocessing


【解决方案1】:

像这样在 MultiProcessing 中更新 DataFrame 是行不通的:

dataf = dataf.append(new_row,ignore_index=True)

一方面这是非常低效的(每个追加的 O(n),所以总共 O(n^2)。首选方法是一次将一些对象连接在一起。

另一方面,更重要的是,dataf 不会为每次更新锁定,因此不能保证两个操作不会冲突(我猜这是导致 python 崩溃)。

最后,append 没有起作用,所以一旦回调完成,变量dataf 就会被丢弃!!并且没有对父 dataf 进行任何更改。


我们可以使用MultiProcessing listdict。如果您不关心 order 或 dict 如果您关心(例如枚举),请列出,因为您必须注意,这些值不是以异步的明确定义的顺序返回的。
(或者我们可以自己创建一个实现 Lock 的对象,请参阅 Eli Bendersky。)
所以做了以下改动:

df = pd.DataFrame(existing_data,columns=cols)
# becomes
df = pd.DataFrame(existing_data,columns=cols)
d = MultiProcessing.list([df])

dataf = dataf.append(new_row,ignore_index=True)
# becomes
d.append(new_row)

现在,一旦异步完成,您就有了一个 MultiProcessing.list 的 DataFrame。您可以连接这些(和ignore_index)以获得所需的结果:

pd.concat(d, ignore_index=True)

应该做的伎俩。


注意:在每个阶段创建 newrow DataFrame 的效率也低于让 pandas 一次性将列表列表直接解析为 DataFrame 的效率。希望这是一个玩具示例,真的你希望你的块非常大以通过 MultiProcessing 获得胜利(我听说 50kb 作为经验法则......),一次一行永远不会成为在这里赢。


另外:您应该避免在代码中使用全局变量(如 df),在函数中传递它们会更简洁(在这种情况下,作为检查器的参数)。

【讨论】:

  • 如果你不重新分配 DataFrame 并且从不两次写入同一位置,那么从多个线程同时写入它是否安全?
  • 在 MultiProcessing.list/dict 上应该没问题,它处理锁定,所以会很安全。
猜你喜欢
  • 2019-07-08
  • 1970-01-01
  • 2018-10-20
  • 2014-03-28
  • 1970-01-01
  • 2016-12-25
  • 2014-06-25
相关资源
最近更新 更多