Frage Pandas: Erstellen Sie zwei neue Spalten in einem Datenrahmen mit Werten, die aus einer bereits vorhandenen Spalte berechnet werden


Ich arbeite mit der Pandas Bibliothek und ich möchte zwei neue Spalten zu einem Datenrahmen hinzufügen df mit n Spalten (n> 0).
Diese neuen Spalten ergeben sich aus der Anwendung einer Funktion auf eine der Spalten im Datenrahmen.

Die anzuwendende Funktion ist wie folgt:

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

Eine Methode zum Erstellen einer neuen Spalte für eine Funktion, die nur einen Wert zurückgibt, ist:

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

Also, was ich will, und versucht, ohne Erfolg (*), ist etwas wie:

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

Was könnte der beste Weg sein, dies zu erreichen? Ich habe das gescannt Dokumentation ohne Ahnung.

*df['column_A'].map(calculate) gibt eine Panda-Reihe zurück, wobei jeder Gegenstand aus einem Tupel z, y besteht. Und wenn Sie versuchen, dies zwei Datenframe-Spalten zuzuordnen, entsteht ein ValueError. 


76
2017-09-10 17:17


Ursprung


Antworten:


Ich würde es einfach benutzen 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
2017-09-10 17:20



Die beste Antwort ist meiner Meinung nach fehlerhaft. Hoffentlich importiert niemand alle Pandas in ihren Namensraum mit from pandas import *. Auch der map Diese Methode sollte für die Zeiten reserviert werden, in denen ein Wörterbuch oder eine Serie übergeben wird. Es kann eine Funktion übernehmen, aber das ist was apply wird für verwendet.

Also, wenn Sie den obigen Ansatz verwenden müssen, würde ich es so schreiben

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

Es gibt eigentlich keinen Grund, zip hier zu verwenden. Sie können dies einfach tun:

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

Diese zweite Methode ist auch bei größeren DataFrames viel schneller

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

DataFrame erstellt mit 300.000 Zeilen

%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 schneller als zip


Im Allgemeinen vermeiden Sie die Anwendung

Anwenden ist im Allgemeinen nicht viel schneller als das Iterieren über eine Python-Liste. Lassen Sie uns die Leistung einer for-Schleife testen, um dasselbe wie oben zu tun

%%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)

Das ist also doppelt so langsam, was keine schreckliche Leistungsregression ist, aber wenn wir das oben genannte verwenden, erhalten wir eine viel bessere Leistung. Angenommen, Sie verwenden 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)

Direkt zuweisen ohne zu übernehmen

Sie können noch größere Geschwindigkeitsverbesserungen erzielen, wenn Sie die direkten vektorisierten Operationen verwenden.

%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)

Dies nutzt die extrem schnellen vektorisierten Operationen von NumPy anstelle unserer Schleifen. Wir haben jetzt eine 30-fache Beschleunigung gegenüber dem Original.


Der einfachste Geschwindigkeitstest mit apply

Das obige Beispiel sollte deutlich zeigen, wie langsam apply kann sein, aber nur so ist es extra klar, schauen wir uns das einfachste Beispiel an. Lassen Sie uns eine Serie von 10 Millionen Zahlen mit und ohne anwenden

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)

Ohne zu bewerben ist 50x schneller

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

19
2017-11-03 18:08