web-dev-qa-db-fra.com

Python: utiliser le multitraitement sur une base de données pandas

Je veux utiliser multiprocessing sur un grand ensemble de données pour trouver la distance entre deux points gps. J'ai construit un ensemble de test, mais je n'ai pas pu obtenir multiprocessing pour travailler sur cet ensemble.

import pandas as pd
from geopy.distance import vincenty
from itertools import combinations
import multiprocessing as mp

df = pd.DataFrame({'ser_no': [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
                'co_nm': ['aa', 'aa', 'aa', 'bb', 'bb', 'bb', 'bb', 'cc', 'cc', 'cc'],
                'lat': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
                'lon': [21, 22, 23, 24, 25, 26, 27, 28, 29, 30]})



def calc_dist(x):
    return pd.DataFrame(
               [ [grp,
                  df.loc[c[0]].ser_no,
                  df.loc[c[1]].ser_no,
                  vincenty(df.loc[c[0], x], 
                           df.loc[c[1], x])
                 ]
                 for grp,lst in df.groupby('co_nm').groups.items()
                 for c in combinations(lst, 2)
               ],
               columns=['co_nm','machineA','machineB','distance'])

if __== '__main__':
    pool = mp.Pool(processes = (mp.cpu_count() - 1))
    pool.map(calc_dist, ['lat','lon'])
    pool.close()
    pool.join()

J'utilise Python 2.7.11 et Ipython 4.1.2 avec Anaconda 2.5.0 64 bits sur Windows7 Professional lorsque cette erreur se produit.

runfile ('C: /.../ Bureau/test.py multitraitement', wdir = 'C: /.../ Bureau') Traceback (appel le plus récent en dernier):

Fichier "", ligne 1, dans runfile ('C: /.../ Desktop/test.py de multitraitement', wdir = 'C: /.../ Desktop')

Fichier "C: ...\Local\Continuum\Anaconda2\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", ligne 699, dans le fichier d'exécution execfile (nom de fichier, espace de noms)

Fichier "C: ...\Local\Continuum\Anaconda2\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", ligne 74, dans execfile exec (compile (scripttext, nom du fichier, 'exec'), glob, loc)

Fichier "C: /..../ multitraitement test.py", ligne 33, dans pool.map (calc_dist, ['lat', 'lon'])

Fichier "C: ...\AppData\Local\Continuum\Anaconda2\lib\multiprocessing\pool.py", ligne 251, dans la carte retourne self.map_async (func, iterable, chunksize) .get ()

Fichier "C: ...\Local\Continuum\Anaconda2\lib\multiprocessing\pool.py", ligne 567, dans get augmenter self._value

TypeError: Impossible de créer une instance de point à partir de 1.

def get(self, timeout=None):
    self.wait(timeout)
    if not self._ready:
        raise TimeoutError
    if self._success:
        return self._value
    else:
        raise self._value
20
dustin

Qu'est-ce qui ne va pas

Cette ligne de votre code:

pool.map(calc_dist, ['lat','lon'])

génère 2 processus - l'un exécute calc_dist('lat') et l'autre calc_dist('lon'). Comparez le premier exemple dans doc . (Fondamentalement, pool.map(f, [1,2,3]) appelle f trois fois avec les arguments indiqués dans la liste suivante: f(1), f(2) et f(3).) Si je ne me trompe pas, votre fonction calc_dist ne peut s'appeler que calc_dist('lat', 'lon'). Et cela ne permet pas le traitement en parallèle.

Solution

Je crois que vous souhaitez fractionner le travail entre les processus, en envoyant probablement chaque Tuple (grp, lst) à un processus séparé. Le code suivant fait exactement cela. 

Commençons par préparer le fractionnement:

grp_lst_args = list(df.groupby('co_nm').groups.items())

print(grp_lst_args)
[('aa', [0, 1, 2]), ('cc', [7, 8, 9]), ('bb', [3, 4, 5, 6])]

Nous enverrons chacun de ces tuples (ici, il y en a trois) sous forme d'argument d'une fonction dans un processus séparé. Nous devons réécrire la fonction, appelons-la calc_dist2. Pour des raisons pratiques, son argument est un tuple comme dans calc_dist2(('aa',[0,1,2]))

def calc_dist2(arg):
    grp, lst = arg
    return pd.DataFrame(
               [ [grp,
                  df.loc[c[0]].ser_no,
                  df.loc[c[1]].ser_no,
                  vincenty(df.loc[c[0], ['lat','lon']], 
                           df.loc[c[1], ['lat','lon']])
                 ]
                 for c in combinations(lst, 2)
               ],
               columns=['co_nm','machineA','machineB','distance'])

Et vient maintenant le multitraitement:

pool = mp.Pool(processes = (mp.cpu_count() - 1))
results = pool.map(calc_dist2, grp_lst_args)
pool.close()
pool.join()

results_df = pd.concat(results)

results est une liste des résultats (ici, des trames de données) des appels calc_dist2((grp,lst)) pour (grp,lst) dans grp_lst_args. Les éléments de results sont ensuite concaténés dans un cadre de données.

print(results_df)
  co_nm  machineA  machineB          distance
0    aa         1         2  156.876149391 km
1    aa         1         3  313.705445447 km
2    aa         2         3  156.829329105 km
0    cc         8         9  156.060165391 km
1    cc         8         0  311.910998169 km
2    cc         9         0  155.851498134 km
0    bb         4         5  156.665641837 km
1    bb         4         6  313.214333025 km
2    bb         4         7  469.622535339 km
3    bb         5         6  156.548897414 km
4    bb         5         7  312.957597466 km
5    bb         6         7   156.40899677 km

BTW, En Python 3, nous pourrions utiliser une construction with:

with mp.Pool() as pool:
    results = pool.map(calc_dist2, grp_lst_args)

Mettre à jour

J'ai testé ce code uniquement sur linux. Sous Linux, le cadre de données en lecture seule df est accessible aux processus enfants et n'est pas copié dans leur espace mémoire, mais je ne suis pas sûr de savoir comment il fonctionne exactement sous Windows. Vous pouvez envisager de scinder df en morceaux (regroupés par co_nm) et de les envoyer sous forme d'arguments à une autre version de calc_dist.

19
ptrj

Étrange. Cela semble fonctionner sous python2 mais pas python3.

Ceci est une version minimale modifiée pour imprimer le résultat:

import pandas as pd
from geopy.distance import vincenty
from itertools import combinations
import multiprocessing as mp

df = pd.DataFrame({'ser_no': [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
                'co_nm': ['aa', 'aa', 'aa', 'bb', 'bb', 'bb', 'bb', 'cc', 'cc', 'cc'],
                'lat': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
                'lon': [21, 22, 23, 24, 25, 26, 27, 28, 29, 30]})



def calc_dist(x):
    ret =  pd.DataFrame(
               [ [grp,
                  df.loc[c[0]].ser_no,
                  df.loc[c[1]].ser_no,
                  vincenty(df.loc[c[0], x],
                           df.loc[c[1], x])
                 ]
                 for grp,lst in df.groupby('co_nm').groups.items()
                 for c in combinations(lst, 2)
               ],
               columns=['co_nm','machineA','machineB','distance'])
    print(ret)
    return ret

if __== '__main__':
    pool = mp.Pool(processes = (mp.cpu_count() - 1))
    pool.map(calc_dist, ['lat','lon'])
    pool.close()
    pool.join()

Et ceci est la sortie de python2

0     aa         1         2  110.723608682 km
1     aa         1         3  221.460709525 km
2     aa         2         3  110.737100843 km
3     cc         8         9  110.827576495 km
4     cc         8         0  221.671650552 km
   co_nm  machineA  machineB          distance
5     cc         9         0  110.844074057 km
0     aa         1         2  110.575064814 km
1     aa         1         3  221.151481337 km
6     bb         4         5  110.765515243 km
2     aa         2         3  110.576416524 km
7     bb         4         6    221.5459187 km
3     cc         8         9  110.598565514 km
4     cc         8         0  221.203121352 km
8     bb         4         7  332.341640771 km
5     cc         9         0  110.604555838 km
6     bb         4         5   110.58113908 km
9     bb         5         6  110.780403457 km
7     bb         4         6  221.165643396 km
10    bb         5         7  221.576125528 km
8     bb         4         7  331.754177186 km
9     bb         5         6  110.584504316 km
10    bb         5         7  221.173038106 km
11    bb         6         7  110.795722071 km
11    bb         6         7   110.58853379 km

Et ceci la trace de pile de python3

"""
Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/geopy/point.py", line 123, in __new__
    seq = iter(arg)
TypeError: 'numpy.int64' object is not iterable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.4/multiprocessing/pool.py", line 119, in worker
    result = (True, func(*args, **kwds))
  File "/usr/lib/python3.4/multiprocessing/pool.py", line 44, in mapstar
    return list(map(*args))
  File "gps.py", line 29, in calc_dist
    for grp, lst in df.groupby('co_nm').groups.items()
  File "gps.py", line 30, in <listcomp>
    for c in combinations(lst, 2)
  File "/usr/local/lib/python3.4/dist-packages/geopy/distance.py", line 322, in __init__
    super(vincenty, self).__init__(*args, **kwargs)
  File "/usr/local/lib/python3.4/dist-packages/geopy/distance.py", line 115, in __init__
    kilometers += self.measure(a, b)
  File "/usr/local/lib/python3.4/dist-packages/geopy/distance.py", line 342, in measure
    a, b = Point(a), Point(b)
  File "/usr/local/lib/python3.4/dist-packages/geopy/point.py", line 126, in __new__
    "Failed to create Point instance from %r." % (arg,)
TypeError: Failed to create Point instance from 8.
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "gps.py", line 38, in <module>
    pool.map(calc_dist, ['lat', 'lon'])
  File "/usr/lib/python3.4/multiprocessing/pool.py", line 260, in map
    return self._map_async(func, iterable, mapstar, chunksize).get()
  File "/usr/lib/python3.4/multiprocessing/pool.py", line 599, in get
    raise self._value
TypeError: Failed to create Point instance from 8.

Je sais que ce n'est pas la réponse, mais peut-être que ça aide ...

1
salomonderossi