it-swarm.com.de

Wählen Sie Zeilen in pandas MultiIndex DataFrame

Ziel und Motivation

Die MultiIndex -API hat im Laufe der Jahre an Popularität gewonnen, jedoch ist nicht alles in Bezug auf Struktur, Funktionsweise und zugehörige Operationen vollständig verstanden.

Eine wichtige Operation ist das Filtern . Filterung ist eine häufige Anforderung, aber die Anwendungsfälle sind vielfältig. Dementsprechend sind bestimmte Methoden und Funktionen für einige Anwendungsfälle besser anwendbar als für andere.

Zusammenfassend soll in diesem Beitrag auf einige häufig auftretende Filterprobleme und Anwendungsfälle eingegangen, verschiedene Methoden zur Lösung dieser Probleme vorgestellt und deren Anwendbarkeit erörtert werden. Einige der wichtigsten Fragen, die in diesem Beitrag angesprochen werden sollen, sind:

  • Schneiden basierend auf einem einzelnen Wert/Etikett
  • Schneiden basierend auf mehreren Etiketten aus einer oder mehreren Ebenen
  • Filtern nach booleschen Bedingungen und Ausdrücken
  • Welche Methoden sind unter welchen Umständen anwendbar?

Diese Probleme wurden in sechs konkrete Fragen unterteilt, die im Folgenden aufgeführt sind. Der Einfachheit halber haben die Beispiel-DataFrames im folgenden Setup nur zwei Ebenen und keine doppelten Indexschlüssel. Die meisten Lösungen für die Probleme können auf N-Ebenen verallgemeinert werden.

In diesem Beitrag wird nicht erläutert, wie MultiIndexes erstellt werden, wie Zuweisungsvorgänge für sie ausgeführt werden oder ob es sich um leistungsbezogene Diskussionen handelt (dies sind separate Themen für ein anderes Mal) ).


Fragen

Frage 1-6 wird im Zusammenhang mit dem Setup unten gestellt.

mux = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    list('tuvwtuvwtuvwtuvw')
], names=['one', 'two'])

df = pd.DataFrame({'col': np.arange(len(mux))}, mux)

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    u      5
    v      6
    w      7
    t      8
c   u      9
    v     10
d   w     11
    t     12
    u     13
    v     14
    w     15

Frage 1: Auswählen eines einzelnen Elements
Wie wähle ich Zeilen mit "a" in Ebene "eins" aus?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

Wie kann ich außerdem die Stufe "Eins" in der Ausgabe löschen?

     col
two     
t      0
u      1
v      2
w      3

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

Frage 2: Auswählen mehrerer Werte in einer Ebene
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

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

Frage 3: Schneiden eines einzelnen Querschnitts (x, y)
Wie rufe ich einen Querschnitt ab, d. H. Eine einzelne Zeile mit bestimmten Werten für den Index aus df? Wie kann ich den Querschnitt von ('c', 'u') Abrufen, der durch gegeben ist?

         col
one two     
c   u      9

Frage 4: Schneiden mehrerer Querschnitte [(a, b), (c, d), ...]
Wie wähle ich die beiden Zeilen aus, die ('c', 'u') Und ('a', 'w') Entsprechen?

         col
one two     
c   u      9
a   w      3

Frage 5: Ein Gegenstand pro Stufe
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

Frage 6: Beliebiges Schneiden
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

Frage 7 wird ein einzigartiges Setup verwenden, das aus einer numerischen Ebene besteht:

np.random.seed(0)
mux2 = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    np.random.choice(10, size=16)
], names=['one', 'two'])

df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2)

         col
one two     
a   5      0
    0      1
    3      2
    3      3
b   7      4
    9      5
    3      6
    5      7
    2      8
c   4      9
    7     10
d   6     11
    8     12
    8     13
    1     14
    6     15

Frage 7: Ungleichheitsfilterung auf numerischen Ebenen
Wie erhalte ich alle Zeilen, in denen die 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
61
cs95

MultiIndex/Advanced Indexing

Hinweis
Dieser Beitrag ist wie folgt gegliedert:

  1. Die im OP gestellten Fragen werden nacheinander beantwortet
  2. Für jede Frage werden eine oder mehrere Methoden zur Lösung dieses Problems und zum Erreichen des erwarteten Ergebnisses vorgestellt.

Hinweis s (ähnlich wie dieses) werden für Leser bereitgestellt, die mehr über zusätzliche Funktionen, Implementierungsdetails und andere Informationen zum vorliegenden Thema erfahren möchten . Diese Notizen wurden durch Durchsuchen der Dokumente und Aufdecken verschiedener undurchsichtiger Merkmale und aus meiner eigenen (zugegebenermaßen begrenzten) Erfahrung zusammengestellt.

Alle Codebeispiele wurden auf pandas v0.23.4, python3.7 erstellt und getestet. Wenn etwas nicht klar oder sachlich nicht korrekt ist oder Sie keine Lösung für Ihren Anwendungsfall gefunden haben, können Sie gerne eine Änderung vorschlagen, eine Erläuterung in den Kommentaren anfordern oder eine neue Frage eröffnen, sofern zutreffend .

