Je travaille avec la bibliothèque pandas et je veux ajouter deux nouvelles colonnes à une structure de données df
à n colonnes (n> 0).
Ces nouvelles colonnes résultent de l'application d'une fonction à l'une des colonnes de la trame de données.
La fonction à appliquer est comme:
def calculate(x):
...operate...
return z, y
Une méthode pour créer une nouvelle colonne pour une fonction ne renvoyant qu'une valeur est la suivante:
df['new_col']) = df['column_A'].map(a_function)
Donc, ce que je veux, et essayé sans succès (*), c'est quelque chose comme:
(df['new_col_zetas'], df['new_col_ys']) = df['column_A'].map(calculate)
Quel est le meilleur moyen d'accomplir cela? J'ai scanné le documentation sans la moindre idée.
** df['column_A'].map(calculate)
retourne un pandas Séries chaque élément consistant en un Tuple z, y. Et essayant de l'attribuer à deux colonnes de trame de données produit une ValueError. *
Je viens d'utiliser 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
La première réponse est erronée à mon avis. Espérons que personne n’importera en masse tous les pandas dans leur espace de noms avec from pandas import *
. De plus, la méthode map
devrait être réservée aux moments où elle passe un dictionnaire ou une série. Cela peut prendre une fonction, mais c’est pour cela que apply
est utilisé.
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 aucune raison d'utiliser Zip ici. Vous pouvez simplement faire ceci:
df["A1"], df["A2"] = calculate(df['a'])
Cette deuxième méthode est également beaucoup plus rapide sur les grands DataFrames
df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})
DataFrame créé avec 300 000 lignes
%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
Appliquer n’est généralement pas beaucoup plus rapide que de parcourir une liste de Python. Testons les performances d’une boucle for 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)
C'est donc deux fois plus lent, ce qui n'est pas une régression de performances terrible, mais si nous rendons les choses précédentes, nous obtenons de bien meilleures performances. En supposant que vous utilisiez 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)
Vous pouvez encore améliorer la vitesse si vous utilisez les opérations vectorielles 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 tire parti des opérations vectorisées extrêmement rapides de NumPy au lieu de nos boucles. Nous avons maintenant une accélération de 30x par rapport à l'original.
apply
L'exemple ci-dessus devrait clairement montrer à quel point apply
peut être lent, mais pour que ce soit plus clair, examinons l'exemple le plus élémentaire. Posons une série de 10 millions de chiffres 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 appliquer, c'est 50 fois plus rapide
%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)