Pandas: créer deux nouvelles colonnes dans une dataframe avec des valeurs calculées à partir d'une colonne préexistante

je travaille avec la bibliothèque pandas et je veux ajouter deux nouvelles colonnes à une dataframe df avec n colonnes (n > 0).

Ces nouvelles colonnes résultat de l'application d'une fonction à l'une des colonnes dans le dataframe.

la fonction à appliquer est comme:

def calculate(x):
    ...operate...
    return z, y

Une méthode pour la création d'une nouvelle colonne pour une fonction retournant seulement une valeur est:

df['new_col']) = df['column_A'].map(a_function)

donc, ce que je veux, et essayé sans succès ( * ), est quelque chose comme:

(df['new_col_zetas'], df['new_col_ys']) = df['column_A'].map(calculate)

Quelle est la meilleure façon d'y parvenir ? J'ai scanné la documentation sans aucune idée.

* * df['column_A'].map(calculate) renvoie une série de pandas composée d'un tuple z, Y. Et essayer d'attribuer ceci à deux colonnes de dataframe produit une Valeurerror.*

79
demandé sur Scott Boston 2012-09-10 21:17:38

2 réponses

Je n'utiliserais que zip :

In [1]: from pandas import *

In [2]: def calculate(x):
   ...:     return x*2, x*3
   ...: 

In [3]: df = DataFrame({'a': [1,2,3], 'b': [2,3,4]})

In [4]: df
Out[4]: 
   a  b
0  1  2
1  2  3
2  3  4

In [5]: df["A1"], df["A2"] = zip(*df["a"].map(calculate))

In [6]: df
Out[6]: 
   a  b  A1  A2
0  1  2   2   3
1  2  3   4   6
2  3  4   6   9
100
répondu DSM 2012-09-10 17:20:49

La première réponse est erronée à mon avis. Heureusement, personne n'importe en masse tous les pandas dans leur espace de nom avec from pandas import * . En outre, la méthode map devrait être réservée pour ces moments lors de la transmission d'un dictionnaire ou D'une série. Il peut prendre une fonction, mais c'est ce que apply est utilisé pour.

donc, si vous devez utiliser l'approche ci-dessus, je l'écrirais comme ceci

df["A1"], df["A2"] = zip(*df["a"].apply(calculate))

il n'y a en fait aucune raison d'utiliser zip ici. Vous pouvez simplement faire ceci:

df["A1"], df["A2"] = calculate(df['a'])

cette seconde méthode est également beaucoup plus rapide sur les plus grandes images de données

df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})

DataFrame created with rows 300,000

%timeit df["A1"], df["A2"] = calculate(df['a'])
2.65 ms ± 92.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
159 ms ± 5.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

60x plus rapide que zip


en général, éviter d'utiliser Appliquer

Apply N'est généralement pas beaucoup plus rapide qu'une itération sur une liste Python. Nous allons tester la performance d'un pour la boucle pour faire la même chose que ci-dessus

%%timeit
A1, A2 = [], []
for val in df['a']:
    A1.append(val**2)
    A2.append(val**3)

df['A1'] = A1
df['A2'] = A2

298 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

donc c'est deux fois plus lent ce qui n'est pas une terrible régression de performance, mais si nous cythonisons ce qui précède, nous obtenons une bien meilleure performance. En supposant que vous utilisez ipython:

%load_ext cython

%%cython
cpdef power(vals):
    A1, A2 = [], []
    cdef double val
    for val in vals:
        A1.append(val**2)
        A2.append(val**3)

    return A1, A2

%timeit df['A1'], df['A2'] = power(df['a'])
72.7 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

attribuer directement sans objet

vous pouvez obtenir encore plus d'améliorations de vitesse si vous utilisez les opérations vectorisées directes.

%timeit df['A1'], df['A2'] = df['a'] ** 2, df['a'] ** 3
5.13 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

ceci profite des opérations vectorisées extrêmement rapides de num Py au lieu de nos boucles. Nous avons maintenant une accélération de 30x par rapport à l'original.


"l'essai de vitesse le plus simple avec apply

L'exemple ci-dessus devrait clairement montrer comment ralentir apply peut être, mais juste pour son extra-clair, regardons l'exemple le plus simple. Une série de 10 millions de nombres avec et sans application

s = pd.Series(np.random.rand(10000000))

%timeit s.apply(calc)
3.3 s ± 57.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

sans application est 50x plus rapide

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
22
répondu Ted Petrou 2017-11-03 18:14:33