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.IndexSlice
dla 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ć xs
tutaj, ponieważ pobieramy pojedynczy przekrój. Zwróć uwagę na argumenty levels
i 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=False
argument jest potrzebny, aby zapobiec spadkowi xs
poziomu „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_level
argument (zakłada się, że jest to True
domyś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.IndexSlice
tutaj 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ż loc
można użyć do zaznaczania i cięcia wzdłuż obu osi ( axis=0
lub
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, loc
akceptuje axis
parametr:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Bez axis
parametru (tj. Po prostu robiąc df.loc[pd.IndexSlice[:, 't']]
) zakłada się, że cięcie odbywa się na kolumnach i KeyError
w takiej sytuacji zostanie podniesione a.
Jest to udokumentowane w fragmentatorach . Jednak na potrzeby tego postu wyraźnie określimy wszystkie osie.
Tak xs
jest
df.xs('t', axis=0, level=1, drop_level=False)
Tak query
jest
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 loc
temu 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_values
i 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 PerformanceWarning
wyglą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 True
lub False
odpowiednio. Możesz wywołać tę funkcję, aby określić, czy dodatkowy krok sortowania jest wymagany, czy nie.
W przypadku xs
jest 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 query
sprawy 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_values
nadal 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 loc
temu jest to nadal tak proste, jak:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
W przypadku programu query
bę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.concat
umoż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 query
temu jest to głupio proste:
df.query("one == 'a' or two == 't'")
Dzięki get_level_values
temu 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ć loc
i xs
są one przeznaczone specjalnie do indeksowania opartego na etykietach, podczas gdy query
i
get_level_values
są 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.
level
kłótniIndex.isin
!