Подсчитывает количество дней из последовательных временных периодов (Python, datetime)

#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]