Выполните агрегацию за последние x месяцев на pandas df с диапазонами дат начала / окончания и случайной контрольной датой. - PullRequest
0 голосов
/ 19 апреля 2020

У меня есть pandas фрейм данных df с непрерывными диапазонами start_date и end_date и по одному ref_date для каждого пользователя:

users = {'user_id': ['A','A','A','A', 'B','B','B'],
         'start_date': ['2017-03-07', '2017-03-12', '2017-04-04', '2017-05-22', '2018-12-01', '2018-12-23', '2018-12-29'],
         'end_date': ['2017-03-11', '2017-04-03', '2017-05-21', '2222-12-31', '2018-12-22', '2018-12-28', '2222-12-31'],
         'status': ['S1', 'S2', 'S1', 'S3', 'S1', 'S2', 'S1'],
         'score': [1000, 1000, 1000, 1000, 900, 900, 1500],
         'ref_date': ['2017-05-22', '2017-05-22', '2017-05-22', '2017-05-22', '2019-01-19', '2019-01-19', '2019-01-19']
        }

df = pd.DataFrame(users, columns = ['user_id', 'start_date', 'end_date', 'status', 'score', 'ref_date'])
print(df)

  user_id  start_date    end_date status  score    ref_date
0       A  2017-03-07  2017-03-11     S1   1000  2017-05-22
1       A  2017-03-12  2017-04-03     S2   1000  2017-05-22
2       A  2017-04-04  2017-05-21     S1   1000  2017-05-22
3       A  2017-05-22  2222-12-31     S3   1000  2017-05-22
4       B  2018-12-01  2018-12-22     S1    900  2019-01-19
5       B  2018-12-23  2018-12-28     S2    900  2019-01-19
6       B  2018-12-29  2222-12-31     S1   1500  2019-01-19

Я хотел бы рассчитать количество показателей на пользователя за последние x месяцев (x = 1, 3, 6, 12) перед каждой ref_date , примеры:

  • количество дней с статус S1, S2, S3 за последние x месяцев до ref_date
  • увеличение количества баллов за последние x месяцев до ref_date
  • среднесуточный балл за последние x месяцев до ref_date

Результат должен выглядеть следующим образом (надеюсь, я правильно сделал вычисления):

user_id    ref_date  nday_s1_last3m  nday_s2_last3m  nday_s3_last3m  \
0       A  2017-05-22            53              23               0   
1       B  2019-01-19            43               6               0   

   ninc_score_last3m  avg_score_last3m  
0                  0           1000.00  
1                  1           1157.14  

Проблема в том, что ref_date - x месяцев может закончиться между существующим интервалом start_date / end_date или даже перед первым start_date, в этом случае время «начинается» с первого start_date. Повторная выборка работает, но создает огромные кадры данных, если один имеет миллионы пользователей и много диапазонов дат; У меня не хватает памяти. Любые предложения?

Примечание: до a ref_date означает до ref_date-1

включительно

1 Ответ

1 голос
/ 20 апреля 2020

Сначала я вычислил бы действительные даты начала и окончания, соответственно соответственно старшие и нижние значения start_date и ref_date минус 3 месяца, а также end_date и ref_date. Как только это будет сделано, число дней, увеличение баллов и среднее будет легко вычислить:

Код может быть:

# convert date columns to datetimes
for col in ['start_date', 'end_date', 'ref_date']:
    df[col] = pd.to_datetime(df[col])

# compute ref_date minus 3 months
ref = df.ref_date - pd.offsets.MonthOffset(3)

# compute the real start and end dates
tmp = df.loc[(df.end_date >= ref)&(df.start_date < df.ref_date),
             ['start_date', 'end_date']].copy()
tmp.loc[df.start_date < ref, 'start_date'] = ref-pd.Timedelta('1D')
tmp.loc[df.end_date >= df.ref_date, 'end_date'] = df.ref_date-pd.Timedelta('1D')

# add the relevant columns to the temp dataframe
tmp['days'] = (tmp.end_date - tmp.start_date).dt.days + 1
tmp['score'] = df.score
tmp['status'] = df.status

# build a list of result fields per user
data =[]
for i in df.user_id.unique():
    # user_id, ref_date
    d = [i, df.loc[df.user_id == i, 'ref_date'].iat[0]]
    data.append(d)
    # extract data for that user
    x = tmp[df.loc[tmp.index,'user_id'] == i]
    # number of days per status
    d.extend(x.groupby('status')['days'].sum().reindex(df.status.unique())
          .fillna(0).astype('int').tolist())
    # increase and average score
    d.extend((np.sum(np.where(x.score > x.score.shift(), 1, 0)),
          np.average(x.score, weights=x.days)))


# build the resulting dataframe
resul = pd.DataFrame(data, columns=['user_id', 'ref_date', 'nday_s1_last3m',
                                    'nday_s2_last3m', 'nday_s3_last3m',
                                    'ninc_score_last3m', 'avg_score_last3m'])

Это дает, как и ожидалось:

  user_id   ref_date  nday_s1_last3m  nday_s2_last3m  nday_s3_last3m  ninc_score_last3m  avg_score_last3m
0       A 2017-05-22              53              23               0                  0       1000.000000
1       B 2019-01-19              43               6               0                  1       1157.142857
...