Jak wybrać wiersze z DataFrame
podstawie wartości w niektórych kolumnach w Python Pandas?
W SQL użyłbym:
SELECT *
FROM table
WHERE colume_name = some_value
Próbowałem przejrzeć dokumentację pand, ale nie znalazłem od razu odpowiedzi.
Jak wybrać wiersze z DataFrame
podstawie wartości w niektórych kolumnach w Python Pandas?
W SQL użyłbym:
SELECT *
FROM table
WHERE colume_name = some_value
Próbowałem przejrzeć dokumentację pand, ale nie znalazłem od razu odpowiedzi.
Odpowiedzi:
Aby wybrać wiersze, których wartość kolumny jest równa skalarowi some_value
, użyj ==
:
df.loc[df['column_name'] == some_value]
Aby wybrać wiersze, których wartość kolumny jest iterowalna some_values
, użyj isin
:
df.loc[df['column_name'].isin(some_values)]
Połącz wiele warunków z &
:
df.loc[(df['column_name'] >= A) & (df['column_name'] <= B)]
Zwróć uwagę na nawiasy. Ze względu na reguły pierwszeństwa operatora Pythona , &
wiązania są ściślejsze niż <=
i >=
. Dlatego nawiasy w ostatnim przykładzie są konieczne. Bez nawiasów
df['column_name'] >= A & df['column_name'] <= B
jest analizowany jako
df['column_name'] >= (A & df['column_name']) <= B
co powoduje, że wartość Prawdy Serii jest niejednoznacznym błędem .
Aby wybrać wiersze, których wartość kolumny nie jest równa some_value
, użyj !=
:
df.loc[df['column_name'] != some_value]
isin
Zwraca wartość logiczną z serii, tak aby wybrać wiersze, których wartość jest nie w some_values
, neguje logiczną Series przy użyciu ~
:
df.loc[~df['column_name'].isin(some_values)]
Na przykład,
import pandas as pd
import numpy as np
df = pd.DataFrame({'A': 'foo bar foo bar foo bar foo foo'.split(),
'B': 'one one two three two two one three'.split(),
'C': np.arange(8), 'D': np.arange(8) * 2})
print(df)
# A B C D
# 0 foo one 0 0
# 1 bar one 1 2
# 2 foo two 2 4
# 3 bar three 3 6
# 4 foo two 4 8
# 5 bar two 5 10
# 6 foo one 6 12
# 7 foo three 7 14
print(df.loc[df['A'] == 'foo'])
daje
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Jeśli masz wiele wartości, które chcesz uwzględnić, umieść je na liście (lub bardziej ogólnie, dowolne iterowalne) i użyj isin
:
print(df.loc[df['B'].isin(['one','three'])])
daje
A B C D
0 foo one 0 0
1 bar one 1 2
3 bar three 3 6
6 foo one 6 12
7 foo three 7 14
Pamiętaj jednak, że jeśli chcesz to zrobić wiele razy, bardziej efektywne jest najpierw utworzenie indeksu, a następnie użycie df.loc
:
df = df.set_index(['B'])
print(df.loc['one'])
daje
A C D
B
one foo 0 0
one bar 1 2
one foo 6 12
lub, aby dołączyć wiele wartości z indeksu, użyj df.index.isin
:
df.loc[df.index.isin(['one','two'])]
daje
A C D
B
one foo 0 0
one bar 1 2
two foo 2 4
two foo 4 8
two bar 5 10
one foo 6 12
df.where(condition)
warunek musi mieć taki sam kształt jak df
.
df[df['column_name'] == some_value]
działa, dlaczego potrzebujemy .loc
tutaj dodać ?
Istnieje kilka sposobów wybierania wierszy z ramki danych pand:
df[df['col'] == value
])df.iloc[...]
)df.xs(...)
)df.query(...)
APIPoniżej pokazuję przykłady każdego z nich wraz z radą, kiedy stosować określone techniki. Załóżmy, że naszym kryterium jest kolumna 'A'
=='foo'
(Uwaga na temat wydajności: w przypadku każdego typu podstawowego możemy uprościć sprawę, używając interfejsu API pand lub możemy wyjść poza interfejs API, zwykle w celu numpy
przyspieszenia.)
Konfiguracja
Pierwszą rzeczą, której potrzebujemy, jest określenie warunku, który będzie naszym kryterium wyboru wierszy. Zaczniemy od przypadku OP column_name == some_value
i dołączymy kilka innych typowych przypadków użycia.
Pożyczanie od @unutbu:
import pandas as pd, numpy as np
df = pd.DataFrame({'A': 'foo bar foo bar foo bar foo foo'.split(),
'B': 'one one two three two two one three'.split(),
'C': np.arange(8), 'D': np.arange(8) * 2})
... Indeksowanie boolowskie wymaga znalezienia prawdziwej wartości 'A'
kolumny każdego wiersza równej 'foo'
, a następnie użycia tych wartości prawdy do zidentyfikowania, które wiersze zachować. Zazwyczaj chcielibyśmy nazwać tej serii tablicę wartości prawdę mask
. Zrobimy to również tutaj.
mask = df['A'] == 'foo'
Następnie możemy użyć tej maski do wycinania lub indeksowania ramki danych
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Jest to jeden z najprostszych sposobów wykonania tego zadania, a jeśli wydajność lub intuicyjność nie stanowią problemu, powinna to być wybrana metoda. Jeśli jednak wydajność stanowi problem, warto rozważyć alternatywny sposób utworzenia pliku mask
.
Indeksowanie pozycyjne ( df.iloc[...]
) ma swoje przypadki użycia, ale nie jest to jeden z nich. Aby określić, gdzie należy kroić, najpierw musimy wykonać tę samą analizę boolowską, którą wykonaliśmy powyżej. To pozostawia nam jeden dodatkowy krok do wykonania tego samego zadania.
mask = df['A'] == 'foo'
pos = np.flatnonzero(mask)
df.iloc[pos]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Indeksowanie etykiet może być bardzo przydatne, ale w tym przypadku ponownie wykonujemy więcej pracy bez żadnych korzyści
df.set_index('A', append=True, drop=False).xs('foo', level=1)
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
df.query()
APIpd.DataFrame.query
jest bardzo eleganckim / intuicyjnym sposobem wykonania tego zadania, ale często jest wolniejszy. Jednakże , jeśli zwrócić uwagę na czasy poniżej, dla dużych danych, zapytanie jest bardzo wydajny. Bardziej niż standardowe podejście i podobnej wielkości jak moja najlepsza sugestia.
df.query('A == "foo"')
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Preferuję używanie Boolean
mask
Rzeczywistych ulepszeń można dokonać, modyfikując sposób, w jaki tworzymy nasze Boolean
mask
.
mask
alternatywa 1
Użyj podstawowej numpy
tablicy i zrezygnuj z narzutów związanych z tworzeniem kolejnejpd.Series
mask = df['A'].values == 'foo'
Na końcu pokażę pełniejsze testy czasowe, ale spójrzmy tylko na wzrost wydajności, jaki uzyskujemy dzięki przykładowej ramce danych. Najpierw przyjrzymy się różnicy w tworzeniumask
%timeit mask = df['A'].values == 'foo'
%timeit mask = df['A'] == 'foo'
5.84 µs ± 195 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
166 µs ± 4.45 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Ocena za mask
pomocą numpy
tablicy jest ~ 30 razy szybsza. Wynika to częściowo z tego, że numpy
ocena jest często szybsza. Wynika to również częściowo z braku narzutu niezbędnego do zbudowania indeksu i odpowiedniego pd.Series
obiektu.
Następnie przyjrzymy się terminowi krojenia z jednym mask
na drugi.
mask = df['A'].values == 'foo'
%timeit df[mask]
mask = df['A'] == 'foo'
%timeit df[mask]
219 µs ± 12.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
239 µs ± 7.03 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Wzrost wydajności nie jest tak wyraźny. Zobaczymy, czy to wytrzyma bardziej szczegółowe testy.
mask
alternatywa 2
Mogliśmy również zrekonstruować ramkę danych. Podczas odtwarzania ramki danych istnieje duże zastrzeżenie - musisz się tym zająćdtypes
to zrobić!
Zamiast tego df[mask]
to zrobimy
pd.DataFrame(df.values[mask], df.index[mask], df.columns).astype(df.dtypes)
Jeśli ramka danych jest typu mieszanego, jak w naszym przykładzie, to kiedy otrzymamy df.values
wynikową tablicę, będzie ona dtype
object
zawierała wszystkie kolumny nowej ramki danych dtype
object
. Wymaga to astype(df.dtypes)
i zabija wszelkie potencjalne przyrosty wydajności.
%timeit df[m]
%timeit pd.DataFrame(df.values[mask], df.index[mask], df.columns).astype(df.dtypes)
216 µs ± 10.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1.43 ms ± 39.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Jeśli jednak ramka danych nie jest typu mieszanego, jest to bardzo przydatny sposób na zrobienie tego.
Dany
np.random.seed([3,1415])
d1 = pd.DataFrame(np.random.randint(10, size=(10, 5)), columns=list('ABCDE'))
d1
A B C D E
0 0 2 7 3 8
1 7 0 6 8 6
2 0 2 0 4 9
3 7 3 2 4 3
4 3 6 7 7 4
5 5 3 7 5 9
6 8 7 6 4 7
7 6 2 6 6 5
8 2 8 7 5 8
9 4 7 6 1 5
%%timeit
mask = d1['A'].values == 7
d1[mask]
179 µs ± 8.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Przeciw
%%timeit
mask = d1['A'].values == 7
pd.DataFrame(d1.values[mask], d1.index[mask], d1.columns)
87 µs ± 5.12 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Skróciliśmy czas o połowę.
mask
alternatywa 3
@unutbu pokazuje również, jak używać pd.Series.isin
do rozliczania każdego elementu df['A']
znajdującego się w zbiorze wartości. To ocenia to samo, jeśli nasz zestaw wartości jest zbiorem jednej wartości, a mianowicie 'foo'
. Ale uogólnia również, aby w razie potrzeby uwzględnić większe zestawy wartości. Okazuje się, że wciąż jest to dość szybkie, mimo że jest to bardziej ogólne rozwiązanie. Jedyną prawdziwą stratą jest intuicyjność dla tych, którzy nie znają tej koncepcji.
mask = df['A'].isin(['foo'])
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Jednak, jak poprzednio, możemy wykorzystać numpy
do poprawy wydajności, poświęcając praktycznie nic. Użyjemynp.in1d
mask = np.in1d(df['A'].values, ['foo'])
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Czas
uwzględnię również inne pojęcia wymienione w innych postach w celach informacyjnych.
Kod poniżej
Każda kolumna w tej tabeli reprezentuje ramkę danych o innej długości, w której testujemy każdą funkcję. Każda kolumna pokazuje względny czas, przy czym najszybsza funkcja ma indeks bazowy wynoszący 1.0
.
res.div(res.min())
10 30 100 300 1000 3000 10000 30000
mask_standard 2.156872 1.850663 2.034149 2.166312 2.164541 3.090372 2.981326 3.131151
mask_standard_loc 1.879035 1.782366 1.988823 2.338112 2.361391 3.036131 2.998112 2.990103
mask_with_values 1.010166 1.000000 1.005113 1.026363 1.028698 1.293741 1.007824 1.016919
mask_with_values_loc 1.196843 1.300228 1.000000 1.000000 1.038989 1.219233 1.037020 1.000000
query 4.997304 4.765554 5.934096 4.500559 2.997924 2.397013 1.680447 1.398190
xs_label 4.124597 4.272363 5.596152 4.295331 4.676591 5.710680 6.032809 8.950255
mask_with_isin 1.674055 1.679935 1.847972 1.724183 1.345111 1.405231 1.253554 1.264760
mask_with_in1d 1.000000 1.083807 1.220493 1.101929 1.000000 1.000000 1.000000 1.144175
Zauważysz, że najszybsze czasy wydają się być dzielone między mask_with_values
imask_with_in1d
res.T.plot(loglog=True)
Funkcje
def mask_standard(df):
mask = df['A'] == 'foo'
return df[mask]
def mask_standard_loc(df):
mask = df['A'] == 'foo'
return df.loc[mask]
def mask_with_values(df):
mask = df['A'].values == 'foo'
return df[mask]
def mask_with_values_loc(df):
mask = df['A'].values == 'foo'
return df.loc[mask]
def query(df):
return df.query('A == "foo"')
def xs_label(df):
return df.set_index('A', append=True, drop=False).xs('foo', level=-1)
def mask_with_isin(df):
mask = df['A'].isin(['foo'])
return df[mask]
def mask_with_in1d(df):
mask = np.in1d(df['A'].values, ['foo'])
return df[mask]
Testowanie
res = pd.DataFrame(
index=[
'mask_standard', 'mask_standard_loc', 'mask_with_values', 'mask_with_values_loc',
'query', 'xs_label', 'mask_with_isin', 'mask_with_in1d'
],
columns=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
dtype=float
)
for j in res.columns:
d = pd.concat([df] * j, ignore_index=True)
for i in res.index:a
stmt = '{}(d)'.format(i)
setp = 'from __main__ import d, {}'.format(i)
res.at[i, j] = timeit(stmt, setp, number=50)
Specjalny czas
Patrząc na szczególny przypadek, gdy mamy pojedynczy obiekt niebędący obiektem dtype
dla całej ramki danych.
Kod poniżej
spec.div(spec.min())
10 30 100 300 1000 3000 10000 30000
mask_with_values 1.009030 1.000000 1.194276 1.000000 1.236892 1.095343 1.000000 1.000000
mask_with_in1d 1.104638 1.094524 1.156930 1.072094 1.000000 1.000000 1.040043 1.027100
reconstruct 1.000000 1.142838 1.000000 1.355440 1.650270 2.222181 2.294913 3.406735
Okazuje się, że odbudowa nie jest warta kilkuset rzędów.
spec.T.plot(loglog=True)
Funkcje
np.random.seed([3,1415])
d1 = pd.DataFrame(np.random.randint(10, size=(10, 5)), columns=list('ABCDE'))
def mask_with_values(df):
mask = df['A'].values == 'foo'
return df[mask]
def mask_with_in1d(df):
mask = np.in1d(df['A'].values, ['foo'])
return df[mask]
def reconstruct(df):
v = df.values
mask = np.in1d(df['A'].values, ['foo'])
return pd.DataFrame(v[mask], df.index[mask], df.columns)
spec = pd.DataFrame(
index=['mask_with_values', 'mask_with_in1d', 'reconstruct'],
columns=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
dtype=float
)
Testowanie
for j in spec.columns:
d = pd.concat([df] * j, ignore_index=True)
for i in spec.index:
stmt = '{}(d)'.format(i)
setp = 'from __main__ import d, {}'.format(i)
spec.at[i, j] = timeit(stmt, setp, number=50)
.iloc(numpy.where(..))
porównać w tym schemacie? ii) czy spodziewałbyś się, że rankingi będą takie same, jeśli zastosujesz wiele warunków?
pd.Series.isin
, trzeba pamiętać, że nie stosowanie np.in1d
pod maską w konkretnym scenariuszu, Khash zastosowania w innych, a pośrednio stosuje kompromis między kosztem mieszaja kontra wydajności w konkretnych sytuacjach. Ta odpowiedź ma więcej szczegółów.
[{P|EXP}TIME]
- a [{C|P|EXP}SPACE]
- koszty korzystania z wyżej proponowanych form blokowo składni (top-down przetwarzanie całych dataframes naraz) rosną , a mianowicie, gdy skalowane do niektórych ~1E6, ~1E9, ~1E12
liczb wierszy? Dzięki za pokazanie nam całego zdjęcia, Sir. Ilościowe odczyty wzorcowe z [min, Avg, MAX, StDev]
są zawsze mile widziane, ponieważ zarówno wartości , jak min
i MAX
wartości towarzyszą Mean/StDev
zwolnieniu partii.
Pandy równoważne
select * from table where column_name = some_value
jest
table[table.column_name == some_value]
Wiele warunków:
table[(table.column_name == some_value) | (table.column_name2 == some_value2)]
lub
table.query('column_name == some_value | column_name2 == some_value2')
import pandas as pd
# Create data set
d = {'foo':[100, 111, 222],
'bar':[333, 444, 555]}
df = pd.DataFrame(d)
# Full dataframe:
df
# Shows:
# bar foo
# 0 333 100
# 1 444 111
# 2 555 222
# Output only the row(s) in df where foo is 222:
df[df.foo == 222]
# Shows:
# bar foo
# 2 555 222
W powyższym kodzie jest to wiersz, df[df.foo == 222]
który podaje wiersze na podstawie wartości kolumny, 222
w tym przypadku.
Możliwe są również różne warunki:
df[(df.foo == 222) | (df.bar == 444)]
# bar foo
# 1 444 111
# 2 555 222
Ale w tym momencie zaleciłbym użycie funkcji zapytania , ponieważ jest mniej gadatliwa i daje ten sam wynik:
df.query('foo == 222 | bar == 444')
query
to jedyna odpowiedź tutaj zgodna z łańcuchem metod. Wygląda na to, że to pandy analogiczne do filter
dplyr.
[
nie okrągłych (
na zewnątrz.
|
to dla AND, ale oczywiście jest to operator OR ...
df[condition1][condition2]
df.query('`my col` == 124')
Uważam, że składnia poprzednich odpowiedzi jest zbędna i trudna do zapamiętania. Pandas wprowadził tę query()
metodę w wersji 0.13 i zdecydowanie wolę ją. Na twoje pytanie możesz to zrobićdf.query('col == val')
Reprodukcja z http://pandas.pydata.org/pandas-docs/version/0.17.0/indexing.html#indexing-query
In [167]: n = 10
In [168]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))
In [169]: df
Out[169]:
a b c
0 0.687704 0.582314 0.281645
1 0.250846 0.610021 0.420121
2 0.624328 0.401816 0.932146
3 0.011763 0.022921 0.244186
4 0.590198 0.325680 0.890392
5 0.598892 0.296424 0.007312
6 0.634625 0.803069 0.123872
7 0.924168 0.325076 0.303746
8 0.116822 0.364564 0.454607
9 0.986142 0.751953 0.561512
# pure python
In [170]: df[(df.a < df.b) & (df.b < df.c)]
Out[170]:
a b c
3 0.011763 0.022921 0.244186
8 0.116822 0.364564 0.454607
# query
In [171]: df.query('(a < b) & (b < c)')
Out[171]:
a b c
3 0.011763 0.022921 0.244186
8 0.116822 0.364564 0.454607
Możesz również uzyskać dostęp do zmiennych w środowisku, przygotowując plik @
.
exclude = ('red', 'orange')
df.query('color not in @exclude')
numexpr
zainstalowanego pakietu .
.query
dzięki pandas >= 0.25.0
:Zaktualizowana odpowiedź z sierpnia 2019 r
Ponieważ pandas >= 0.25.0
możemy użyć tej query
metody do filtrowania ramek danych za pomocą metod pand, a nawet nazw kolumn zawierających spacje. Normalnie spacje w nazwach kolumn dawałyby błąd, ale teraz możemy to rozwiązać za pomocą backsticka (`) patrz GitHub :
# Example dataframe
df = pd.DataFrame({'Sender email':['ex@example.com', "reply@shop.com", "buy@shop.com"]})
Sender email
0 ex@example.com
1 reply@shop.com
2 buy@shop.com
Za .query
pomocą metody str.endswith
:
df.query('`Sender email`.str.endswith("@shop.com")')
Wynik
Sender email
1 reply@shop.com
2 buy@shop.com
Możemy również użyć zmiennych lokalnych, poprzedzając je @
w naszym zapytaniu:
domain = 'shop.com'
df.query('`Sender email`.str.endswith(@domain)')
Wynik
Sender email
1 reply@shop.com
2 buy@shop.com
Szybsze wyniki można osiągnąć za pomocą numpy.where .
Na przykład przy konfiguracji unubtu -
In [76]: df.iloc[np.where(df.A.values=='foo')]
Out[76]:
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Porównanie czasowe:
In [68]: %timeit df.iloc[np.where(df.A.values=='foo')] # fastest
1000 loops, best of 3: 380 µs per loop
In [69]: %timeit df.loc[df['A'] == 'foo']
1000 loops, best of 3: 745 µs per loop
In [71]: %timeit df.loc[df['A'].isin(['foo'])]
1000 loops, best of 3: 562 µs per loop
In [72]: %timeit df[df.A=='foo']
1000 loops, best of 3: 796 µs per loop
In [74]: %timeit df.query('(A=="foo")') # slowest
1000 loops, best of 3: 1.71 ms per loop
Oto prosty przykład
from pandas import DataFrame
# Create data set
d = {'Revenue':[100,111,222],
'Cost':[333,444,555]}
df = DataFrame(d)
# mask = Return True when the value in column "Revenue" is equal to 111
mask = df['Revenue'] == 111
print mask
# Result:
# 0 False
# 1 True
# 2 False
# Name: Revenue, dtype: bool
# Select * FROM df WHERE Revenue = 111
df[mask]
# Result:
# Cost Revenue
# 1 444 111
Aby wybrać tylko określone kolumny z wielu kolumn dla danej wartości w pandach:
select col_name1, col_name2 from table where column_name = some_value.
Opcje:
df.loc[df['column_name'] == some_value][[col_name1, col_name2]]
lub
df.query['column_name' == 'some_value'][[col_name1, col_name2]]
Aby dołączyć do tego znanego pytania (choć trochę za późno): Możesz także zrobić df.groupby('column_name').get_group('column_desired_value').reset_index()
nową ramkę danych z określoną kolumną o określonej wartości. Na przykład
import pandas as pd
df = pd.DataFrame({'A': 'foo bar foo bar foo bar foo foo'.split(),
'B': 'one one two three two two one three'.split()})
print("Original dataframe:")
print(df)
b_is_two_dataframe = pd.DataFrame(df.groupby('B').get_group('two').reset_index()).drop('index', axis = 1)
#NOTE: the final drop is to remove the extra index column returned by groupby object
print('Sub dataframe where B is two:')
print(b_is_two_dataframe)
Uruchom to daje:
Original dataframe:
A B
0 foo one
1 bar one
2 foo two
3 bar three
4 foo two
5 bar two
6 foo one
7 foo three
Sub dataframe where B is two:
A B
0 foo two
1 foo two
2 bar two
get_group()
automatycznie zwróci ramkę danych. Możesz także powiedzieć „drop = True” jako parametr parametru reset_index()
. Innymi słowy, można go skrócić do: b_is_two_dataframe = df.groupby('B').get_group('two').reset_index(drop=True)
Możesz także użyć .apply:
df.apply(lambda row: row[df['B'].isin(['one','three'])])
W rzeczywistości działa wierszowo (tzn. Stosuje funkcję do każdego wiersza).
Dane wyjściowe to
A B C D
0 foo one 0 0
1 bar one 1 2
3 bar three 3 6
6 foo one 6 12
7 foo three 7 14
Wyniki są takie same jak przy użyciu, jak wspomniano w @unutbu
df[[df['B'].isin(['one','three'])]]