Uwaga
Ten post będzie miał następującą strukturę:
- Pytania postawione w PO zostaną omówione po kolei
- Dla każdego pytania zostanie zademonstrowana jedna lub więcej metod mających zastosowanie do rozwiązania tego problemu i uzyskania oczekiwanego wyniku.
Uwaga (podobnie jak ta) zostaną dołączone dla czytelników zainteresowanych dodatkowymi funkcjami, szczegółami implementacji i innymi pobieżnymi informacjami na dany temat. Te notatki zostały zebrane podczas przeszukiwania dokumentów i odkrywania różnych niejasnych cech oraz na podstawie mojego własnego (wprawdzie ograniczonego) doświadczenia.
Wszystkie próbki kodu zostały utworzone i przetestowane na pandas v0.23.4, python3.7 . Jeśli coś jest niejasne lub niezgodne ze stanem faktycznym, lub jeśli nie znalazłeś rozwiązania pasującego do twojego przypadku użycia, możesz zasugerować zmianę, poprosić o wyjaśnienie w komentarzach lub otworzyć nowe pytanie ... .
Oto wprowadzenie do niektórych popularnych idiomów (odtąd nazywanych czterema idiomami), które będziemy często ponownie odwiedzać
DataFrame.loc- Ogólne rozwiązanie do wyboru według etykiety (+ pd.IndexSlicedla bardziej złożonych zastosowań obejmujących plastry)
DataFrame.xs - Wyodrębnij określony przekrój z Series / DataFrame.
DataFrame.query- Dynamicznie określaj krojenie i / lub filtrowanie operacji (tj. Jako wyrażenie, które jest oceniane dynamicznie. Ma większe zastosowanie w niektórych scenariuszach niż w innych. Zobacz również tę sekcję dokumentacji dotyczącą zapytań dotyczących MultiIndexes.
Indeksowanie logiczne z maską wygenerowaną przy użyciu MultiIndex.get_level_values(często w połączeniu z Index.isin, szczególnie podczas filtrowania z wieloma wartościami). Jest to również przydatne w niektórych okolicznościach.
Korzystne będzie przyjrzenie się różnym problemom z wycinaniem i filtrowaniem pod kątem czterech idiomów, aby lepiej zrozumieć, co można zastosować w danej sytuacji. Bardzo ważne jest, aby zrozumieć, że nie wszystkie idiomy będą działać równie dobrze (jeśli w ogóle) w każdych okolicznościach. Jeśli idiom nie został wymieniony jako potencjalne rozwiązanie problemu poniżej, oznacza to, że nie można go skutecznie zastosować do tego problemu.
Pytanie 1
Jak wybrać wiersze mające „a” na poziomie „jeden”?
col
one two
a t 0
u 1
v 2
w 3
Możesz użyć loc, jako rozwiązania ogólnego przeznaczenia, mającego zastosowanie w większości sytuacji:
df.loc[['a']]
W tym momencie, jeśli dostaniesz
TypeError: Expected tuple, got str
Oznacza to, że używasz starszej wersji pand. Rozważ aktualizację! W przeciwnym razie użyjdf.loc[('a', slice(None)), :] .
Alternatywnie możesz użyć xstutaj, ponieważ pobieramy pojedynczy przekrój. Zwróć uwagę na argumenty levelsi axis(można tutaj założyć rozsądne wartości domyślne).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Tutaj drop_level=Falseargument jest potrzebny, aby zapobiec spadkowi xspoziomu „jeden” w wyniku (poziom, na który się przecięliśmy).
Jeszcze inną opcją jest tutaj użycie query:
df.query("one == 'a'")
Jeśli indeks nie miałby nazwy, należałoby zmienić ciąg zapytania na "ilevel_0 == 'a'" .
Wreszcie, używając 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']
Ponadto, jak mogę obniżyć poziom „jeden” na wyjściu?
col
two
t 0
u 1
v 2
w 3
Można to łatwo zrobić za pomocą obu
df.loc['a'] # Notice the single string argument instead the list.
Lub,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
Zauważ, że możemy pominąć drop_levelargument (zakłada się, że jest to Truedomyślny).
Uwaga
Można zauważyć, że filtrowana ramka danych może nadal mieć wszystkie poziomy, nawet jeśli nie są one wyświetlane podczas drukowania ramki danych. Na przykład,
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'])
Możesz pozbyć się tych poziomów za pomocą 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'])
Pytanie 1b
Jak pokroić wszystkie wiersze z wartością „t” na poziomie „dwa”?
col
one two
a t 0
b t 4
t 8
d t 12
Intuicyjnie chciałbyś czegoś obejmującego slice():
df.loc[(slice(None), 't'), :]
To po prostu działa! ™ Ale jest niezgrabny. Możemy ułatwić bardziej naturalną składnię wycinania, używając pd.IndexSlicetutaj API.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
To jest dużo, dużo czystsze.
Uwaga
Dlaczego końcowy wycinek :w kolumnach jest wymagany? Dzieje się tak, ponieważ locmożna użyć do zaznaczania i cięcia wzdłuż obu osi ( axis=0lub
axis=1). Bez wyraźnego wyjaśnienia, na której osi ma zostać wykonane cięcie, operacja staje się niejednoznaczna. Zobacz duże czerwone pole w dokumentacji dotyczącej krojenia .
Jeśli chcesz usunąć jakikolwiek odcień niejasności, locakceptuje axis
parametr:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Bez axisparametru (tj. Po prostu robiąc df.loc[pd.IndexSlice[:, 't']]) zakłada się, że cięcie odbywa się na kolumnach i KeyErrorw takiej sytuacji zostanie podniesione a.
Jest to udokumentowane w fragmentatorach . Jednak na potrzeby tego postu wyraźnie określimy wszystkie osie.
Tak xsjest
df.xs('t', axis=0, level=1, drop_level=False)
Tak queryjest
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
I wreszcie get_level_values, możesz to zrobić
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
Wszystko w tym samym efekcie.
pytanie 2
Jak mogę wybrać wiersze odpowiadające elementom „b” i „d” na poziomie „jeden”?
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
Używając loc, odbywa się to w podobny sposób, określając listę.
df.loc[['b', 'd']]
Aby rozwiązać powyższy problem wyboru „b” i „d”, możesz również użyć 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')
Uwaga
Tak, domyślny parser to 'pandas', ale ważne jest, aby podkreślić, że ta składnia nie jest tradycyjnie Pythonem. Parser Pandas generuje drzewo analizy nieco inne niż wyrażenie. Ma to na celu uczynienie niektórych operacji bardziej intuicyjnymi. Aby uzyskać więcej informacji, przeczytaj mój post dotyczący
oceny wyrażeń dynamicznych w pandach przy użyciu pd.eval () .
A z get_level_values+ Index.isin:
df[df.index.get_level_values("one").isin(['b', 'd'])]
Pytanie 2b
Jak uzyskać wszystkie wartości odpowiadające „t” i „w” na poziomie „dwa”?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
Dzięki loctemu jest to możliwe tylko w połączeniu z pd.IndexSlice.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
Pierwszy okrężnicy :w pd.IndexSlice[:, ['t', 'w']]środek do krojenia w poprzek pierwszego poziomu. Wraz ze wzrostem głębokości poziomu, którego dotyczy zapytanie, należy określić więcej plasterków, po jednym na poziom. Nie będziesz jednak musiał określać więcej poziomów poza tym, który jest cięty.
Dzięki query, to jest
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')
Z get_level_valuesi Index.isin(podobnie jak powyżej):
df[df.index.get_level_values('two').isin(['t', 'w'])]
pytanie 3
Jak uzyskać przekrój poprzeczny, tj. Pojedynczy wiersz zawierający określone wartości indeksu z df? W szczególności, jak uzyskać przekrój poprzeczny ('c', 'u'), podany przez
col
one two
c u 9
Użyj loc, określając krotkę kluczy:
df.loc[('c', 'u'), :]
Lub,
df.loc[pd.IndexSlice[('c', 'u')]]
Uwaga
W tym momencie możesz natknąć się na następujący PerformanceWarningwygląd:
PerformanceWarning: indexing past lexsort depth may impact performance.
Oznacza to po prostu, że Twój indeks nie jest posortowany. pandy zależy od sortowanego indeksu (w tym przypadku leksykograficznie, ponieważ mamy do czynienia z wartościami ciągów) dla optymalnego wyszukiwania i pobierania. Szybkim rozwiązaniem byłoby wcześniejsze posortowanie ramki DataFrame za pomocą DataFrame.sort_index. Jest to szczególnie pożądane z punktu widzenia wydajności, jeśli planujesz wykonywać wiele takich zapytań jednocześnie:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
Możesz również użyć, MultiIndex.is_lexsorted()aby sprawdzić, czy indeks jest posortowany, czy nie. Ta funkcja zwraca Truelub Falseodpowiednio. Możesz wywołać tę funkcję, aby określić, czy dodatkowy krok sortowania jest wymagany, czy nie.
W przypadku xsjest to ponownie po prostu przekazanie pojedynczej krotki jako pierwszego argumentu, przy wszystkich innych argumentach ustawionych na odpowiednie wartości domyślne:
df.xs(('c', 'u'))
Dzięki temu querysprawy stają się nieco niezgrabne:
df.query("one == 'c' and two == 'u'")
Teraz widać, że będzie to stosunkowo trudne do uogólnienia. Ale nadal jest OK dla tego konkretnego problemu.
W przypadku dostępu obejmującego wiele poziomów get_level_valuesnadal można z niego korzystać, ale nie jest to zalecane:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
Pytanie 4
Jak wybrać dwa wiersze odpowiadające ('c', 'u')i ('a', 'w')?
col
one two
c u 9
a w 3
Dzięki loctemu jest to nadal tak proste, jak:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
W przypadku programu querybędziesz musiał dynamicznie generować ciąg zapytania przez iterację po przekrojach i poziomach:
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% NIE POLECAM! Ale jest to możliwe.
Pytanie 5
Jak mogę pobrać wszystkie wiersze odpowiadające „a” na poziomie „pierwszym” lub „t” na poziomie „drugim”?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
Jest to w rzeczywistości bardzo trudne loc, zapewniając jednocześnie poprawność i przejrzystość kodu. df.loc[pd.IndexSlice['a', 't']]jest niepoprawny, jest interpretowany jako df.loc[pd.IndexSlice[('a', 't')]](tj. wybór przekroju). Możesz pomyśleć o rozwiązaniu, które pd.concatumożliwiłoby obsługę każdej etykiety osobno:
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
Ale zauważysz, że jeden z wierszy jest zduplikowany. Dzieje się tak, ponieważ ten wiersz spełniał oba warunki krojenia i pojawił się dwukrotnie. Zamiast tego będziesz musiał to zrobić
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
Ale jeśli ramka DataFrame z natury zawiera zduplikowane indeksy (które chcesz), to ich nie zachowa. Używaj z najwyższą ostrożnością .
Dzięki querytemu jest to głupio proste:
df.query("one == 'a' or two == 't'")
Dzięki get_level_valuestemu jest to nadal proste, ale nie tak eleganckie:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
Pytanie 6
Jak mogę ciąć określone przekroje? Dla „a” i „b” chciałbym zaznaczyć wszystkie wiersze z podpoziomami „u” i „v”, a dla „d” chciałbym zaznaczyć wiersze z podpoziomem „w”.
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
Jest to specjalny przypadek, który dodałem, aby pomóc zrozumieć zastosowanie czterech idiomów - jest to jeden przypadek, w którym żaden z nich nie będzie działał skutecznie, ponieważ krojenie jest bardzo specyficzne i nie przebiega według żadnego rzeczywistego wzoru.
Zwykle takie problemy z wycinaniem wymagają jawnego przekazania listy kluczy do loc. Jednym ze sposobów jest:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
Jeśli chcesz zaoszczędzić trochę pisania, zauważysz, że istnieje wzorzec do krojenia „a”, „b” i jego podpoziomów, więc możemy podzielić zadanie krojenia na dwie części i concat wynik:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
Specyfikacja krojenia „a” i „b” jest nieco bardziej przejrzysta, (('a', 'b'), ('u', 'v'))ponieważ indeksowane poziomy podrzędne są takie same dla każdego poziomu.
Pytanie 7
Jak uzyskać wszystkie wiersze, w których wartości na poziomie „dwa” są większe niż 5?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
Można to zrobić za pomocą query,
df2.query("two > 5")
A get_level_values.
df2[df2.index.get_level_values('two') > 5]
Uwaga
Podobnie jak w tym przykładzie, możemy filtrować na podstawie dowolnego warunku przy użyciu tych konstrukcji. Ogólnie rzecz biorąc, warto o tym pamiętać loci xssą one przeznaczone specjalnie do indeksowania opartego na etykietach, podczas gdy queryi
get_level_valuessą pomocne przy tworzeniu ogólnych masek warunkowych do filtrowania.
Pytanie dodatkowe
A co, jeśli muszę wyciąć MultiIndex kolumnę ?
W rzeczywistości większość rozwiązań tutaj ma zastosowanie również do kolumn, z niewielkimi zmianami. Rozważać:
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
Oto następujące zmiany, które musisz wprowadzić w czterech idiomach, aby działały z kolumnami.
Do krojenia z loc , użyj
df3.loc[:, ....] # Notice how we slice across the index with `:`.
lub,
df3.loc[:, pd.IndexSlice[...]]
Używać xs odpowiednio, po prostu przekaż argument axis=1.
Dostęp do wartości na poziomie kolumny można uzyskać bezpośrednio za pomocą df.columns.get_level_values . Będziesz wtedy musiał zrobić coś takiego
df.loc[:, {condition}]
Gdzie {condition}reprezentuje warunek zbudowany przy użyciucolumns.get_level_values .
Aby użyć query, jedyną opcją jest transpozycja, zapytanie o indeks i ponowna transpozycja:
df3.T.query(...).T
Niezalecane, użyj jednej z pozostałych 3 opcji.
levelkłótniIndex.isin!