Hinweis
Dieser Beitrag ist folgendermaßen aufgebaut:
- Die im OP gestellten Fragen werden nacheinander behandelt
- Für jede Frage werden eine oder mehrere Methoden demonstriert, mit denen dieses Problem gelöst und das erwartete Ergebnis erzielt werden kann.
Hinweise (ähnlich wie diese) sind für Leser enthalten, die sich über zusätzliche Funktionen, Implementierungsdetails und andere Informationen zum jeweiligen Thema informieren möchten. Diese Notizen wurden zusammengestellt, indem die Dokumente durchsucht und verschiedene dunkle Merkmale aufgedeckt wurden, und zwar aus meiner eigenen (zugegebenermaßen begrenzten) Erfahrung.
Alle Codebeispiele wurden unter pandas v0.23.4, python3.7 erstellt und getestet . Wenn etwas nicht klar oder sachlich falsch ist oder wenn Sie keine für Ihren Anwendungsfall geeignete Lösung gefunden haben, können Sie eine Änderung vorschlagen, in den Kommentaren eine Klarstellung anfordern oder eine neue Frage öffnen .
Hier ist eine Einführung in einige gebräuchliche Redewendungen (im Folgenden als die vier Redewendungen bezeichnet), die wir häufig wieder besuchen werden
DataFrame.loc
- Eine allgemeine Lösung für die Auswahl nach Etikett (+ pd.IndexSlice
für komplexere Anwendungen mit Scheiben)
DataFrame.xs
- Extrahieren Sie einen bestimmten Querschnitt aus einer Serie / einem DataFrame.
DataFrame.query
- Geben Sie Schnitt- und / oder Filtervorgänge dynamisch an (dh als Ausdruck, der dynamisch ausgewertet wird. Dies gilt für einige Szenarien besser als für andere. Informationen zum Abfragen von MultiIndexes finden Sie auch in diesem Abschnitt der Dokumente .
Boolesche Indizierung mit einer Maske, die mit generiert wurde MultiIndex.get_level_values
(häufig in Verbindung mit Index.isin
, insbesondere beim Filtern mit mehreren Werten). Dies ist unter bestimmten Umständen auch sehr nützlich.
Es ist von Vorteil, die verschiedenen Probleme beim Schneiden und Filtern im Hinblick auf die vier Redewendungen zu betrachten, um ein besseres Verständnis dafür zu erhalten, was auf eine bestimmte Situation angewendet werden kann. Es ist sehr wichtig zu verstehen, dass nicht alle Redewendungen unter allen Umständen gleich gut (wenn überhaupt) funktionieren. Wenn ein Idiom nicht als mögliche Lösung für ein Problem aufgeführt ist, bedeutet dies, dass das Idiom nicht effektiv auf dieses Problem angewendet werden kann.
Frage 1
Wie wähle ich Zeilen mit "a" in Ebene "eins" aus?
col
one two
a t 0
u 1
v 2
w 3
Sie können loc
als Allzwecklösung für die meisten Situationen Folgendes verwenden:
df.loc[['a']]
An diesem Punkt, wenn Sie bekommen
TypeError: Expected tuple, got str
Das bedeutet, dass Sie eine ältere Version von Pandas verwenden. Erwägen Sie ein Upgrade! Andernfalls verwenden Sie df.loc[('a', slice(None)), :]
.
Alternativ können Sie xs
hier verwenden, da wir einen einzelnen Querschnitt extrahieren. Beachten Sie die Argumente levels
und axis
(hier können vernünftige Standardeinstellungen angenommen werden).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Hier wird das drop_level=False
Argument benötigt, um zu verhindern, xs
dass Level "Eins" im Ergebnis (das Level, auf das wir geschnitten haben) abfällt.
Eine weitere Option ist die Verwendung von query
:
df.query("one == 'a'")
Wenn der Index keinen Namen hätte, müssten Sie Ihre Abfragezeichenfolge ändern "ilevel_0 == 'a'"
.
Schließlich mit get_level_values
:
df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']
Wie könnte ich außerdem die Stufe "Eins" in der Ausgabe löschen?
col
two
t 0
u 1
v 2
w 3
Dies kann einfach mit beiden durchgeführt werden
df.loc['a'] # Notice the single string argument instead the list.
Oder,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
Beachten Sie, dass wir das drop_level
Argument weglassen können (es wird True
standardmäßig angenommen).
Hinweis
Möglicherweise stellen Sie fest, dass ein gefilterter DataFrame möglicherweise noch alle Ebenen aufweist, auch wenn diese beim Ausdrucken des DataFrame nicht angezeigt werden. Beispielsweise,
v = df.loc[['a']]
print(v)
col
one two
a t 0
u 1
v 2
w 3
print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
Sie können diese Ebenen entfernen, indem Sie MultiIndex.remove_unused_levels
:
v.index = v.index.remove_unused_levels()
print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
Frage 1b
Wie schneide ich alle Zeilen mit dem Wert "t" auf Ebene "zwei"?
col
one two
a t 0
b t 4
t 8
d t 12
Intuitiv möchten Sie etwas mit slice()
:
df.loc[(slice(None), 't'), :]
Es funktioniert einfach! ™ Aber es ist klobig. Mit der pd.IndexSlice
API können wir hier eine natürlichere Slicing-Syntax ermöglichen .
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
Das ist viel, viel sauberer.
Hinweis
Warum ist das nachfolgende Slice :
über die Spalten erforderlich? Dies liegt daran, loc
dass Sie beide Achsen ( axis=0
oder
axis=1
) auswählen und entlang schneiden können . Ohne explizit klar zu machen, auf welcher Achse das Schneiden durchgeführt werden soll, wird die Operation mehrdeutig. Siehe das große rote Kästchen in der Dokumentation zum Schneiden .
Wenn Sie Mehrdeutigkeiten entfernen möchten, loc
akzeptieren Sie einen axis
Parameter:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Ohne den axis
Parameter (dh nur durch Ausführen df.loc[pd.IndexSlice[:, 't']]
) wird angenommen, dass sich das Schneiden in den Spalten befindet, und unter KeyError
diesen Umständen wird a ausgelöst.
Dies ist in Slicern dokumentiert . Für den Zweck dieses Beitrags werden jedoch alle Achsen explizit angegeben.
Mit xs
ist es
df.xs('t', axis=0, level=1, drop_level=False)
Mit query
ist es
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
Und schließlich, mit get_level_values
, können Sie tun
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
Alles zum gleichen Effekt.
Frage 2
Wie kann ich Zeilen auswählen, die den Elementen "b" und "d" in Ebene "eins" entsprechen?
col
one two
b t 4
u 5
v 6
w 7
t 8
d w 11
t 12
u 13
v 14
w 15
Bei Verwendung von loc erfolgt dies auf ähnliche Weise durch Angabe einer Liste.
df.loc[['b', 'd']]
Um das obige Problem der Auswahl von "b" und "d" zu lösen, können Sie auch Folgendes verwenden query
:
items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')
Hinweis
Ja, der Standardparser ist 'pandas'
, aber es ist wichtig hervorzuheben, dass diese Syntax nicht herkömmlich Python ist. Der Pandas-Parser generiert einen etwas anderen Analysebaum als der Ausdruck. Dies geschieht, um die Angabe einiger Vorgänge intuitiver zu gestalten. Weitere Informationen finden Sie in meinem Beitrag zur
Auswertung dynamischer Ausdrücke in Pandas mit pd.eval () .
Und mit get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
Frage 2b
Wie würde ich alle Werte erhalten, die "t" und "w" in Stufe "zwei" entsprechen?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
Mit loc
ist dies nur in Verbindung mit möglich pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
Der erste Doppelpunkt :
in pd.IndexSlice[:, ['t', 'w']]
bedeutet, über die erste Ebene zu schneiden. Wenn die Tiefe des abgefragten Levels zunimmt, müssen Sie mehr Slices angeben, eines pro Level, über das geschnitten wird. Sie werden nicht mehr Ebenen angeben müssen über die von denen eine in Scheiben geschnitten, jedoch.
Mit query
, das ist
items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas')
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')
Mit get_level_values
und Index.isin
(ähnlich wie oben):
df[df.index.get_level_values('two').isin(['t', 'w'])]
Frage 3
Wie rufe ich einen Querschnitt ab, dh eine einzelne Zeile mit bestimmten Werten für den Index aus df
? Wie rufe ich den Querschnitt von ('c', 'u')
, gegeben durch
col
one two
c u 9
Verwenden Sie diese loc
Option, indem Sie ein Tupel von Schlüsseln angeben:
df.loc[('c', 'u'), :]
Oder,
df.loc[pd.IndexSlice[('c', 'u')]]
Hinweis
An dieser Stelle kann es vorkommen, PerformanceWarning
dass Sie auf Folgendes stoßen:
PerformanceWarning: indexing past lexsort depth may impact performance.
Dies bedeutet nur, dass Ihr Index nicht sortiert ist. Pandas hängen vom zu sortierenden Index ab (in diesem Fall lexikografisch, da es sich um Zeichenfolgenwerte handelt), um eine optimale Suche und einen optimalen Abruf zu gewährleisten. Eine schnelle Lösung wäre, Ihren DataFrame im Voraus mit zu sortieren DataFrame.sort_index
. Dies ist unter Leistungsgesichtspunkten besonders wünschenswert, wenn Sie mehrere solcher Abfragen gleichzeitig ausführen möchten:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
Sie können auch MultiIndex.is_lexsorted()
überprüfen, ob der Index sortiert ist oder nicht. Diese Funktion kehrt zurück True
oder False
entsprechend. Sie können diese Funktion aufrufen, um festzustellen, ob ein zusätzlicher Sortierschritt erforderlich ist oder nicht.
Mit xs
wird hiermit einfach ein einzelnes Tupel als erstes Argument übergeben, wobei alle anderen Argumente auf die entsprechenden Standardeinstellungen gesetzt werden:
df.xs(('c', 'u'))
Mit query
werden die Dinge etwas klobig:
df.query("one == 'c' and two == 'u'")
Sie können jetzt sehen, dass dies relativ schwer zu verallgemeinern sein wird. Ist aber für dieses spezielle Problem noch in Ordnung.
Mit Zugriffen auf mehreren Ebenen get_level_values
kann weiterhin verwendet werden, wird jedoch nicht empfohlen:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
Frage 4
Wie wähle ich die beiden Zeilen aus ('c', 'u')
, die und entsprechen ('a', 'w')
?
col
one two
c u 9
a w 3
Mit loc
ist dies immer noch so einfach wie:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
Mit query
müssen Sie dynamisch eine Abfragezeichenfolge generieren, indem Sie Ihre Querschnitte und Ebenen durchlaufen:
cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses)
query = '(' + ') or ('.join([
' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)])
for cs in cses
]) + ')'
print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))
df.query(query)
100% NICHT EMPFEHLEN! Aber es ist möglich.
Frage 5
Wie kann ich alle Zeilen abrufen, die "a" in Ebene "eins" oder "t" in Ebene "zwei" entsprechen?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
Dies ist tatsächlich sehr schwierig zu tun, loc
während die Korrektheit sichergestellt wird und die Klarheit des Codes erhalten bleibt. df.loc[pd.IndexSlice['a', 't']]
falsch ist, wird es interpretiert als df.loc[pd.IndexSlice[('a', 't')]]
(dh Auswahl eines Querschnitts). Sie können sich eine Lösung vorstellen pd.concat
, mit der Sie jedes Etikett separat behandeln können:
pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
col
one two
a t 0
u 1
v 2
w 3
t 0 # Does this look right to you? No, it isn't!
b t 4
t 8
d t 12
Sie werden jedoch feststellen, dass eine der Zeilen dupliziert ist. Dies liegt daran, dass diese Reihe beide Schnittbedingungen erfüllte und daher zweimal auftrat. Sie müssen stattdessen tun
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
Wenn Ihr DataFrame jedoch von Natur aus doppelte Indizes enthält (die Sie möchten), werden diese nicht beibehalten. Mit äußerster Vorsicht verwenden .
Mit query
ist das blöd einfach:
df.query("one == 'a' or two == 't'")
Mit get_level_values
ist das noch einfach, aber nicht so elegant:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
Frage 6
Wie kann ich bestimmte Querschnitte schneiden? Für "a" und "b" möchte ich alle Zeilen mit den Unterebenen "u" und "v" auswählen, und für "d" möchte ich Zeilen mit der Unterebene "w" auswählen.
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
Dies ist ein Sonderfall, den ich hinzugefügt habe, um die Anwendbarkeit der vier Redewendungen zu verstehen. Dies ist ein Fall, in dem keine von ihnen effektiv funktioniert, da das Schneiden sehr spezifisch ist und keinem wirklichen Muster folgt.
Normalerweise erfordert das Schneiden solcher Probleme das explizite Übergeben einer Liste von Schlüsseln an loc
. Eine Möglichkeit, dies zu tun, ist:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
Wenn Sie einige Eingaben speichern möchten, werden Sie erkennen, dass das Schneiden von "a", "b" und seinen Unterebenen ein Muster hat, sodass wir die Schneideaufgabe in zwei Teile und concat
das Ergebnis aufteilen können :
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
Die Schnittspezifikation für "a" und "b" ist etwas sauberer, (('a', 'b'), ('u', 'v'))
da die gleichen zu indizierenden Unterebenen für jede Ebene gleich sind.
Frage 7
Wie erhalte ich alle Zeilen, in denen Werte in Stufe "zwei" größer als 5 sind?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
Dies kann erfolgen mit query
:
df2.query("two > 5")
Und get_level_values
.
df2[df2.index.get_level_values('two') > 5]
Hinweis
Ähnlich wie in diesem Beispiel können wir mit diesen Konstrukten basierend auf einer beliebigen Bedingung filtern. In der Regel ist es sinnvoll , dass sich daran zu erinnern loc
und xs
sind speziell für den Etikettenbasierte Indizierung, während query
und
get_level_values
nützlich sind für den Aufbau von allgemeinen bedingten Masken zum Filtern.
Bonus-Frage
Was ist, wenn ich eine MultiIndex
Spalte in Scheiben schneiden muss ?
Tatsächlich gelten die meisten Lösungen mit geringfügigen Änderungen auch für Spalten. Erwägen:
np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
list('ABCD'), list('efgh')
], names=['one','two'])
df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)
one A B C D
two e f g h e f g h e f g h e f g h
0 5 0 3 3 7 9 3 5 2 4 7 6 8 8 1 6
1 7 7 8 1 5 9 8 9 4 3 0 3 5 0 2 3
2 8 1 3 3 3 7 0 1 9 9 0 4 7 3 2 7
Dies sind die folgenden Änderungen, die Sie an den vier Redewendungen vornehmen müssen, damit sie mit Spalten arbeiten.
Zum Schneiden loc
verwenden
df3.loc[:, ....] # Notice how we slice across the index with `:`.
oder,
df3.loc[:, pd.IndexSlice[...]]
Übergeben Sie zur Verwendung xs
einfach ein Argument axis=1
.
Sie können direkt mit auf die Werte auf Spaltenebene zugreifen df.columns.get_level_values
. Sie müssen dann so etwas tun
df.loc[:, {condition}]
Wobei {condition}
eine Bedingung dargestellt wird, die mit erstellt wurde columns.get_level_values
.
Zur Verwendung query
besteht Ihre einzige Option darin, zu transponieren, den Index abzufragen und erneut zu transponieren:
df3.T.query(...).T
Nicht empfohlen, verwenden Sie eine der anderen 3 Optionen.
level
Argument zuIndex.isin
!