What's the right way to perform a groupby + rolling aggregate operation in polars? For some reason performing an ewm_mean over a rolling groupby gives me the list of all the ewm's rolling by time. For example take the dataframe below:
shape: (10, 3)
┌─────────────────────┬────────┬───────────┐
│ ts ┆ symbol ┆ signal_0 │
│ --- ┆ --- ┆ --- │
│ datetime[ns] ┆ cat ┆ f64 │
╞═════════════════════╪════════╪═══════════╡
│ 2022-02-14 09:20:00 ┆ A ┆ -1.704301 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2022-02-14 09:20:00 ┆ AA ┆ -1.181743 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2022-02-14 09:50:00 ┆ A ┆ 1.040125 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2022-02-14 09:50:00 ┆ AA ┆ 0.776798 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ ... ┆ ... ┆ ... │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2022-02-14 11:20:00 ┆ A ┆ 2.088835 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2022-02-14 11:20:00 ┆ AA ┆ 1.741544 │
└─────────────────────┴────────┴───────────┘
Here, I want to group by symbol and get the rolling mean for signal_0 at every timestamp. Unfortunately this doesn't work:
portfolios.groupby_rolling("ts", by="symbol", period="1d").agg(
pl.col("signal_0").ewm_mean(half_life=0.1).alias(f"signal_0_mean")
)
^The above yields:
shape: (10, 3)
┌────────┬─────────────────────┬─────────────────────────────────────┐
│ symbol ┆ ts ┆ signal_0_mean │
│ --- ┆ --- ┆ --- │
│ cat ┆ datetime[ns] ┆ list[f64] │
╞════════╪═════════════════════╪═════════════════════════════════════╡
│ A ┆ 2022-02-14 09:20:00 ┆ [-1.704301] │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ A ┆ 2022-02-14 09:50:00 ┆ [-1.704301, 1.037447] │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ A ┆ 2022-02-14 10:20:00 ┆ [-1.704301, 1.037447, 1.933809] │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ A ┆ 2022-02-14 10:50:00 ┆ [-1.704301, 1.037447, ... 2.0732... │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ... ┆ ... ┆ ... │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ AA ┆ 2022-02-14 10:50:00 ┆ [-1.181743, 0.774887, ... 1.6235... │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ AA ┆ 2022-02-14 11:20:00 ┆ [-1.181743, 0.774887, ... 1.7414... │
└────────┴─────────────────────┴─────────────────────────────────────┘
Here is a mock polars dataframe to reproduce the issue:
pl.DataFrame([{'ts': datetime.datetime(2022, 2, 14, 9, 20),
'symbol': 'A',
'signal_0': -1.7043013917942946},
{'ts': datetime.datetime(2022, 2, 14, 9, 20),
'symbol': 'AA',
'signal_0': -1.1817425250668494},
{'ts': datetime.datetime(2022, 2, 14, 9, 50),
'symbol': 'A',
'signal_0': 1.040124717732041},
{'ts': datetime.datetime(2022, 2, 14, 9, 50),
'symbol': 'AA',
'signal_0': 0.776797510847028},
{'ts': datetime.datetime(2022, 2, 14, 10, 20),
'symbol': 'A',
'signal_0': 1.934685674908703},
{'ts': datetime.datetime(2022, 2, 14, 10, 20),
'symbol': 'AA',
'signal_0': 1.480892267491577},
{'ts': datetime.datetime(2022, 2, 14, 10, 50),
'symbol': 'A',
'signal_0': 2.0734176892015395},
{'ts': datetime.datetime(2022, 2, 14, 10, 50),
'symbol': 'AA',
'signal_0': 1.6236975034431897},
{'ts': datetime.datetime(2022, 2, 14, 11, 20),
'symbol': 'A',
'signal_0': 2.0888349547293954},
{'ts': datetime.datetime(2022, 2, 14, 11, 20),
'symbol': 'AA',
'signal_0': 1.741544172734057}])
If I wanted to do this in pandas, I would write:
df.set_index(["ts", "symbol"]).groupby(level=1)["signal_0"].transform(
lambda x: x.ewm(halflife=10).mean()
)
Which would yield:
ts symbol
2022-02-14 09:20:00 A -1.704301
AA -1.181743
2022-02-14 09:50:00 A -0.284550
AA -0.168547
2022-02-14 10:20:00 A 0.507021
AA 0.419785
2022-02-14 10:50:00 A 0.940226
AA 0.752741
2022-02-14 11:20:00 A 1.202843
AA 0.978820
Name: signal_0, dtype: float64