Wie teile ich eine Tupelspalte im Pandas-Datenrahmen?

88

Ich habe einen Pandas-Datenrahmen (dies ist nur ein kleines Stück)

>>> d1
   y norm test  y norm train  len(y_train)  len(y_test)  \
0    64.904368    116.151232          1645          549   
1    70.852681    112.639876          1645          549   

                                    SVR RBF  \
0   (35.652207342877873, 22.95533537448393)   
1  (39.563683797747622, 27.382483096332511)   

                                        LCV  \
0  (19.365430594452338, 13.880062435173587)   
1  (19.099614489458364, 14.018867136617146)   

                                   RIDGE CV  \
0  (4.2907610988480362, 12.416745648065584)   
1    (4.18864306788194, 12.980833914392477)   

                                         RF  \
0   (9.9484841581029428, 16.46902345373697)   
1  (10.139848213735391, 16.282141345406522)   

                                           GB  \
0  (0.012816232716538605, 15.950164822266007)   
1  (0.012814519804493328, 15.305745202851712)   

                                             ET DATA  
0  (0.00034337162272515505, 16.284800366214057)  j2m  
1  (0.00024811554516431878, 15.556506191784194)  j2m  
>>> 

Ich möchte alle Spalten teilen, die Tupel enthalten. Zum Beispiel möchte ich die Spalte LCVdurch die Spalten LCV-aund ersetzen LCV-b.

Wie kann ich das machen?

Donbeo
quelle

Antworten:

159

Sie können dies tun, indem Sie pd.DataFrame(col.tolist())in dieser Spalte Folgendes tun :

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

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

In [4]: df['b'].tolist()                                                                                                                                                        
Out[4]: [(1, 2), (3, 4)]

In [5]: pd.DataFrame(df['b'].tolist(), index=df.index)                                                                                                                                          
Out[5]: 
   0  1
0  1  2
1  3  4

In [6]: df[['b1', 'b2']] = pd.DataFrame(df['b'].tolist(), index=df.index)                                                                                                                       

In [7]: df                                                                                                                                                                      
Out[7]: 
   a       b  b1  b2
0  1  (1, 2)   1   2
1  2  (3, 4)   3   4

Hinweis: In einer früheren Version wurde empfohlen, diese Antwort df['b'].apply(pd.Series)anstelle von zu verwenden pd.DataFrame(df['b'].tolist(), index=df.index). Das funktioniert auch (weil es aus jedem Tupel eine Serie macht, die dann als Zeile eines Datenrahmens betrachtet wird), ist aber langsamer / verbraucht mehr Speicher als die tolistVersion, wie in den anderen Antworten hier angegeben (danke an @denfromufa). .
Ich habe diese Antwort aktualisiert, um sicherzustellen, dass die sichtbarste Antwort die beste Lösung hat.

Joris
quelle
2
Gibt es eine Möglichkeit, dies aufgrund der großen Anzahl von Spalten zu automatisieren?
Donbeo
Nicht direkt denke ich. Aber Sie können leicht eine Funktion dafür schreiben, indem Sie den obigen Code verwenden (+ den ursprünglichen entfernen)
joris
Wenn Sie eine große Anzahl von Spalten haben, sollten Sie in Betracht ziehen, Ihre Daten aufzuräumen : vita.had.co.nz/papers/tidy-data.html Sie können dies mit der Schmelzfunktion tun.
Axel
.apply (pd.Series) funktioniert einwandfrei, verbraucht jedoch bei großen Datenmengen viel Speicher und kann Speicherfehler verursachen
Yury Wallet
26

Bei viel größeren Datensätzen stellte ich fest, dass .apply()nur wenige Bestellungen langsamer sind alspd.DataFrame(df['b'].values.tolist(), index=df.index)

Dieses Leistungsproblem wurde in GitHub geschlossen, obwohl ich dieser Entscheidung nicht zustimme:

https://github.com/pandas-dev/pandas/issues/11615

BEARBEITEN: basierend auf dieser Antwort: https://stackoverflow.com/a/44196843/2230844

denfromufa
quelle
5
pd.DataFrame(df['b'].tolist())ohne das .valuesscheint auch gut zu funktionieren. (Und danke, Ihre Lösung ist viel schneller als .apply())
Swier
Ich war besorgt über die Erfassung des Index und daher die explizite Verwendung von .values.
Denfromufa
1
Die Lösung von @denfromufa funktioniert superschnell. df [['b1', 'b2']] = pd.DataFrame (df ['b']. values.tolist (), index = df.index) und verursacht keinen Speicherfehler (as im Vergleich zu .apply (pd.Series))
Yury Wallet
17

Der strAccessor, der für pandas.SeriesObjekte von verfügbar ist, dtype == objectist tatsächlich iterierbar.

Angenommen, a pandas.DataFrame df:

df = pd.DataFrame(dict(col=[*zip('abcdefghij', range(10, 101, 10))]))

df

        col
0   (a, 10)
1   (b, 20)
2   (c, 30)
3   (d, 40)
4   (e, 50)
5   (f, 60)
6   (g, 70)
7   (h, 80)
8   (i, 90)
9  (j, 100)

Wir können testen, ob es iterierbar ist

from collections import Iterable