Hier finden Sie eine Einführung in einige gebräuchliche Redewendungen (im Folgenden als die vier Redewendungen bezeichnet), die wir häufig erneut besuchen werden

  1. DataFrame.loc - Eine allgemeine Lösung zur Auswahl nach Label (+ pd.IndexSlice für komplexere Anwendungen mit Slices)

  2. DataFrame.xs - Einen bestimmten Querschnitt aus einer Serie/einem DataFrame extrahieren.

  3. DataFrame.query - Geben Sie die Slicing- und/oder Filteroperationen dynamisch an (dh als Ausdruck, der dynamisch ausgewertet wird. Gilt eher für einige Szenarien als andere. Siehe auch dieser Abschnitt der Dokumentation zum Abfragen von MultiIndexes.

  4. Boolesche Indizierung mit einer Maske, die mit 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 wird von Vorteil sein, die verschiedenen Probleme beim Schneiden und Filtern in Bezug auf die vier Redewendungen zu betrachten, um ein besseres Verständnis dafür zu erlangen, was auf eine bestimmte Situation angewendet werden kann. Es ist sehr wichtig zu verstehen, dass nicht alle Redewendungen unter allen Umständen gleich gut funktionieren (wenn überhaupt). Wenn eine Redewendung nicht als mögliche Lösung für ein unten stehendes Problem aufgeführt ist, bedeutet dies, dass die Redewendung 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 allgemeine Lösung für die meisten Situationen verwenden:

df.loc[['a']]

An diesem Punkt, wenn Sie bekommen

TypeError: Expected Tuple, got str

Das heißt, Sie verwenden eine ältere Version von Pandas. Erwägen Sie ein Upgrade! Verwenden Sie andernfalls df.loc[('a', slice(None)), :].

Alternativ können Sie hier xs verwenden, da wir einen einzelnen Querschnitt extrahieren. Beachten Sie die Argumente levels und axis (hier können sinnvolle Standardwerte angenommen werden).

df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)

Hier wird das Argument drop_level=False Benötigt, um zu verhindern, dass xs die Ebene "Eins" im Ergebnis (die Ebene, auf der wir uns befinden) fallen lässt.

Eine weitere Option ist die Verwendung von query:

df.query("one == 'a'")

Wenn der Index keinen Namen hätte, müssten Sie die Abfragezeichenfolge auf "ilevel_0 == 'a'" Ändern.

Zum Schluss 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 kann ich außerdem die Stufe "Eins" in der Ausgabe löschen?

     col
two     
t      0
u      1
v      2
w      3

Dies kann mit einfach 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 Argument drop_level Weglassen können (es wird standardmäßig als True angenommen).

Hinweis
Möglicherweise stellen Sie fest, dass ein gefilterter DataFrame möglicherweise immer noch alle Ebenen aufweist, auch wenn diese beim Ausdrucken des DataFrames nicht angezeigt werden. Zum Beispiel,

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 mit MultiIndex.remove_unused_levels loswerden:

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 API pd.IndexSlice 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 wird der abschließende Slice : Über die Spalten benötigt? Dies liegt daran, dass mit loc beide Achsen ausgewählt und geschnitten werden können (axis=0 Oder axis=1). 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, akzeptiert loc einen axis -Parameter:

df.loc(axis=0)[pd.IndexSlice[:, 't']]

Ohne den axis -Parameter (d. H. Nur durch Ausführen von df.loc[pd.IndexSlice[:, 't']]) Wird angenommen, dass das Schneiden in den Spalten erfolgt, und unter diesen Umständen wird ein KeyError ausgelöst.

Dies ist in Slicer dokumentiert. In diesem Beitrag 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 können Sie mit get_level_values Dies 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

Mit loc geschieht dies auf ähnliche Weise, indem eine Liste angegeben wird.

df.loc[['b', 'd']]

Um das obige Problem der Auswahl von "b" und "d" zu lösen, können Sie auch query verwenden:

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 Standard-Parser ist 'pandas', Aber es ist wichtig zu betonen, dass diese Syntax nicht konventionell Python ist. Der Pandas Parser generiert einen etwas anderen Analysebaum als der Ausdruck. Dies dient dazu, einige Operationen intuitiver zu spezifizieren. Weitere Informationen finden Sie in meinem Beitrag zu Dynamic Expression Evaluation in pandas using 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 pd.IndexSlice Möglich.

df.loc[pd.IndexSlice[:, ['t', 'w']], :] 

Der erste Doppelpunkt : In pd.IndexSlice[:, ['t', 'w']] Bedeutet, die erste Ebene zu durchschneiden. Wenn die Tiefe der abgefragten Ebene zunimmt, müssen Sie mehr Segmente angeben, eines pro Ebene, die in mehrere Segmente aufgeteilt werden. Sie müssen jedoch nicht mehr Ebenen als die zu schneidende angeben .

