将功能有效地并行应用到分组的熊猫DataFrame


89

我经常需要将函数应用于非常大的组DataFrame(混合数据类型),并想利用多个内核。

我可以从组中创建一个迭代器并使用多处理模块,但是这样做效率不高,因为每个组和函数的结果都必须经过腌制才能在进程之间进行消息传递。

有什么方法可以避免酸洗,甚至避免DataFrame完全复制?看起来多处理模块的共享内存功能仅限于numpy数组。还有其他选择吗?


据我所知,没有办法共享任意对象。我想知道,酸洗是否需要比通过多处理获得的时间多得多的时间。也许您应该寻找一种可能性,为每个过程创建更大的工作包,以减少相对的酸洗时间。另一种可能是在创建组时使用多处理。
塞巴斯蒂安·

3
我做类似的事情,但使用UWSGI,Flask和preforking:我将pandas数据帧加载到一个进程中,将其分叉x次(使其成为共享内存对象),然后从另一个python进程中调用这些进程,在该进程中我将其合并结果。大气压我使用JSON作为通信处理,但这是未来(但高度实验仍然):pandas.pydata.org/pandas-docs/dev/io.html#msgpack-experimental
岩溶

顺便说一句,您曾经看过带有分块的HDF5吗?(HDF5不会为并发写入而保存,但您也可以将其保存到单独的文件中,并最终将其连接起来)
Carst 2013年

7
这将针对0.14,请参阅此问题:github.com/pydata/pandas/issues/5751
Jeff

4
@Jeff被推到0.15 =(
pyCthon 2014年

Answers:


12

从上面的评论来看,这似乎计划了pandas一段时间(还有一个我刚刚注意到的有趣的rosetta项目)。

但是,直到将所有并行功能都集成到中pandas,我才注意到,pandas直接使用cython+ OpenMP和C ++ 编写高效且非内存复制的并行增强非常容易。

这是编写并行groupby-sum的简短示例,其用法如下所示:

import pandas as pd
import para_group_demo

df = pd.DataFrame({'a': [1, 2, 1, 2, 1, 1, 0], 'b': range(7)})
print para_group_demo.sum(df.a, df.b)

输出为:

     sum
key     
0      6
1      11
2      4

注意毫无疑问,此简单示例的功能最终将成为的一部分pandas。但是,在C ++中使用某些东西在一段时间内会变得更加自然,并且重要的是要意识到将其组合到中是多么容易pandas


为此,我编写了一个简单的单一源文件扩展名,其代码如下。

它从一些导入和类型定义开始

from libc.stdint cimport int64_t, uint64_t
from libcpp.vector cimport vector
from libcpp.unordered_map cimport unordered_map

cimport cython
from cython.operator cimport dereference as deref, preincrement as inc
from cython.parallel import prange

import pandas as pd

ctypedef unordered_map[int64_t, uint64_t] counts_t
ctypedef unordered_map[int64_t, uint64_t].iterator counts_it_t
ctypedef vector[counts_t] counts_vec_t

C ++ unordered_map类型用于单个线程求和,而C vector用于所有线程求和。

现在要功能了sum。它从键入的内存视图开始以快速访问:

def sum(crit, vals):
    cdef int64_t[:] crit_view = crit.values
    cdef int64_t[:] vals_view = vals.values

该函数通过将半等值除以线程(在此硬编码为4),并使每个线程将其范围内的条目相加来继续:

    cdef uint64_t num_threads = 4
    cdef uint64_t l = len(crit)
    cdef uint64_t s = l / num_threads + 1
    cdef uint64_t i, j, e
    cdef counts_vec_t counts
    counts = counts_vec_t(num_threads)
    counts.resize(num_threads)
    with cython.boundscheck(False):
        for i in prange(num_threads, nogil=True): 
            j = i * s
            e = j + s
            if e > l:
                e = l
            while j < e:
                counts[i][crit_view[j]] += vals_view[j]
                inc(j)

线程完成后,该函数将所有结果(来自不同范围)合并为一个unordered_map

    cdef counts_t total
    cdef counts_it_t it, e_it
    for i in range(num_threads):
        it = counts[i].begin()
        e_it = counts[i].end()
        while it != e_it:
            total[deref(it).first] += deref(it).second
            inc(it)        

剩下的就是创建一个DataFrame并返回结果:

    key, sum_ = [], []
    it = total.begin()
    e_it = total.end()
    while it != e_it:
        key.append(deref(it).first)
        sum_.append(deref(it).second)
        inc(it)

    df = pd.DataFrame({'key': key, 'sum': sum_})
    df.set_index('key', inplace=True)
    return df
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.