Consider the following data frame:
import pandas as pd
df = pd.DataFrame({
"id": [0, 0, 0, 1, 1, 1, 1, 2, 2, 2],
"date": ["2017-01-31", "2017-02-28", "2017-03-31", "2017-01-31", "2017-03-31", "2017-04-30", "2017-05-31", "2017-01-31", "2017-03-31", "2017-05-31"],
"value": [10., 12., 15., 8., 11., 15., 17., 6., 14., 15.]
})
df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d")
I want to create monthly shifted value columns within each id group, where the monthly shifts are specified in a list and can also be negative (meaning past and future shifts should be allowed).
Desired result:
from pandas import Timestamp
from numpy import nan
data={
'id': {0: 0, 1: 0, 2: 0, 3: 1, 4: 1, 5: 1, 6: 1, 7: 2, 8: 2, 9: 2},
'date': {
0: Timestamp('2017-01-31 00:00:00'),
1: Timestamp('2017-02-28 00:00:00'),
2: Timestamp('2017-03-31 00:00:00'),
3: Timestamp('2017-01-31 00:00:00'),
4: Timestamp('2017-03-31 00:00:00'),
5: Timestamp('2017-04-30 00:00:00'),
6: Timestamp('2017-05-31 00:00:00'),
7: Timestamp('2017-01-31 00:00:00'),
8: Timestamp('2017-03-31 00:00:00'),
9: Timestamp('2017-05-31 00:00:00')
},
'value': {0: 10.0, 1: 12.0, 2: 15.0, 3: 8.0, 4: 11.0, 5: 15.0, 6: 17.0, 7: 6.0, 8: 14.0, 9: 15.0},
'value_1': {0: 12.0, 1: 15.0, 2: nan, 3: nan, 4: 15.0, 5: 17.0, 6: nan, 7: nan, 8: nan, 9: nan},
'value_2': {0: 15.0, 1: nan, 2: nan, 3: 11.0, 4: 17.0, 5: nan, 6: nan, 7: 14.0, 8: 15.0, 9: nan}
}
df = pd.DataFrame(data=data)
id date value value_1 value_2
0 0 2017-01-31 10.0 12.0 15.0
1 0 2017-02-28 12.0 15.0 NaN
2 0 2017-03-31 15.0 NaN NaN
3 1 2017-01-31 8.0 NaN 11.0
4 1 2017-03-31 11.0 15.0 17.0
5 1 2017-04-30 15.0 17.0 NaN
6 1 2017-05-31 17.0 NaN NaN
7 2 2017-01-31 6.0 NaN 14.0
8 2 2017-03-31 14.0 NaN 15.0
9 2 2017-05-31 15.0 NaN NaN
In the data frame above, the columns value_1 and value_2 shall be created.
My approach so far:
from pandas.tseries.offsets import MonthEnd
shifts = [1, 2]
tmp = df.copy()
for shift in shifts:
tmp_shifed = tmp.rename(columns={"value": f"value_{shift}"}).assign(date=df["date"] + MonthEnd(-1 * shift))
df = df.merge(tmp_shifed, on=["id", "date"], how="left")
It works but I am sure there is a better way to achieve this. Note that my data frame is quite large and that the shift list has the size of 7.
Any help is appreciated!