web-dev-qa-db-fra.com

Comment itérer sur des morceaux consécutifs de Pandas dataframe efficacement

J'ai une grande trame de données (plusieurs millions de lignes).

Je veux pouvoir faire une opération groupby dessus, mais simplement grouper par sous-ensembles consécutifs arbitraires (de préférence de taille égale), plutôt que d'utiliser une propriété particulière des lignes individuelles pour décider à quel groupe ils vont.

Le cas d'utilisation: je veux appliquer une fonction à chaque ligne via une carte parallèle dans IPython. Peu importe les lignes qui vont à quel moteur principal, car la fonction calcule un résultat basé sur une ligne à la fois. (Conceptuellement au moins; en réalité, c'est vectorisé.)

J'ai trouvé quelque chose comme ça:

# Generate a number from 0-9 for each row, indicating which tenth of the DF it belongs to
max_idx = dataframe.index.max()
tenths = ((10 * dataframe.index) / (1 + max_idx)).astype(np.uint32)

# Use this value to perform a groupby, yielding 10 consecutive chunks
groups = [g[1] for g in dataframe.groupby(tenths)]

# Process chunks in parallel
results = dview.map_sync(my_function, groups)

Mais cela semble très long et ne garantit pas des morceaux de taille égale. Surtout si l'index est clairsemé ou non entier ou autre.

Des suggestions pour une meilleure façon?

Merci!

37
Andrew Clegg

En pratique, vous ne pouvez pas garantir des morceaux de taille égale: le nombre de lignes peut être premier, après tout, auquel cas vos seules options de segmentation seraient des morceaux de taille 1 ou un gros morceau. J'ai tendance à passer un tableau à groupby. A partir de:

>>> df = pd.DataFrame(np.random.Rand(15, 5), index=[0]*15)
>>> df[0] = range(15)
>>> df
    0         1         2         3         4
0   0  0.746300  0.346277  0.220362  0.172680
0   1  0.657324  0.687169  0.384196  0.214118
0   2  0.016062  0.858784  0.236364  0.963389
[...]
0  13  0.510273  0.051608  0.230402  0.756921
0  14  0.950544  0.576539  0.642602  0.907850

[15 rows x 5 columns]

où j'ai délibérément rendu l'index non informatif en le mettant à 0, nous décidons simplement de notre taille (ici 10) et divisons un tableau par lui:

>>> df.groupby(np.arange(len(df))//10)
<pandas.core.groupby.DataFrameGroupBy object at 0xb208492c>
>>> for k,g in df.groupby(np.arange(len(df))//10):
...     print(k,g)
...     
0    0         1         2         3         4
0  0  0.746300  0.346277  0.220362  0.172680
0  1  0.657324  0.687169  0.384196  0.214118
0  2  0.016062  0.858784  0.236364  0.963389
[...]
0  8  0.241049  0.246149  0.241935  0.563428
0  9  0.493819  0.918858  0.193236  0.266257

[10 rows x 5 columns]
1     0         1         2         3         4
0  10  0.037693  0.370789  0.369117  0.401041
0  11  0.721843  0.862295  0.671733  0.605006
[...]
0  14  0.950544  0.576539  0.642602  0.907850

[5 rows x 5 columns]

Les méthodes basées sur le découpage du DataFrame peuvent échouer lorsque l'index n'est pas compatible avec cela, bien que vous puissiez toujours utiliser .iloc[a:b] pour ignorer les valeurs d'index et accéder aux données par position.

28
DSM

Utiliser numpy a ceci intégré: np.array_split ()

import numpy as np
import pandas as pd

data = pd.DataFrame(np.random.Rand(10, 3))
for chunk in np.array_split(data, 5):
    assert len(chunk) == len(data) / 5
38
Ivelin

Je ne sais pas si c'est exactement ce que vous voulez, mais j'ai trouvé ces fonctions de groupeur sur n autre SO thread assez utile pour faire un pool multiprocesseur.

Voici un court exemple de ce fil, qui pourrait faire quelque chose comme ce que vous voulez:

import numpy as np
import pandas as pds

df = pds.DataFrame(np.random.Rand(14,4), columns=['a', 'b', 'c', 'd'])

def chunker(seq, size):
    return (seq[pos:pos + size] for pos in xrange(0, len(seq), size))

for i in chunker(df,5):
    print i

Ce qui vous donne quelque chose comme ça:

          a         b         c         d
0  0.860574  0.059326  0.339192  0.786399
1  0.029196  0.395613  0.524240  0.380265
2  0.235759  0.164282  0.350042  0.877004
3  0.545394  0.881960  0.994079  0.721279
4  0.584504  0.648308  0.655147  0.511390
          a         b         c         d
5  0.276160  0.982803  0.451825  0.845363
6  0.728453  0.246870  0.515770  0.343479
7  0.971947  0.278430  0.006910  0.888512
8  0.044888  0.875791  0.842361  0.890675
9  0.200563  0.246080  0.333202  0.574488
           a         b         c         d
10  0.971125  0.106790  0.274001  0.960579
11  0.722224  0.575325  0.465267  0.258976
12  0.574039  0.258625  0.469209  0.886768
13  0.915423  0.713076  0.073338  0.622967

J'espère que ça aide.

MODIFIER

Dans ce cas, j'ai utilisé cette fonction avec pool de processeurs de (approximativement) de cette manière:

from multiprocessing import Pool

nprocs = 4

pool = Pool(nprocs)

for chunk in chunker(df, nprocs):
    data = pool.map(myfunction, chunk)
    data.domorestuff()

Je suppose que cela devrait être très similaire à l'utilisation de la machinerie distribuée IPython, mais je ne l'ai pas essayé.

37
Ryan

Un signe d'un bon environnement est de nombreux choix, donc j'ajouterai ceci de Anaconda Blaze , en utilisant vraiment Odo

import blaze as bz
import pandas as pd

df = pd.DataFrame({'col1':[1,2,3,4,5], 'col2':[2,4,6,8,10]})

for chunk in bz.odo(df, target=bz.chunks(pd.DataFrame), chunksize=2):
    # Do stuff with chunked dataframe
11
Miles