Сообщить об ошибке.

Методы отбора данных в pandas

Материал содержит методы отбора и фильтрации данных в 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]

Содержание:


Использование условий для фильрации данных

В 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.

Допускаются следующие входные данные:

  • Одиночная метка, например, 5 или 'a' (обратите внимание, что 5 интерпретируется как метка индекса. Это использование не является целым числом в индексе.).
  • Список или массив меток ['a', 'b', 'c'].
  • Объект среза с метками 'a':'f'. Обратите внимание, что в отличие от обычных срезов Python, включаются как начало, так и конец, когда они присутствуют в индексе!
  • Логический массив True/False.

Начнем с 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.

Допускаются следующие входные данные:

  • Целое число, например 5.
  • Список или массив целых чисел [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 (полночь) для любого неуказанного компонента даты. Это отличается от частичного среза строк, который возвращает любые частично совпадающие даты.