Python Pandas Sum Значения в столбцах Если дата между 2 датами
У меня есть датафрейм df
который может быть создан с этим:
data={'id':[1,1,1,1,2,2,2,2],
'date1':[datetime.date(2016,1,1),datetime.date(2016,1,2),datetime.date(2016,1,3),datetime.date(2016,1,4),
datetime.date(2016,1,2),datetime.date(2016,1,4),datetime.date(2016,1,3),datetime.date(2016,1,1)],
'date2':[datetime.date(2016,1,5),datetime.date(2016,1,3),datetime.date(2016,1,5),datetime.date(2016,1,5),
datetime.date(2016,1,4),datetime.date(2016,1,5),datetime.date(2016,1,4),datetime.date(2016,1,1)],
'score1':[5,7,3,2,9,3,8,3],
'score2':[1,3,0,5,2,20,7,7]}
df=pd.DataFrame.from_dict(data)
And looks like this:
id date1 date2 score1 score2
0 1 2016-01-01 2016-01-05 5 1
1 1 2016-01-02 2016-01-03 7 3
2 1 2016-01-03 2016-01-05 3 0
3 1 2016-01-04 2016-01-05 2 5
4 2 2016-01-02 2016-01-04 9 2
5 2 2016-01-04 2016-01-05 3 20
6 2 2016-01-03 2016-01-04 8 7
7 2 2016-01-01 2016-01-01 3 7
Что мне нужно сделать, это создать столбец для каждого из score1
а также score2
, который создает два столбца, которые суммируют значения score1
а также score2
соответственно в зависимости от того usedate
находится между date1
а также date2
, usedate
создается путем получения всех дат между и в том числе date1
минимум и date2
максимум. Я использовал это, чтобы создать диапазон дат:
drange=pd.date_range(df.date1.min(),df.date2.max())
Результирующий кадр данных newdf
должен выглядеть так:
usedate score1sum score2sum
0 2016-01-01 8 8
1 2016-01-02 21 6
2 2016-01-03 32 13
3 2016-01-04 30 35
4 2016-01-05 13 26
Для уточнения, на usedate
2016-01-01, score1sum
8, который рассчитывается путем просмотра строк в df
где 2016-01-01 между и в том числе date1
а также date2
, которые суммируют row0(5) и row8(3). На usedate
2016-01-04, score2sum
35, который рассчитывается путем просмотра строк в df
где 2016-01-04 между и в том числе date1
а также date2
, которые суммируют row0(1), row3(0), row4(5), row5(2), row6(20), row7(7).
Может быть какой-то groupby
, или же melt
затем groupby
?
3 ответа
Ты можешь использовать apply
с лямбда-функцией:
df['date1'] = pd.to_datetime(df['date1'])
df['date2'] = pd.to_datetime(df['date2'])
df1 = pd.DataFrame(index=pd.date_range(df.date1.min(), df.date2.max()), columns = ['score1sum', 'score2sum'])
df1[['score1sum','score2sum']] = df1.apply(lambda x: df.loc[(df.date1 <= x.name) &
(x.name <= df.date2),
['score1','score2']].sum(), axis=1)
df1.rename_axis('usedate').reset_index()
Выход:
usedate score1sum score2sum
0 2016-01-01 8 8
1 2016-01-02 21 6
2 2016-01-03 32 13
3 2016-01-04 30 35
4 2016-01-05 13 26
Метод 1: список понимания
Это не элегантно, но эй, это работает! (РЕДАКТИРОВАТЬ: добавлен второй метод ниже.)
# Convert datetime.date to pandas timestamps for easier comparisons
df['date1'] = pd.to_datetime(df['date1'])
df['date2'] = pd.to_datetime(df['date2'])
# solution
newdf = pd.DataFrame(data=drange, columns=['usedate'])
# for each usedate ud, get all df rows whose dates contain ud,
# then sum the scores of these rows
newdf['score1sum'] = [df[(df['date1'] <= ud) & (df['date2'] >= ud)]['score1'].sum() for ud in drange]
newdf['score2sum'] = [df[(df['date1'] <= ud) & (df['date2'] >= ud)]['score2'].sum() for ud in drange]
# output
newdf
usedate score1sum score2sum
2016-01-01 8 8
2016-01-02 21 6
2016-01-03 32 13
2016-01-04 30 35
2016-01-05 13 26
Метод 2: вспомогательная функция с transform
(или же apply
)
newdf = pd.DataFrame(data=drange, columns=['usedate'])
def sum_scores(d):
return df[(df['date1'] <= d) & (df['date2'] >= d)][['score1', 'score2']].sum()
# apply works here too, and is about equally fast in my testing
newdf[['score1sum', 'score2sum']] = newdf['usedate'].transform(sum_scores)
# newdf is same to above
Сроки сопоставимы
# Jupyter timeit cell magic
%%timeit
newdf['score1sum'] = [df[(df['date1'] <= d) & (df['date2'] >= d)]['score1'].sum() for d in drange]
newdf['score1sum'] = [df[(df['date1'] <= d) & (df['date2'] >= d)]['score2'].sum() for d in drange]
100 loops, best of 3: 10.4 ms per loop
# Jupyter timeit line magic
%timeit newdf[['score1sum', 'score2sum']] = newdf['usedate'].transform(sum_scores)
100 loops, best of 3: 8.51 ms per loop
conditional_join из pyjanitor может быть полезен для абстракции / удобства:
# pip install pyjanitor
import pandas as pd
import janitor as jn
drange = pd.DataFrame(drange, columns=['dates'])
df['date1'] = pd.to_datetime(df['date1'])
df['date2'] = pd.to_datetime(df['date2'])
(drange.conditional_join(df,
('dates', 'date1', '>='),
('dates', 'date2', '<='))
.droplevel(0, 1)
.select_columns('dates', 'score*')
.groupby('dates')
.sum()
.add_suffix('num')
)
score1num score2num
dates
2016-01-01 8 8
2016-01-02 21 6
2016-01-03 32 13
2016-01-04 30 35
2016-01-05 13 26