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

100

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

Die anzuwendende Funktion lautet 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 erfolglos versucht habe (*), ist so 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 die Dokumentation ohne Ahnung gescannt .

** df['column_A'].map(calculate)gibt eine Pandas-Serie zurück, wobei jedes Element aus einem Tupel z, y besteht. Wenn Sie versuchen, dies zwei Datenrahmenspalten zuzuweisen, wird ein ValueError erzeugt. *

Joaquin
quelle

Antworten:

119

Ich würde nur verwenden 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
DSM
quelle
Danke, toll, es funktioniert. Ich habe nichts dergleichen in den Dokumenten für 0.8.1 gefunden ... Ich denke, ich sollte immer an Serien als Listen von Tupeln denken ...
Joaquin
Gibt es einen Unterschied in Bezug auf die Leistung, wenn Sie dies stattdessen tun? zip (* map (berechne, df ["a"])) anstelle von zip (* df ["a"]. map (berechne)), was auch (wie oben) [(2, 4, 6), ( 3, 6, 9)]?
Ekta
1
Beim Erstellen einer neuen Spalte wird folgende Warnung angezeigt: "SettingWithCopyWarning: Ein Wert versucht, auf einer Kopie eines Slice aus einem DataFrame festgelegt zu werden. Verwenden Sie stattdessen .loc [row_indexer, col_indexer] = value." Sollte ich mir darüber Sorgen machen? pandas v.0.15
taras
46

Die Top-Antwort ist meiner Meinung nach fehlerhaft. Hoffentlich importiert niemand alle Pandas in ihren Namespace mit from pandas import *. Außerdem sollte die mapMethode für diese Zeiten reserviert werden, wenn ein Wörterbuch oder eine Reihe übergeben wird. Es kann eine Funktion annehmen, aber dafür wird applyes verwendet.

Wenn Sie also den obigen Ansatz verwenden müssen, würde ich ihn so schreiben

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

Es gibt hier eigentlich keinen Grund, zip 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 mit 300.000 Zeilen erstellt

%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 Reißverschluss


Vermeiden Sie im Allgemeinen die Verwendung von apply

Das Anwenden ist im Allgemeinen nicht viel schneller als das Durchlaufen einer 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 Gesagte zythonisieren, 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)

Direktes Zuweisen ohne zutreffen

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 es sein applykann, aber nur damit es besonders klar ist, schauen wir uns das grundlegendste Beispiel an. Lassen Sie uns eine Reihe von 10 Millionen Zahlen mit und ohne Anwendung quadrieren

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 Anwendung ist 50x schneller

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Ted Petrou
quelle
1
Dies ist eine wirklich gute Antwort. Ich wollte fragen: Was halten Sie applymapfür den Fall, wenn Sie für jedes Element des Datenrahmens eine bestimmte Funktion implementieren müssen?
David
3
Obwohl diese Antwort einige gute Ratschläge enthält, glaube ich, dass der wichtigste Ratschlag, der func(series)anstelle von zu verwenden series.apply(func)ist, nur dann anwendbar ist, wenn die Funktion vollständig mit Operationen definiert ist, die sich sowohl für einen einzelnen Wert als auch für eine Serie ähnlich verhalten. Dies ist im Beispiel in der ersten Antwort der Fall, aber nicht in der Frage des OP, in der allgemeiner nach der Anwendung von Funktionen auf Spalten gefragt wird. 1/2
Graham Lea
1
Wenn df beispielsweise: DataFrame({'a': ['Aaron', 'Bert', 'Christopher'], 'b': ['Bold', 'Courageous', 'Distrusted']})und calcist: def calc(x): return x[0], len(x)dann tdf.a.apply(calc))und calc(tdf.a)sehr unterschiedliche Dinge zurückgeben.
Graham Lea