在具有 start/end 日期范围和随机参考日期的 pandas df 上执行过去 x 个月的聚合
Perform an aggregation over the past x months on a pandas df with start/end date ranges and a random reference date
我有一个 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
我想在每个 ref_date 之前计算过去 x 个月(x=1、3、6、12)每个用户的一些关键数据,例子是:
- 前 x 个月状态为 S1、S2、S3 的天数
ref_date
- 在
ref_date
之前的最后 x 个月内得分增加的次数
ref_date
前最后x个月的平均每日得分
结果应该是这样的(希望我计算正确):
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
之前结束,在这种情况下时间 "starts" 第一个 start_date
。重采样有效,但如果拥有数百万用户和多个日期范围,则会创建巨大的数据帧;我运行 内存不足。有什么建议吗?
需要注意的细节:在之前 ref_date
意味着直到并包括 ref_date-1
我会首先计算真正的开始和结束日期,分别是 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
我有一个 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
我想在每个 ref_date 之前计算过去 x 个月(x=1、3、6、12)每个用户的一些关键数据,例子是:
- 前 x 个月状态为 S1、S2、S3 的天数
ref_date
- 在
ref_date
之前的最后 x 个月内得分增加的次数
ref_date
前最后x个月的平均每日得分
结果应该是这样的(希望我计算正确):
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
之前结束,在这种情况下时间 "starts" 第一个 start_date
。重采样有效,但如果拥有数百万用户和多个日期范围,则会创建巨大的数据帧;我运行 内存不足。有什么建议吗?
需要注意的细节:在之前 ref_date
意味着直到并包括 ref_date-1
我会首先计算真正的开始和结束日期,分别是 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