Урок курса
Временные столбцы: парсинг, .dt-компоненты, длительности и фильтрация
NumPy и pandas: практический тренажёрКак преобразовать строку в дату pandas
pd.to_datetime преобразует строки в даты. Параметр format задаёт ожидаемый формат, а errors="coerce" заменяет неверные значения на NaT. После преобразования свойства .dt.year и .dt.month доступны у Series.
import pandas as pd
s = pd.Series(["2026-01-15", "не дата", "2026-03-02"])
dates = pd.to_datetime(s, format="%Y-%m-%d", errors="coerce")
print(dates.isna().tolist())
print(dates.dropna().dt.year.tolist())
print(dates.dropna().dt.month.tolist())Результат:
[False, True, False]
[2026, 2026]
[1, 3]Ошибка «Can only use .dt accessor with datetimelike values» означает, что Series ещё не имеет подходящего типа дат. Проверьте s.dtype и результат преобразования. После coerce отдельно посчитайте NaT, чтобы ошибочные даты не потерялись незаметно.
В прошлом уроке мы разбирали .str-аксессор — инструмент для поэлементной работы с текстовыми столбцами. Datetime-столбцы устроены по той же логике: сначала нужно привести данные к нужному типу, а затем работать через специальный аксессор. Начнём с первого шага — парсинга строк в datetime.
Парсинг строкового столбца в datetime через pd.to_datetime
Когда вы читаете CSV или получаете данные из базы, даты почти всегда приходят в виде строк: '2024-03-15', '2024-03-15 14:30:00' и тому подобное. Pandas не знает, что это дата — для него это просто object-столбец. Пока тип остаётся object, никакая временна́я арифметика и никакие .dt-компоненты не работают.
Есть два момента, когда обычно нужно явно преобразовывать тип. Первый — когда столбец уже загружен в DataFrame как строки (например, CSV прочитан без дополнительных параметров). В этом случае используют pd.to_datetime:
import pandas as pd
df = pd.DataFrame({
'event': ['A', 'B', 'C', 'D'],
'start_str': ['2024-01-10 08:00', '2024-02-15 09:30', '2024-03-05 14:00', '2024-04-20 07:00'],
'end_str': ['2024-01-10 10:30', '2024-02-16 09:30', '2024-03-05 18:00', '2024-04-21 07:00']
})
print(df['start_str'].dtype) # object
df['start'] = pd.to_datetime(df['start_str'])
df['end'] = pd.to_datetime(df['end_str'])
print(df['start'].dtype) # datetime64[ns]
pd.to_datetime принимает Series строк и возвращает новый Series с dtype datetime64[ns]. Исходный строковый столбец start_str при этом не трогается — результат кладётся в новый столбец start.
Второй момент — чтение CSV. Если вы знаете заранее, что столбец содержит даты, можно попросить pandas распарсить его прямо при загрузке через parse_dates:
# При чтении CSV: pandas сразу создаст datetime64[ns]-столбец
df = pd.read_csv('events.csv', parse_dates=['start', 'end'])
В этом случае pd.to_datetime не нужен — столбец уже будет нужного типа. Но если CSV уже загружен без parse_dates, или данные пришли не из файла, а из другого источника — pd.to_datetime это стандартный инструмент для преобразования.
Формат строки pandas распознаёт автоматически для распространённых вариантов: 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS', 'YYYY-MM-DD HH:MM' — всё это проходит без дополнительных настроек.
Типичная ошибка: .dt на строковом столбце
Если обратиться к .dt на столбце с dtype object, pandas бросит ошибку:
# Неверно: start_str имеет dtype object
print(df['start_str'].dt.year)
# AttributeError: Can only use .dt accessor with datetimelike values
Диагноз простой: проверьте df['start_str'].dtype. Если видите object — перед вами строки, и нужен pd.to_datetime. После преобразования df['start'].dtype покажет datetime64[ns], и аксессор заработает.
# Верно: сначала парсим, потом используем .dt
df['start'] = pd.to_datetime(df['start_str'])
print(df['start'].dt.year.tolist()) # [2024, 2024, 2024, 2024]
Правило: прежде чем использовать .dt или временну́ю арифметику, убедитесь, что столбец имеет dtype datetime64[ns] — через pd.to_datetime или parse_dates при чтении файла.
Извлечение компонентов даты и времени через .dt-аксессор
Как только столбец стал datetime64[ns], он открывает аксессор .dt — способ добраться до отдельных частей даты и времени. Работает это так же, как .str для строковых столбцов: пишете df['col'].dt.что-то и получаете новый Series той же длины.
Основные компоненты:
.dt.year— год (например,2024).dt.month— номер месяца от 1 до 12.dt.day— день месяца.dt.dayofweek— день недели от 0 (понедельник) до 6 (воскресенье).dt.hour— час из временно́й части
Возьмём столбец start из нашего датафрейма с событиями:
import pandas as pd
df = pd.DataFrame({
'event': ['A', 'B', 'C', 'D'],
'start_str': ['2024-01-10 08:00', '2024-02-15 09:30', '2024-03-05 14:00', '2024-04-20 07:00'],
'end_str': ['2024-01-10 10:30', '2024-02-16 09:30', '2024-03-05 18:00', '2024-04-21 07:00']
})
df['start'] = pd.to_datetime(df['start_str'])
print(df['start'].dt.year.tolist()) # [2024, 2024, 2024, 2024]
print(df['start'].dt.month.tolist()) # [1, 2, 3, 4]
print(df['start'].dt.day.tolist()) # [10, 15, 5, 20]
print(df['start'].dt.dayofweek.tolist()) # [2, 3, 1, 5]
print(df['start'].dt.hour.tolist()) # [8, 9, 14, 7]
Здесь dayofweek=2 означает среду (событие A, 10 января 2024), а dayofweek=5 — субботу (событие D, 20 апреля 2024). Это полезно, когда нужно выделить, например, только рабочие дни: df[df['start'].dt.dayofweek < 5].
Каждый из этих вызовов возвращает обычный целочисленный Series, который можно сразу класть в новый столбец:
df['month'] = df['start'].dt.month
df['hour'] = df['start'].dt.hour
print(df[['event', 'month', 'hour']])
event month hour 0 A 1 8 1 B 2 9 2 C 3 14 3 D 4 7
Так из одного datetime-столбца появляются числовые признаки, которые уже можно использовать в группировках, сравнениях и условиях.
Поведение при NaT. Если в столбце есть пропуски — они представлены как NaT (Not a Time). В этом случае .dt.year и другие компоненты вернут NaN на месте пропуска, а тип всего Series сменится с int64 на float64, потому что целые числа в pandas не умеют хранить NaN. Это не ошибка — просто нужно иметь в виду при дальнейшей работе с такими столбцами.
Самостоятельная практика. Закрепите работу с датами на архиве заказов: извлеките год, месяц и день недели, затем отберите нужный период. Решить задачу на даты и фильтрацию. Другие упражнения на таблицы, пропуски и группировки доступны в бесплатном тренажёре NumPy и pandas; после регистрации дальнейший прогресс сохраняется. Открыть бесплатный курс NumPy и pandas.
Вычисление длительности: Timedelta и перевод в числовые единицы
Когда у вас есть два datetime-столбца — момент начала и момент конца — разность между ними даёт не число, а специальный тип: timedelta64[ns]. Это логично: разница между двумя датами — это длительность, а не просто цифра.
df['start'] = pd.to_datetime(df['start_str'])
df['end'] = pd.to_datetime(df['end_str'])
df['duration'] = df['end'] - df['start']
print(df['duration'].dtype) # timedelta64[ns]
print(df['duration'].tolist())
# [Timedelta('0 days 02:30:00'), Timedelta('1 days 00:00:00'),
# Timedelta('0 days 04:00:00'), Timedelta('1 days 00:00:00')]
Timedelta-столбец уже умеет многое «из коробки»: его можно сравнивать (df['duration'] > pd.Timedelta(hours=3)), а стандартные агрегации — mean(), sum(), min(), max() — работают напрямую:
print(df['duration'].mean()) # 0 days 13:37:30
print(df['duration'].max()) # 1 days 00:00:00
Перевод в числа нужен тогда, когда результат требуется в конкретных единицах — например, чтобы посчитать среднее в часах, сохранить в числовой столбец или передать в модель.
Как получить числовое значение
Самый прямолинейный способ — .dt.total_seconds(). Он переводит длительность в полное количество секунд (тип float64):
df['duration_sec'] = df['duration'].dt.total_seconds()
print(df['duration_sec'].tolist())
# [9000.0, 86400.0, 14400.0, 86400.0]
Здесь 9000 секунд — это 2.5 часа события A, 86400 — ровно сутки события B.
Если нужны часы или дни, делят либо на число секунд в единице, либо на pd.Timedelta:
# Часы через деление total_seconds
df['hours'] = df['duration'].dt.total_seconds() / 3600
print(df['hours'].tolist()) # [2.5, 24.0, 4.0, 24.0]
# Дробные дни через pd.Timedelta
df['days'] = df['duration'] / pd.Timedelta(days=1)
print(df['days'].tolist()) # [0.10416..., 1.0, 0.16666..., 1.0]
Оба подхода дают float64. Деление на pd.Timedelta(hours=1) — то же самое, что total_seconds() / 3600, просто читается чуть понятнее в коде.
Почему не .dt.days
У Timedelta-столбца есть .dt.days, но он возвращает нормализованный компонент дней из внутреннего представления длительности — без учёта часов и минут. Для события A (02:30:00) .dt.days вернёт 0 — часы потеряны:
print(df['duration'].dt.days.tolist()) # [0, 1, 0, 1]
# Событие A: 0 — потеряли 2.5 часа
Для неотрицательных длительностей это ведёт себя как усечение к нулю дробной части суток. Но для отрицательных длительностей поведение другое: нормализация идёт в сторону уменьшения, поэтому, например, -2.5 часа дадут .dt.days == -1 (а не 0). По этой причине .dt.days лучше не использовать как универсальный способ перевода в дни — это источник неочевидных ошибок. Для точного перевода в любые единицы правильный путь — total_seconds() с последующим делением.
Фильтрация строк по временному интервалу через loc
Когда datetime-столбец готов, с ним можно строить булевы маски точно так же, как с числовыми столбцами: операторы >=, <=, >, < работают напрямую. Правая часть сравнения — строка в формате, который pandas понимает как дату, например '2024-02-01'.
import pandas as pd
df = pd.DataFrame({
'event': ['A', 'B', 'C', 'D'],
'start_str': ['2024-01-10 08:00', '2024-02-15 09:30', '2024-03-05 14:00', '2024-04-20 07:00'],
'end_str': ['2024-01-10 10:30', '2024-02-16 09:30', '2024-03-05 18:00', '2024-04-21 07:00']
})
df['start'] = pd.to_datetime(df['start_str'])
df['end'] = pd.to_datetime(df['end_str'])
df['duration'] = df['end'] - df['start']
df['hours'] = df['duration'].dt.total_seconds() / 3600
# Отбираем события, начавшиеся в феврале и марте 2024
mask = (df['start'] >= '2024-02-01') & (df['start'] < '2024-04-01')
result = df.loc[mask, ['event', 'start', 'hours']]
print(result)
event start hours 1 B 2024-02-15 09:30:00 24.0 2 C 2024-03-05 14:00:00 4.0
События A и D не попали в выборку: A началось в январе, D — в апреле. Остались только B и C.
Про скобки. Каждое подусловие нужно оборачивать в скобки. Без них Python разбирает выражение не так, как вы ожидаете, и получается либо TypeError, либо неверный результат:
# Неверно — операторы сравнения и & имеют разные приоритеты
mask = df['start'] >= '2024-02-01' & df['start'] < '2024-04-01'
# Верно
mask = (df['start'] >= '2024-02-01') & (df['start'] < '2024-04-01')
Правило: если объединяете несколько условий через & или |, скобки обязательны вокруг каждого.
Вместо строки можно передать pd.Timestamp. Это тот же результат, просто более явный способ:
mask = (df['start'] >= pd.Timestamp('2024-02-01')) & (df['start'] < pd.Timestamp('2024-04-01'))
Поведение идентично — pandas всё равно приводит строки к Timestamp внутри сравнения. Разница чисто стилистическая.
Полный пайплайн. Всё, что разбиралось в этом уроке, ложится в одну последовательность:
pd.to_datetimeпревращает строки вdatetime64[ns]..dt.year,.dt.month,.dt.dayofweek,.dt.hour— числовые признаки из даты.- Вычитание двух datetime-столбцов даёт
timedelta64[ns];total_seconds()или деление наpd.Timedeltaпереводит в числа. - Булева маска с
>=и<на datetime-столбце +df.loc[mask]отбирает нужный временной интервал.
Каждый шаг независим: можно извлечь .dt.month без вычисления длительности, или отфильтровать строки без создания новых компонентных столбцов. Но вместе они покрывают большинство задач, которые возникают при работе с временными данными.
Эти же числовые столбцы — month, dayofweek, hour — станут естественными ключами группировки, когда дойдёте до groupby.
Задача 1
Series s = pd.Series(['2024-03-15 10:00', '2024-06-01 22:30']). После выполнения s = pd.to_datetime(s) что вернёт s.dt.dayofweek.tolist()?
Подсказка: 2024-03-15 — пятница, 2024-06-01 — суббота.
Задача 2
DataFrame df имеет столбец 'created' dtype datetime64[ns]. Нужно отфильтровать строки за март 2024 года (с 1 марта включительно по 31 марта включительно). Какой код корректно строит маску?
Задача 3
Журнал сессий: парсинг, .dt-компоненты, длительность в минутах и фильтрация по диапазону
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая обрабатывает журнал пользовательских сессий:
- Читает из
stdinцелое числоn— количество строк журнала. - Читает следующие
nстрок; каждая содержит пять токенов через пробел:session_id, дату входа, время входа, дату выхода и время выхода. Пример строки:1 2024-01-15 08:00 2024-01-15 09:30.
Внимание: каждая строка содержит ровно пять токенов:
session_id, дату входа, время входа, дату выхода и время выхода. Читайте строку целиком черезinput()и разбивайте по пробелам: токены[1]и[2]объедините через пробел — этоlogin; токены[3]и[4]объедините через пробел — этоlogout.
- Создаёт
pd.DataFrameс именемdfсо столбцами'session_id'(int),'login'(str),'logout'(str). - Преобразует столбцы
'login'и'logout'вdatetime64[ns]черезpd.to_datetime. - Создаёт столбец
'login_hour'черезdf['login'].dt.hourи столбец'login_weekday'черезdf['login'].dt.dayofweek. - Вычисляет столбец
'duration_min'— длительность сессии в минутах как(df['logout'] - df['login']).dt.total_seconds() / 60. - Читает из
stdinстроку с двумя токенами через пробел:date_fromиdate_to— границы временного интервала (строки видаYYYY-MM-DD). - Фильтрует строки, где
login >= date_fromиlogin < date_to, черезdf.loc[mask]. Сохраняет результат вfiltered. - Выводит в
stdoutровноlen(filtered) + 2строк:
- Строки 1…
len(filtered): для каждой строкиfiltered(в порядке исходного DataFrame) одну строку вида<session_id> <login_hour> <login_weekday> <duration_min>, гдеduration_min— целое число (используйтеint(...)). - Предпоследняя строка:
count=<len(filtered)>. - Последняя строка:
max_duration=<значение>, где значение — максимальнаяduration_minсреди отфильтрованных строк, целое число; еслиfilteredпуст — выведитеmax_duration=0.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n session_id1 YYYY-MM-DD HH:MM YYYY-MM-DD HH:MM ... date_from date_to
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
<session_id> <login_hour> <login_weekday> <duration_min>
...
count=<k>
max_duration=<m>
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 4
Найди и исправь: три ошибки в .dt-компонентах и переводе Timedelta в дни
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.
В редакторе уже есть сломанный код. В нём ровно три ошибки в трёх независимых строках:
- Столбец
'month'вычисляется черезdf['start'].dt.month— но перед этимdf['start']не преобразован вdatetime64[ns], он остаётся строковым (object). Нужно добавитьpd.to_datetimeдо обращения к.dt. - День недели извлекается через
df['start'].dt.day, хотя нужен день недели — используйтеdt.dayofweek. - Длительность в днях вычисляется через
df['duration'].dt.days, хотя этот атрибут возвращает только целую часть суток (часы теряются). Нужно использовать.dt.total_seconds() / 86400.
Каждая ошибка находится в отдельной строке. Исправьте все три.
Что нужно сделать
Исправьте три ошибки так, чтобы программа:
- Читала из
stdinцелое числоn— количество строк. - Читала следующие
nстрок; каждая содержит пять токенов через пробел:id, дату начала, время начала, дату конца и время конца. Формат:id YYYY-MM-DD HH:MM YYYY-MM-DD HH:MM— итого 5 токенов; токены[1]+[2]образуютstart, токены[3]+[4]—end. - Строила
pd.DataFrameсо столбцами'id','start'(str),'end'(str). - Преобразовывала
'start'и'end'вdatetime64[ns]черезpd.to_datetime. - Создавала столбец
'month'черезdf['start'].dt.month. - Создавала столбец
'weekday'черезdf['start'].dt.dayofweek. - Вычисляла столбец
'duration'=df['end'] - df['start']и столбец'days'=df['duration'].dt.total_seconds() / 86400. - Для каждой строки выводила:
<id> <month> <weekday> <days>, гдеdays— с округлением до 4 знаков.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n id YYYY-MM-DD HH:MM YYYY-MM-DD HH:MM ...
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
<id> <month> <weekday> <days>
...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
Архив заказов: год, месяц, день недели и фильтрация по месяцу
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая обрабатывает архив заказов:
- Читает из
stdinцелое числоn— количество строк архива. - Читает следующие
nстрок; каждая содержит два токена через пробел:order_id(целое число) иorder_date(строка видаYYYY-MM-DD). Пример строки:101 2024-03-15. - Создаёт
pd.DataFrameс именемdfсо столбцами'order_id'(int) и'order_date'(str). - Преобразует столбец
'order_date'вdatetime64[ns]черезpd.to_datetime. Сохраняет обратно вdf['order_date']. - Создаёт столбец
'year'черезdf['order_date'].dt.year, столбец'month'черезdf['order_date'].dt.month, столбец'weekday'черезdf['order_date'].dt.dayofweek. - Читает из
stdinцелое числоtarget_month— номер месяца для фильтрации (от 1 до 12). - Фильтрует строки, где
month == target_month, черезdf.loc[mask]. Сохраняет вfiltered. - Выводит в
stdoutровноlen(filtered) + 2строк:
- Строки 1…
len(filtered): для каждой строкиfiltered(в порядке исходного DataFrame) одну строку вида<order_id> <year> <month> <weekday>. - Предпоследняя строка:
count=<len(filtered)>. - Последняя строка:
weekdays=<список>, где список — значенияweekdayотфильтрованных строк через запятую и пробел, в порядке исходного DataFrame. Еслиfilteredпуст — выведитеweekdays=.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n order_id YYYY-MM-DD ... target_month
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
<order_id> <year> <month> <weekday>
...
count=<k>
weekdays=<w1>, <w2>, ...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
Бронирование переговорных: длительность в часах и фильтрация по дате
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая обрабатывает журнал бронирования переговорных комнат:
- Читает из
stdinцелое числоn— количество записей. - Читает следующие
nстрок. Каждая строка содержит пять токенов через пробел:
booking_id(целое число)- дата начала
YYYY-MM-DD - время начала
HH:MM - дата конца
YYYY-MM-DD - время конца
HH:MM
Объедините токены [1] и [2] через пробел — это строка start; токены [3] и [4] — строка end.
- Создаёт
pd.DataFrameс именемdfсо столбцами'booking_id'(int),'start'(str),'end'(str). - Преобразует оба строковых столбца в
datetime64[ns]черезpd.to_datetime. - Создаёт столбец
'start_day'черезdf['start'].dt.day, столбец'start_hour'черезdf['start'].dt.hour. - Вычисляет столбец
'duration_h'— длительность бронирования в часах как(df['end'] - df['start']).dt.total_seconds() / 3600. Значения могут быть дробными. - Читает из
stdinстроку с двумя токенами через пробел:date_fromиdate_to— границы фильтрации (строки видаYYYY-MM-DD). - Фильтрует строки, где
start >= date_fromиstart < date_to, черезdf.loc[mask]. Сохраняет результат вfiltered. - Выводит в
stdoutровноlen(filtered) + 2строк:
- Строки 1…
len(filtered): для каждой строкиfiltered(в порядке исходного DataFrame) одну строку вида<booking_id> <start_day> <start_hour> <duration_h>, гдеduration_hокруглён до 2 знаков после запятой. - Предпоследняя строка:
count=<len(filtered)>. - Последняя строка:
total_hours=<значение>— суммаduration_hпо отфильтрованным строкам, округлённая до 2 знаков; еслиfilteredпуст — выведитеtotal_hours=0.00.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n booking_id YYYY-MM-DD HH:MM YYYY-MM-DD HH:MM ... date_from date_to
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
<booking_id> <start_day> <start_hour> <duration_h>
...
count=<k>
total_hours=<s>
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- Временные столбцы: парсинг, .dt-компоненты, длительности и фильтрация — задача по NumPy и pandas
Практическая задача по теме «Временные столбцы: парсинг, .dt-компоненты, длительности и фильтрация» с проверкой ответа онлайн.
- pandas dt: год, месяц, день недели и фильтрация дат
Как извлечь year, month и weekday из дат pandas? Преобразование строк в даты, пример фильтра и задача с проверкой.
- pandas Timedelta: длительность бронирования и фильтр по дате
Рассчитайте длительность бронирования в часах и отберите строки по дате через pandas datetime и Timedelta.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
