Материал содержит методы отбора и фильтрации данных в
pandas
различными способами с подробными примерами и описанием.
Для выполнения примеров загрузим данные:
>>> import pandas as pd >>> import numpy as np url = ( "https://raw.githubusercontent.com/pandas-dev" "/pandas/main/pandas/tests/io/data/csv/tips.csv" ) >>> tips = pd.read_csv(url) >>> tips # total_bill tip sex smoker day time size # 0 16.99 1.01 Female No Sun Dinner 2 # 1 10.34 1.66 Male No Sun Dinner 3 # 2 21.01 3.50 Male No Sun Dinner 3 # 3 23.68 3.31 Male No Sun Dinner 2 # 4 24.59 3.61 Female No Sun Dinner 4 # .. ... ... ... ... ... ... ... # 239 29.03 5.92 Male No Sat Dinner 3 # 240 27.18 2.00 Female Yes Sat Dinner 2 # 241 22.67 2.00 Male Yes Sat Dinner 2 # 242 17.82 1.75 Male No Sat Dinner 2 # 243 18.78 3.00 Female No Thur Dinner 2 # # [244 rows x 7 columns]
DataFrame.query()
;Series.between()
;.take()
);.truncate()
).В pandas.DataFrame
можно фильтровать данные несколькими способами. Наиболее интуитивным из них является использование логической индексации.
>>> tips[tips["total_bill"] > 10].head() # total_bill tip sex smoker day time size new_bill # 0 14.99 1.01 Female No Sun Dinner 2 7.495 # 2 19.01 3.50 Male No Sun Dinner 3 9.505 # 3 21.68 3.31 Male No Sun Dinner 2 10.840 # 4 22.59 3.61 Female No Sun Dinner 4 11.295 # 5 23.29 4.71 Male No Sun Dinner 4 11.645
Для комбинации нескольких условий pandas
использует &
(И), |
(ИЛИ) и ~
(НЕ):
>>> tips[(tips["total_bill"] > 10) & (tips["sex"] == "Male")].head() # total_bill tip sex smoker day time size # 1 10.34 1.66 Male No Sun Dinner 3 # 2 21.01 3.50 Male No Sun Dinner 3 # 3 23.68 3.31 Male No Sun Dinner 2 # 5 25.29 4.71 Male No Sun Dinner 4 # 7 26.88 3.12 Male No Sun Dinner 4 # для получения среза добавим в конец выражения необходимый метод >>> tips[(tips["total_bill"] > 10) & (tips["sex"] == "Male")].loc[100:106, ['total_bill', 'tip']] # total_bill tip # 105 15.36 1.64 # 106 20.49 4.06
DataFrame.query()
Если необходимо выставить большое количество условий, то метод DataFrame.query()
дает более читаемый синтаксис:
>>> tips.query('total_bill > 10 and sex == "Male"').loc[100:106, ['total_bill', 'tip']] # total_bill tip # 105 15.36 1.64 # 106 20.49 4.06
Метод DataFrame.query()
позволяет использовать переменные внутри своего запроса. Переменные внутри текстового запроса обозначаются символом @
:
>>> bill = 10 >>> tips.query('total_bill > @bill and sex == "Male"').loc[100:106, ['total_bill', 'tip']] # total_bill tip # 105 15.36 1.64 # 106 20.49 4.06
Обратите внимание, что метод
DataFrame.query()
предоставляется только объектомpandas.DataFrame
.
Допустим, есть список каких-то данных и стоит необходимость отфильтровать только те строки, которые содержат эти данные:
>>> size = [1, 4, 6] >>> tips[tips['size'].isin(size)].loc[111:142, ['total_bill', 'tip', 'size']] # total_bill tip size # 111 7.25 1.00 1 # 116 29.93 5.07 4 # 119 24.08 2.92 4 # 125 29.80 4.20 6 # 141 34.30 6.70 6
Метод
.isin()
предоставляется как объектомpandas.Series
так иpandas.DataFrame
.
Обратите внимание, что методы строк предоставляются только объектом
pandas.Series
.
>>> tips[tips['day'].str.startswith('S')].loc[54:58, ['total_bill', 'tip', 'day']] # total_bill tip day # 54 25.56 4.34 Sun # 55 19.49 3.51 Sun # 56 38.01 3.00 Sat # 57 26.41 1.50 Sat # 58 11.24 1.76 Sat
Дополнительно смотрите материал "Строковые методы объектов Series/Index в pandas"
Pandas
также позволяет использовать регулярные выражения для фильтрации текстовых данных. Очень полезно, когда необходимы сложные условия для составления фильтра.
>>> tips[tips['day'].str.contains(r'hur', regex=True)].loc[77:81, ['total_bill', 'day']] # total_bill day # 77 27.20 Thur # 78 22.76 Thur # 79 17.29 Thur # 80 19.44 Thur # 81 16.66 Thur
Обратите внимание, что "Строковые методы объектов Series/Index в pandas" предоставляются только объектом
pandas.Series
.Больше об использовании регулярных выражений смотрите в материале "Использование регулярных выражений в Pandas".
Series.between()
Метод Series.between()
возвращает логическое значение Series
, эквивалентное left <= Series <= right
. Другими словами возвращает логический вектор, содержащий значение True
, везде, где соответствующий элемент Series
находится между граничными значениями, находящимися слева и справа.
>>> tips[tips['tip'].between(5, 6)].loc[10:50, ['total_bill', 'tip']] # total_bill tip # 11 35.26 5.0 # 39 31.27 5.0 # 44 30.40 5.6 # 46 22.23 5.0 # 47 32.40 6.0
Обратите внимание, что метод
Series.between()
предоставляются только объектомpandas.Series
.Есть также метод
.between_time()
, который предоставляется как объектомpandas.Series
так иpandas.DataFrame
.
Для более сложной фильтрации можно использовать метод DataFrame.apply()
вместе с пользовательской функцией. Ранее обсуждалось, как работает фильтрация на основе логических значений. Владея этой информацией можно приспособить метод .apply()
для отбора необходимых данных:
def my_filter(col): return col['total_bill'] < 10 and 'Y' in col['smoker'] >>> t = tips[tips.apply(my_filter, axis='columns')] >>> t[['total_bill', 'smoker']] # total_bill smoker # 67 3.07 Yes # 92 5.75 Yes # 172 7.25 Yes # 178 9.60 Yes # 218 7.74 Yes # 222 8.58 Yes
Метод
.apply()
предоставляется как объектомpandas.Series
так иpandas.DataFrame
.
Для фильтрации строк с пропущенными значениями или без них в pandas
используются методы .isna()
и .notna()
соответственно:
# добавим пропущенные значения # на основе условия создадим серию столбца 'smoker' >>> s = tips.query('total_bill < 10 and smoker == "Yes"')['smoker'] >>> s # 67 Yes # 92 Yes # 172 Yes # 178 Yes # 218 Yes # 222 Yes # Name: smoker, dtype: object # на серии полученной серии присвоим ее значение `np.nan` >>> tips.loc[list(s.index), s.name] = np.nan # теперь выберем пропущенные значения >>> tips[tips['smoker'].isna()].loc[:, 'total_bill':'smoker'] # total_bill tip sex smoker # 67 3.07 1.00 Female NaN # 92 5.75 1.00 Female NaN # 172 7.25 5.15 Male NaN # 178 9.60 4.00 Female NaN # 218 7.74 1.44 Male NaN # 222 8.58 1.92 Male NaN
Методы
.isna()
и.notna()
предоставляется как объектомpandas.Series
так иpandas.DataFrame
.
Pandas
предоставляет методы для отбора данных исключительно на основе индексных меток. Это протокол, основанный на строгом включении. Каждая запрошенная метка должна находиться в индексе, иначе будет выдано KeyError
. Внимание! При нарезке на основе индексных меток включаются как начальная граница, так и конечная граница, если они присутствуют в индексе. Целые числа являются допустимыми метками, но они относятся к метке, а не к позиции.
Свойство
.loc
является основным методом доступа к даннымpandas.DataFrame
иpandas.Series
.
Допускаются следующие входные данные:
'a'
(обратите внимание, что 5 интерпретируется как метка индекса. Это использование не является целым числом в индексе.).['a', 'b', 'c']
.'a':'f'
. Обратите внимание, что в отличие от обычных срезов Python, включаются как начало, так и конец, когда они присутствуют в индексе!Начнем с pandas.Series
:
>>> s1 = pd.Series(np.random.randn(6), index=list('abcdef')) >>> s1 # a 1.431256 # b 1.340309 # c -1.170299 # d -0.226169 # e 0.410835 # f 0.813850 # dtype: float64 # срез данных серии >>> s1.loc['c':] # c -1.170299 # d -0.226169 # e 0.410835 # f 0.813850 # dtype: float64 # получение конкретного значения серии >>> s1.loc['b'] # 1.3403088497993827
Обратите внимание, что присвоение данных также работает:
>>> s1.loc['c':] = 0 >>> s1 # a 1.431256 # b 1.340309 # c 0.000000 # d 0.000000 # e 0.000000 # f 0.000000 # dtype: float64
Перейдем к pandas.DataFrame
:
df1 = pd.DataFrame(np.random.randn(6, 4), index=list('abcdef'), columns=list('ABCD')) >>> df1 A B C D # a 0.132003 -0.827317 -0.076467 -1.187678 # b 1.130127 -1.436737 -1.413681 1.607920 # c 1.024180 0.569605 0.875906 -2.211372 # d 0.974466 -2.006747 -0.410001 -0.078638 # e 0.545952 -1.219217 -1.226825 0.769804 # f -1.281247 -0.727707 -0.121306 -0.097883 # отбор данных по конкретным индексным меткам строк >>> df1.loc[['a', 'b', 'd'], :] # A B C D # a 0.132003 -0.827317 -0.076467 -1.187678 # b 1.130127 -1.436737 -1.413681 1.607920 # d 0.974466 -2.006747 -0.410001 -0.078638
Обратите внимание, что числовые метки строк работают аналогичным образом. Числовые метки строк появляются, если при создании
DataFrame
не задан индекс строк (аргументindex
)
Доступ через фрагменты/срезы индексных меток.
>>> df1.loc['d':, 'A':'C'] # A B C # d 0.974466 -2.006747 -0.410001 # e 0.545952 -1.219217 -1.226825 # f -1.281247 -0.727707 -0.121306
Обратите внимание, что числовые метки строк работают аналогичным образом.
Доступ к элементам одной строки с помощью индексной метки строки:
>>> df1.loc['a'] A 0.132003 B -0.827317 C -0.076467 D -1.187678 Name: a, dtype: float64
Получение значений с помощью логического массива:
# получение логического массива >>> df1.loc['a'] > 0 # A True # B False # C False # D False # Name: a, dtype: bool # подставляем условие в `.loc` >>> df1.loc[:, df1.loc['a'] > 0] # A # a 0.132003 # b 1.130127 # c 1.024180 # d 0.974466 # e 0.545952 # f -1.281247
Pandas
предоставляет методы для отбора данных исключительно на основе целочисленной индексации. Семантика тесно связана со срезами Python и нарезкой в NumPy
. Это индексация, отсчитываемая от 0. При нарезке включается начальная граница, а верхняя граница исключается. Попытка использовать нецелое число, даже допустимую метку, вызовет ошибку IndexError
.
Свойство
.iloc
является основным методом доступа к даннымpandas.DataFrame
иpandas.Series
.
Допускаются следующие входные данные:
[4, 3, 0]
.1:7
.Индексы среза, выходящие за пределы допустимого диапазона, обрабатываются корректно, как и в Python/NumPy.
Начнем примеры с pandas.Series
:
>>> s1 = pd.Series(np.random.randn(5), index=list(range(0, 10, 2))) >>> s1 # 0 0.695775 # 2 0.341734 # 4 0.959726 # 6 -1.110336 # 8 -0.619976 # dtype: float64 # доступ к срезу строк >>> s1.iloc[:3] # 0 0.695775 # 2 0.341734 # 4 0.959726 # dtype: float64 # доступ к конкретному значению >>> s1.iloc[3] # -1.110336102891167
Обратите внимание, что присвоение данных также работает:
>>> s1.iloc[:3] = 0 >>> s1 # 0 0.000000 # 2 0.000000 # 4 0.000000 # 6 -1.110336 # 8 -0.619976 # dtype: float64
Перейдем к pandas.DataFrame
:
df1 = pd.DataFrame(np.random.randn(6, 4), index=list(range(0, 12, 2)), columns=list(range(0, 8, 2))) >>> df1 # 0 2 4 6 # 0 0.149748 -0.732339 0.687738 0.176444 # 2 0.403310 -0.154951 0.301624 -2.179861 # 4 -1.369849 -0.954208 1.462696 -1.743161 # 6 -0.826591 -0.345352 1.314232 0.690579 # 8 0.995761 2.396780 0.014871 3.357427 # 10 -0.317441 -1.236269 0.896171 -0.487602
Выбор данных с помощью среза строк:
>>> df1.iloc[:3] # 0 2 4 6 # 0 0.149748 -0.732339 0.687738 0.176444 # 2 0.403310 -0.154951 0.301624 -2.179861 # 4 -1.369849 -0.954208 1.462696 -1.743161 >>> df1.iloc[1:5, 2:4] # 4 6 # 2 0.301624 -2.179861 # 4 1.462696 -1.743161 # 6 1.314232 0.690579 # 8 0.014871 3.357427
Выбор через данных через целочисленный список строк и столбцов соответственно:
>>> df1.iloc[[1, 3, 5], [1, 3]] # 2 6 # 2 -0.154951 -2.179861 # 6 -0.345352 0.690579 # 10 -1.236269 -0.487602 >>> df1.iloc[1:3, :] # 0 2 4 6 # 2 0.403310 -0.154951 0.301624 -2.179861 # 4 -1.369849 -0.954208 1.462696 -1.743161 >>> df1.iloc[:, 1:3] # 2 4 # 0 -0.732339 0.687738 # 2 -0.154951 0.301624 # 4 -0.954208 1.462696 # 6 -0.345352 1.314232 # 8 2.396780 0.014871 # 10 -1.236269 0.896171
Выбор конкретного значения
# эквивалентно `df1.iat[1,1]` >>> df1.iloc[1, 1] # -0.1549507744249032
Получение данных всех столбцов, расположенных во 2 строке (эквивалент df1.xs(1)
). Обратите внимание, что в полученной Series
индексные метки столбцов стали индексными метками строк:
>>> df1.iloc[1] # 0 0.403310 # 2 -0.154951 # 4 0.301624 # 6 -2.179861 # Name: 2, dtype: float64
Обратите внимание, что использование срезов, выходящих за пределы, может привести к пустой оси (например, к возврату пустого DataFrame
).
>>> dfl = pd.DataFrame(np.random.randn(5, 2), columns=list('AB')) >>> dfl # A B # 0 -0.082240 -2.182937 # 1 0.380396 0.084844 # 2 0.432390 1.519970 # 3 -0.493662 0.600178 # 4 0.274230 0.132885 # получаем пустой `DataFrame` >>> dfl.iloc[:, 2:3] # Empty DataFrame # Columns: [] # Index: [0, 1, 2, 3, 4] >>> dfl.iloc[:, 1:3] # B # 0 -2.182937 # 1 0.084844 # 2 1.519970 # 3 0.600178 # 4 0.132885 >>> dfl.iloc[4:6] # A B # 4 0.27423 0.132885
Доступ к меткам строк и столбцов можно получить соответственно, обратившись к атрибутам индекса DataFrame.index
и столбца DataFrame.columns
:
tmp = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}, index=list('abc')) >>> tmp # A B # a 1 4 # b 2 5 # c 3 6 >>> tmp.index # Index(['a', 'b', 'c'], dtype='object') >>> tmp.columns # Index(['A', 'B'], dtype='object')
Владея этой информацией можно получить 0-й и 2-й элементы из индекса строк в столбце ‘A’ или первые 2 строки:
# 0-й и 2-й элементы из индекса строк в столбце ‘A’ >>> tmp.loc[tmp.index[[0, 2]], 'A'] # a 1 # c 3 # Name: A, dtype: int64 # или срез первых 2 строк >>> tmp.loc[tmp.index[:2], 'A'] # a 1 # b 2 # Name: A, dtype: int64
Это также может быть выражено с помощью .iloc
, путем явного получения местоположений в индексаторах и использования позиционной индексации для выбора объектов.
>>> tmp.iloc[[0, 2], tmp.columns.get_loc('A')] # a 1 # c 3 # Name: A, dtype: int64
Для получения нескольких индексаторов необходимо использовать .get_indexer
:
>>> tmp.iloc[[0, 2], tmp.columns.get_indexer(['A', 'B'])] # A B # a 1 4 # c 3 6
Атрибуты .loc
, .iloc
, а также []
могут принимать вызываемый объект в качестве индексатора. Вызываемый объект должен быть функцией с одним аргументом (принимающий Series
или DataFrame
), которая возвращает допустимый вывод для индексации.
# создадим `DataFrame` df1 = pd.DataFrame(np.random.randn(6, 4), index=list('abcdef'), columns=list('ABCD')) >>> df1 # A B C D # a 0.359167 -0.745366 -0.025158 0.540382 # b -0.460401 -1.093290 -1.130575 0.941098 # c 0.185106 0.260443 -0.783108 -1.799287 # d -1.306875 -0.962832 1.433570 1.708664 # e 0.946027 2.124018 1.517254 1.023682 # f -0.392529 0.917637 -0.013044 0.528854 # отбираем срез данных по условию `df['A'] > 0` # используя `lambda` и атрибут `.loc` >>> df1.loc[lambda df: df['A'] > 0, :] # A B C D # a 0.359167 -0.745366 -0.025158 0.540382 # c 0.185106 0.260443 -0.783108 -1.799287 # e 0.946027 2.124018 1.517254 1.023682 # отбираем столбцы по индексным меткам # используя `lambda` и атрибут `.loc` >>> df1.loc[:, lambda df: ['A', 'B']] # A B # a 0.359167 -0.745366 # b -0.460401 -1.093290 # c 0.185106 0.260443 # d -1.306875 -0.962832 # e 0.946027 2.124018 # f -0.392529 0.917637 # отбираем столбцы по позиции # используя `lambda` и атрибут `.iloc` >>> df1.iloc[:, lambda df: [0, 1]] # A B # a 0.359167 -0.745366 # b -0.460401 -1.093290 # c 0.185106 0.260443 # d -1.306875 -0.962832 # e 0.946027 2.124018 # f -0.392529 0.917637 # отбираем столбец `A` по позиции # используя `lambda` и выражение `DataFrame[]` >>> df1[lambda df: df.columns[0]] # a 0.359167 # b -0.460401 # c 0.185106 # d -1.306875 # e 0.946027 # f -0.392529 # Name: A, dtype: float64
Можно использовать вызываемую функцию в Series
:
>>> df1['A'].loc[lambda s: s > 0] # c 0.299368 # e 1.289997 # Name: A, dtype: float64
О производительности: так как метод
.take()
объектовIndex
/Series
/DataFrame
обрабатывает более узкий диапазон входных данных, он может обеспечить производительность, значительно превышающую производительность обычного индексирования.loc[]
и.iloc[]
.
Index
, Series
и DataFrame
предоставляют метод .take()
, который извлекает элементы вдоль заданной оси по заданным позиционным индексам. Указанные индексы должны быть либо списком, либо массивом позиций целочисленных индексов. Метод .take()
также принимает отрицательные целые числа в качестве позиций относительно конца объекта.
pandas.Index
>>> import pandas as pd >>> import numpy as np >>> index = pd.Index(np.random.randint(0, 1000, 10)) >>> index # Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64') >>> positions = [0, 9, 3] >>> index[positions] # Index([214, 329, 567], dtype='int64') >>> index.take(positions) # Index([214, 329, 567], dtype='int64')
pandas.Series
>>> ser = pd.Series(np.random.randn(10)) >>> ser.iloc[positions] # 0 -0.179666 # 9 1.824375 # 3 0.392149 # dtype: float64 >>> ser.take(positions) # 0 -0.179666 # 9 1.824375 # 3 0.392149 # dtype: float64
pandas.DataFrame
Для DataFrame
данные индексов должны быть списком 1d или numpy.ndarray()
, который определяет позиции строк или столбцов.
>>> frm = pd.DataFrame(np.random.randn(5, 3)) >>> frm.take([1, 4, 3]) # 0 1 2 # 1 -1.237881 0.106854 -1.276829 # 4 0.629675 -1.425966 1.857704 # 3 0.979542 -1.633678 0.615855 >>> frm.take([0, 2], axis=1) # 0 2 # 0 0.595974 0.601544 # 1 -1.237881 -1.276829 # 2 -0.767101 1.499591 # 3 0.979542 0.615855 # 4 0.629675 1.857704
Важно отметить, что метод .take()
объектов pandas
НЕ ПРЕДНАЗНАЧЕН для работы с логическими операторами и может возвращать неожиданные результаты.
>>> arr = np.random.randn(10) >>> arr # array([-0.85087687, -0.30374928, -0.38772344, 1.19152042, -0.49715602, # 0.2688488 , -1.03714584, 0.24092975, 0.29487095, 0.20540514]) >>> arr.take([False, False, True, True]) # array([-0.85087687, -0.85087687, -0.30374928, -0.30374928]) # понимаете о чем речь >>> arr[[0, 1]] # array([-0.85087687, -0.30374928]) >>> ser = pd.Series(np.random.randn(10)) >>> ser # 0 -0.293532 # 1 0.147384 # 2 0.080274 # 3 -0.812621 # 4 1.136176 # 5 0.958868 # 6 -0.078712 # 7 0.702776 # 8 0.611545 # 9 -0.358883 # dtype: float64 >>> ser.take([False, False, True, True]) # 0 -0.293532 # 0 -0.293532 # 1 0.147384 # 1 0.147384 # dtype: float64 # тоже самое >>> ser.iloc[[0, 1]] # 0 -0.293532 # 1 0.147384 # dtype: float64
Метод .truncate()
объектов Series
/DataFrame
усекает Series
/DataFrame
до и после некоторого значения индекса. Если усекаемый индекс содержит представления "datetime
", то аргументы before
и after
могут быть указаны в виде строк даты/времени.
>>> import pandas as pd df = pd.DataFrame({'A': ['a', 'b', 'c', 'd', 'e'], 'B': ['f', 'g', 'h', 'i', 'j'], 'C': ['k', 'l', 'm', 'n', 'o']}, index=[1, 2, 3, 4, 5]) >>> df.truncate(before=2, after=4) # A B C # 2 b g l # 3 c h m # 4 d i n
Обрезаем столбцы DataFrame
.
>>> df.truncate(before="A", after="B", axis="columns") # A B # 1 a f # 2 b g # 3 c h # 4 d i # 5 e j
Для Series
можно обрезать только строки.
>>> df['A'].truncate(before=2, after=4) # 2 b # 3 c # 4 d # Name: A, dtype: object
Пример для значений индекса, как представления "datetime
" библиотеки pandas
. Создадим DataFrame
с индексом из даты/времени:
>>> dates = pd.date_range('2024-01-01', '2024-02-01', freq='s') >>> df = pd.DataFrame(index=dates, data={'A': 1}) # передаем представления "datetime" библиотеки `pandas` >>> df.truncate(before=pd.Timestamp('2024-01-05'), ... after=pd.Timestamp('2024-01-10')).tail() # A # 2024-01-09 23:59:56 1 # 2024-01-09 23:59:57 1 # 2024-01-09 23:59:58 1 # 2024-01-09 23:59:59 1 # 2024-01-10 00:00:00 1
Так как индекс представляет собой DatetimeIndex
, то можно указать аргументы before
и after
в виде строк даты и времени. Перед усечением они будут принудительно преобразованы в метки даты/времени.
# передаем строки в виде даты >>> df.truncate('2024-01-05', '2024-01-10').tail() # A # 2024-01-09 23:59:56 1 # 2024-01-09 23:59:57 1 # 2024-01-09 23:59:58 1 # 2024-01-09 23:59:59 1 # 2024-01-10 00:00:00 1
Обратите внимание, что усечение DatetimeIndex
предполагает значение 0 (полночь) для любого неуказанного компонента даты. Это отличается от частичного среза строк, который возвращает любые частично совпадающие даты.