Mit query ist dies

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, d. H. Eine einzelne Zeile mit bestimmten Werten für den Index aus df? Wie kann ich den Querschnitt von ('c', 'u') Abrufen, der durch gegeben ist?

         col
one two     
c   u      9

Verwenden Sie loc, indem Sie ein Tupel von Schlüsseln angeben:

df.loc[('c', 'u'), :]

Oder,

df.loc[pd.IndexSlice[('c', 'u')]]

Hinweis
An dieser Stelle könnten Sie auf ein PerformanceWarning stoßen, das so aussieht:

PerformanceWarning: indexing past lexsort depth may impact performance.

Dies bedeutet nur, dass Ihr Index nicht sortiert ist. pandas hängt vom zu sortierenden Index ab (in diesem Fall lexikografisch, da es sich um Zeichenfolgenwerte handelt), um eine optimale Suche und einen optimalen Abruf zu ermöglichen. Eine schnelle Lösung wäre, Ihren DataFrame im Voraus mit - zu sortieren. DataFrame.sort_index . Dies ist vom Standpunkt der Leistung aus 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() verwenden, um zu überprüfen, ob der Index sortiert ist oder nicht. Diese Funktion gibt entsprechend True oder False zurück. Mit dieser Funktion können Sie feststellen, ob ein zusätzlicher Sortierschritt erforderlich ist oder nicht.

Mit xs wird wieder einfach ein einzelnes Tupel als erstes Argument übergeben, wobei alle anderen Argumente auf die entsprechenden Standardwerte 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 OK.

Bei mehrstufigen Zugriffen kann get_level_values 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, die ('c', 'u') Und ('a', 'w') Entsprechen?

         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 eigentlich sehr schwierig mit loc zu tun, während die Korrektheit und die Code-Klarheit erhalten bleiben. df.loc[pd.IndexSlice['a', 't']] Ist falsch und wird als df.loc[pd.IndexSlice[('a', 't')]] interpretiert (d. H. Auswahl eines Querschnitts). Sie können sich eine Lösung mit pd.concat Vorstellen, um jedes Etikett separat zu behandeln:

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 Schneidebedingungen 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 ganz einfach:

df.query("one == 'a' or two == 't'")

Mit get_level_values Ist dies immer noch einfach, aber nicht so elegant:

m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
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 spezieller Fall, den ich hinzugefügt habe, um die Anwendbarkeit der vier Redewendungen besser zu verstehen. In diesem Fall wird keiner von ihnen effektiv funktionieren, da das Schneiden sehr spezifisch ist , und folgt keinem realen Muster.

Normalerweise erfordert das Schneiden von Problemen wie diesen die explizite Übergabe einer Schlüsselliste an loc. Ein Weg dies zu tun ist mit:

keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]

Wenn Sie eine Eingabe speichern möchten, werden Sie feststellen, dass es ein Muster für das Schneiden von "a", "b" und seinen Unterebenen gibt, sodass wir die Aufteilungsaufgabe in zwei Teile aufteilen und concat das Ergebnis:

pd.concat([
     df.loc[(('a', 'b'), ('u', 'v')), :], 
     df.loc[('d', 'w'), :]
   ], axis=0)

Die Aufteilungsspezifikation für "a" und "b" ist etwas sauberer (('a', 'b'), ('u', 'v')), Da die gleichen indizierten Unterebenen für jede Ebene gleich sind.


Frage 7

Wie erhalte ich alle Zeilen, in denen die Werte in Ebene "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 mit query geschehen,

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 nach beliebigen Bedingungen filtern. Im Allgemeinen ist es hilfreich, sich daran zu erinnern, dass loc und xs speziell für die kennsatzbasierte Indizierung vorgesehen sind, während query und get_level_values Für die Erstellung allgemeiner Bedingungen hilfreich sind Masken zum Filtern.


Bonus-Frage

Was ist, wenn ich eine MultiIndex -Spalte schneiden muss?

Tatsächlich sind die meisten Lösungen hier mit geringfügigen Änderungen auch auf Spalten anwendbar. 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.

  1. Verwenden Sie zum Schneiden mit loc

    df3.loc[:, ....] # Notice how we slice across the index with `:`. 
    

    Oder,

    df3.loc[:, pd.IndexSlice[...]]
    
  2. Um xs entsprechend zu verwenden, übergeben Sie einfach ein Argument axis=1.

  3. Sie können direkt mit df.columns.get_level_values Auf die Werte der Spaltenebene zugreifen. Sie müssen dann so etwas tun

    df.loc[:, {condition}] 
    

    Wobei {condition} Eine Bedingung darstellt, die mit columns.get_level_values Erstellt wurde.

  4. Um query zu verwenden, müssen Sie nur transponieren, den Index abfragen und erneut transponieren:

    df3.T.query(...).T
    

    Nicht empfohlen, verwenden Sie eine der anderen 3 Optionen.

62
cs95