#python #pandas #datetime
#python #pandas #datetime
Вопрос:
Я хочу рассчитать количество дней из последовательных периодов.
В df ниже у меня есть четыре столбца:
- идентификатор; представляющий человека.
- период; число, где наименьшее значение соответствует первому периоду, а наибольшее — последнему.
- in_date; дата начала периода.
- out_date; дата окончания периода.
Я хочу создать универсальную функцию, которая выполняет следующее:
- подсчитайте количество дней последовательных периодов. Два периода считаются последовательными, если количество дней между ними меньше 90.
- Я хочу только рассчитать количество дней, если последний период для идентификатора имеет out_date в 2013 году. Если последний период имеет значение ‘out_date’ 2014 или 2012, я хочу игнорировать этот идентификатор.
- Я хочу включить дни между периодами в переменную результата.
Моя проблема в том, что я довольно новичок в Python, я не могу придумать хорошую идею, как рассчитать дни между периодами и классифицировать последовательный период. Любая помощь будет высоко оценена.
import pandas as pd
import numpy as np
import datetime
data = {'id':[1, 1, 1, 2, 2, 2, 2, 3, 3, 3],
'period':[1, 2, 3, 1, 3, 5, 6, 2, 3, 4],
'in_date': ['2011-02-15','2011-11-10','2012-10-13',
'2010-04-03','2012-02-17','2012-08-15','2014-01-04','2010-06-01','2012-03-29','2012-09-12'],
'out_date': ['2011-05-21','2012-10-11','2013-10-25',
'2012-02-16','2012-02-19','2013-11-23','2014-12-18','2011-08-21','2012-09-11','2013-01-10']}
df = pd.DataFrame(data)
df['in_date'] = pd.to_datetime(df['in_date'])
df['out_date'] = pd.to_datetime(df['out_date'])
df['n_days'] = df['out_date'] - df['in_date']
Ожидаемый результат:
Комментарии:
1. Я опубликовал ответ, но отмечу, что я не получил 745 для первого идентификатора, когда писал для него код, поэтому я либо неправильно понял, либо это значение неверно указано выше.
2. @Rick M — это была моя ошибка. Большое спасибо за ваши усилия! Выглядит нормально! Сегодня я попробую ваше решение на своем реальном наборе данных.
Ответ №1:
Сначала преобразуйте n_days
в числовое значение и убедитесь, что df отсортирован:
df['n_days'] = (df['out_date'] - df['in_date']).dt.days
df = df.sort_values(['id','period'])
Добавьте столбец, подсчитывающий дни между периодами:
df['days_since_last'] = (df['in_date'] - df['out_date'].shift(1)).dt.days
… и убедитесь, что эти значения не пересекаются между разными id
значениями:
id_changed = (df['id'].shift(1) != df['id'])
df.loc[id_changed, 'days_since_last'] = np.nan
Определите условие, которое отмечает, где количество дней между ними слишком велико:
days_cut = (df['days_since_last'] >= 90)
Возьмите подмножество фрейма данных, где это либо новый id
, либо действительный последовательный цикл дней. Присвойте каждому из этих допустимых запусков уникальное значение ‘run’ (будет использоваться для группировки позже):
tmp = df[days_cut | id_changed ].copy()
tmp['run'] = range(len(tmp))
Объедините это обратно в основной фрейм данных и заполните run
вперед, чтобы он показывал, где находятся допустимые прогоны последовательных периодов:
df = pd.merge(df, tmp[['id','period','run']], on=['id','period'], how='left')
df['run'] = df['run'].fillna(method='ffill')
Вот как это выглядит на данный момент. Вы можете видеть, что для каждого id
из них существуют непрерывные циклы run
значений:
print(df)
id period in_date out_date n_days days_since_last run
0 1 1 2011-02-15 2011-05-21 95 NaN 0.0
1 1 2 2011-11-10 2012-10-11 336 173.0 1.0
2 1 3 2012-10-13 2013-10-25 377 2.0 1.0
3 2 1 2010-04-03 2012-02-16 684 NaN 2.0
4 2 3 2012-02-17 2012-02-19 2 1.0 2.0
5 2 5 2012-08-15 2013-11-23 465 178.0 3.0
6 2 6 2014-01-04 2014-12-18 348 42.0 3.0
7 3 2 2010-06-01 2011-08-21 446 NaN 4.0
8 3 3 2012-03-29 2012-09-11 166 221.0 5.0
9 3 4 2012-09-12 2013-01-10 120 1.0 5.0
Извлеките последовательные дни для каждого run
, суммируя n_days
столбец. .agg
Также отслеживается максимальная дата в прогоне, поэтому мы можем сохранить только те прогоны, которые заканчиваются в 2013 году:
consecutive_days = df.groupby(['id','run']).agg( {'n_days' : np.sum, 'out_date' : np.max } )
consecutive_days = consecutive_days[(consecutive_days['out_date'].dt.year == 2013)]
consecutive_days = consecutive_days.drop(columns=['out_date']).rename(columns={'n_days' : 'consecutive_days'})
Наконец, объедините это обратно в исходный фрейм данных и удалите лишние столбцы:
df = pd.merge(df, consecutive_days, on='id', how='left')
df = df.drop(columns=['days_since_last','run'])
print(df)
id period in_date out_date n_days consecutive_days
0 1 1 2011-02-15 2011-05-21 95 713.0
1 1 2 2011-11-10 2012-10-11 336 713.0
2 1 3 2012-10-13 2013-10-25 377 713.0
3 2 1 2010-04-03 2012-02-16 684 NaN
4 2 3 2012-02-17 2012-02-19 2 NaN
5 2 5 2012-08-15 2013-11-23 465 NaN
6 2 6 2014-01-04 2014-12-18 348 NaN
7 3 2 2010-06-01 2011-08-21 446 286.0
8 3 3 2012-03-29 2012-09-11 166 286.0
9 3 4 2012-09-12 2013-01-10 120 286.0
Комментарии:
1. Одним из упрощений может быть вначале отфильтровать те, у которых out_date=2013. df[‘last_value’] =df.groupby(‘id’)[‘in_date’].transform(‘last’), а затем df = df[df[‘last_value]=2013]