isinstance(df.col.str, Iterable)

True

Wir können es dann wie andere Iterables zuweisen:

var0, var1 = 'xy'
print(var0, var1)

x y

Einfachste Lösung

In einer Zeile können wir also beide Spalten zuweisen

df['a'], df['b'] = df.col.str

df

        col  a    b
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

Schnellere Lösung

Nur etwas komplizierter, können wir verwenden zip, um eine ähnliche iterable zu erstellen

df['c'], df['d'] = zip(*df.col)

df

        col  a    b  c    d
0   (a, 10)  a   10  a   10
1   (b, 20)  b   20  b   20
2   (c, 30)  c   30  c   30
3   (d, 40)  d   40  d   40
4   (e, 50)  e   50  e   50
5   (f, 60)  f   60  f   60
6   (g, 70)  g   70  g   70
7   (h, 80)  h   80  h   80
8   (i, 90)  i   90  i   90
9  (j, 100)  j  100  j  100

In der Reihe

Bedeutung, vorhandene nicht mutieren df
Dies funktioniert, da assignSchlüsselwortargumente verwendet werden, bei denen die Schlüsselwörter die neuen (oder vorhandenen) Spaltennamen sind und die Werte die Werte der neuen Spalte sind. Sie können ein Wörterbuch verwenden und es entpacken **und als Schlüsselwortargumente verwenden. Dies ist also eine clevere Möglichkeit, eine neue Spalte mit dem Namen zuzuweisen , die 'g'das erste Element in der df.col.strIterable und 'h'das zweite Element in der df.col.strIterable ist.

df.assign(**dict(zip('gh', df.col.str)))

        col  g    h
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

Meine Version des listAnsatzes

Mit modernem Listenverständnis und variablem Auspacken.
Hinweis: auch inline mitjoin

df.join(pd.DataFrame([*df.col], df.index, [*'ef']))

        col  g    h
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

Die mutierende Version wäre

df[['e', 'f']] = pd.DataFrame([*df.col], df.index)

Naiver Zeittest

Kurzer DataFrame

Verwenden Sie eine oben definierte

%timeit df.assign(**dict(zip('gh', df.col.str)))
%timeit df.assign(**dict(zip('gh', zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))

1.16 ms ± 21.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
635 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
795 µs ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Langer DataFrame

10 ^ 3 mal größer

df = pd.concat([df] * 1000, ignore_index=True)

%timeit df.assign(**dict(zip('gh', df.col.str)))
%timeit df.assign(**dict(zip('gh', zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))

11.4 ms ± 1.53 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.1 ms ± 41.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.33 ms ± 35.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
piRSquared
quelle
2
df['a'], df['b'] = df.col.str
Erwägen
11

Ich denke, ein einfacher Weg ist:

>>> import pandas as pd
>>> df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]}) 
>>> df
   a       b
0  1  (1, 2)
1  2  (3, 4)
>>> df['b_a']=df['b'].str[0]
>>> df['b_b']=df['b'].str[1]
>>> df
   a       b  b_a  b_b
0  1  (1, 2)    1    2
1  2  (3, 4)    3    4
Jinhua Wang
quelle
1
Diese Lösung ist in der Tat viel einfacher
ApplePie
@ jinhuawang es scheint, dass dies ein Hack über der strDarstellung eines pd.SeriesObjekts ist. Können Sie erklären, wie das überhaupt funktioniert?!
Denfromufa
Ich denke, es ist nur so, wie das str-Objekt funktioniert? Sie können auf das Array-Objekt mit str
Jinhua Wang
Was ist, wenn einige der Zeilen Tupel mit einer anderen Anzahl von Werten haben?
Mammykins
Ich denke, das sollte akzeptiert werden. Es ist mehr "pandas-onic" ... wenn das eine Sache ist.
Natacha
8

Ich weiß, dass dies von vor einiger Zeit ist, aber eine Einschränkung der zweiten Lösung:

pd.DataFrame(df['b'].values.tolist())

ist, dass der Index explizit verworfen und ein sequentieller Standardindex hinzugefügt wird, während die akzeptierte Antwort

apply(pd.Series)

wird nicht, da das Ergebnis von apply den Zeilenindex beibehält. Während die Reihenfolge zunächst vom ursprünglichen Array beibehalten wird, versuchen Pandas, die Angaben aus den beiden Datenrahmen abzugleichen.

Dies kann sehr wichtig sein, wenn Sie versuchen, die Zeilen in ein numerisch indiziertes Array zu setzen, und Pandas automatisch versuchen, den Index des neuen Arrays mit dem alten abzugleichen, und eine gewisse Verzerrung in der Reihenfolge verursachen.

Eine bessere Hybridlösung wäre, den Index des ursprünglichen Datenrahmens auf den neuen zu setzen, d. H.

pd.DataFrame(df['b'].values.tolist(), index=df.index)

Dadurch bleibt die Geschwindigkeit der Verwendung der zweiten Methode erhalten, während sichergestellt wird, dass die Reihenfolge und die Indizierung für das Ergebnis beibehalten werden.

Mike
quelle
Ich habe meine Antwort basierend auf Ihrer Indizierungsbeobachtung bearbeitet, danke!
Denfromufa