web-dev-qa-db-fra.com

Appliquer efficacement une fonction à un groupe pandas Dataframe en parallèle

J'ai souvent besoin d'appliquer une fonction aux groupes d'un très grand DataFrame (de types de données mixtes) et souhaitez tirer parti de plusieurs cœurs.

Je peux créer un itérateur à partir des groupes et utiliser le module multiprocessionnant, mais il n'est pas efficace car chaque groupe et les résultats de la fonction doivent être marinés pour la messagerie entre les processus.

Y a-t-il un moyen d'éviter le décapage ou même d'éviter la copie du DataFrame complètement? On dirait que les fonctions de mémoire partagées des modules multiprocessionnaires sont limitées aux tableaux numpy. Il y a-t-il des alternatives?

88
user2303

Des commentaires ci-dessus, il semble que cela soit prévu pour pandas quelque temps (il existe également un projet intéressant rosetta projet que je viens de remarquer).

Cependant, jusqu'à ce que chaque fonctionnalité parallèle soit intégrée à pandas _, j'ai remarqué qu'il est très facile d'écrire des augmentations parallèles efficaces et non à la copie de mémoire à pandas directement en utilisant cython + openmp et c ++.

Voici un court exemple d'écriture d'une somme parallèle à la somme, dont la consommation est quelque chose comme ceci:

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)

et la sortie est:

     sum
key     
0      6
1      11
2      4

Remarque sans doute, la fonctionnalité de cet exemple simple fera finalement partie de pandas. Certaines choses, cependant, seront plus naturelles à paralléliser en C++ pendant un certain temps, et il est important de savoir à quel point il est facile de le combiner dans pandas.


Pour ce faire, j'ai écrit une simple extension de fichier à source unique dont le code suit.

Il commence par certaines importations et définitions de type

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

Le C++ unordered_map Type est pour la somme de sommation par un seul thread, et le vector est destiné à la somme de tous les threads.

Maintenant à la fonction sum. Il démarre avec Vues de mémoire tapé pour un accès rapide:

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

La fonction se poursuit en divisant les semi-égales aux fils (ici codé en dur à 4) et que chaque thread somme les entrées dans sa plage:

    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)

Lorsque les threads sont terminés, la fonction fusionne tous les résultats (à partir des différentes gammes) en un seul 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)        

Tout ce qui reste est de créer un DataFrame et renvoyer les résultats:

    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
12
Ami Tavory