Я пытался реализовать скользящую автокорреляцию в полярах, но получил некоторые странные результаты, когда в них участвуют null
.
Код довольно прост. Допустим, у меня есть два фрейма данных df1
и df2
:
df1 = pl.DataFrame({'a': [1.06, 1.07, 0.93, 0.78, 0.85], 'lag_a': [1., 1.06, 1.07,
0.93, 0.78]})
df2 = pl.DataFrame({'a': [1., 1.06, 1.07, 0.93, 0.78, 0.85], 'lag_a': [None, 1., 1.06, 1.07, 0.93, 0.78]})
Вы можете видеть, что единственная разница в том, что в df2
первая строка для lag_a
имеет значение None, поскольку она сдвинута с a
.
Однако когда я вычислил rolling_corr
для обоих фреймов данных, я получил разные результаты.
# df1.select(pl.rolling_corr('a', 'lag_a', window_size=10, min_periods=5, ddof=1))
shape: (5, 1)
┌──────────┐
│ a │
│ --- │
│ f64 │
╞══════════╡
│ null │
│ null │
│ null │
│ null │
│ 0.622047 │
└──────────┘
# df2.select(pl.rolling_corr('a', 'lag_a', window_size=10, min_periods=5, ddof=1))
shape: (6, 1)
┌───────────┐
│ a │
│ --- │
│ f64 │
╞═══════════╡
│ null │
│ null │
│ null │
│ null │
│ null │
│ -0.219851 │
└───────────┘
Результат df1
, то есть 0,622047, я тоже получил от numpy.corrcoef
. Интересно, откуда взялось -0,219851?
Я думаю, что это ошибка в реализации rolling_corr на Rust (справедливости ради, в питоне он помечен как нестабильный). Похоже, оно наивно применяется rolling_mean
, не нанеся предварительно нулевую маску суставов. Таким образом, скользящее среднее a
, используемое в вычислениях, равно
df2.get_column("a").rolling_mean(window_size=10, min_periods=5)
shape: (6,)
Series: 'a' [f64]
[
null
null
null
null
0.968
0.948333
]
Это правильное скользящее среднее в вакууме, но в этом случае первая строка столбца df2
a
должна считаться нулевой, потому что lag_a
там нулевое, и поэтому скользящее среднее df2
должно быть таким же, как скользящее среднее df1
, с дополнительным нулем впереди.
df1.get_column("a").rolling_mean(window_size=10, min_periods=5)
shape: (5,)
Series: 'a' [f64]
[
null
null
null
null
0.938
]
Я бы предложил подать отчет об ошибке или даже PR. Это не похоже на жесткое исправление, просто потребуется предварительно вычислить маску и применить фильтры ко всем выражениям, прежде чем рассчитывать по ним скользящую статистику.
А пока вы можете применить маску самостоятельно перед вычислением корреляции:
df2.with_columns(
pl.when(pl.any_horizontal(pl.all().is_null()))
.then(None)
.otherwise(pl.all())
.name.keep()
).select(pl.rolling_corr("a", "lag_a", window_size=10, min_periods=5))
shape: (6, 1)
┌──────────┐
│ a │
│ --- │
│ f64 │
╞══════════╡
│ null │
│ null │
│ null │
│ null │
│ null │
│ 0.622047 │
└──────────┘
Большое спасибо @BallpointBen. Опубликую это на GitHub.