%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.11%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22marimo%3D%3D0.20.4%22%2C%0A%23%20%20%20%20%20%22basanos%22%2C%0A%23%20%20%20%20%20%22numpy%3E%3D2.0.0%22%2C%0A%23%20%20%20%20%20%22polars%3E%3D1.0.0%22%2C%0A%23%20%20%20%20%20%22pandas%3E%3D2.0.0%22%2C%0A%23%20%20%20%20%20%22pyarrow%3E%3D12.0.0%22%2C%0A%23%20%20%20%20%20%22plotly%3E%3D6.0.0%22%2C%0A%23%20%20%20%20%20%22scipy%3E%3D1.0.0%22%2C%0A%23%20%5D%0A%23%20%5Btool.uv.sources%5D%0A%23%20basanos%20%3D%20%7B%20path%20%3D%20%22..%2F..%2F..%22%2C%20editable%20%3D%20true%20%7D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.20.4%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0Awith%20app.setup%3A%0A%20%20%20%20import%20timeit%0A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20plotly.graph_objects%20as%20go%0A%20%20%20%20import%20polars%20as%20pl%0A%20%20%20%20from%20plotly.subplots%20import%20make_subplots%0A%0A%20%20%20%20from%20basanos.math.optimizer%20import%20_ewm_corr_numpy%20as%20ewm_corr_numpy%0A%0A%20%20%20%20def%20ewm_corr_pandas(data%3A%20np.ndarray%2C%20com%3A%20int%2C%20min_periods%3A%20int)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%22%22%22Original%20EWM%20correlation%20via%20pandas%20(pre-migration%20implementation).%0A%0A%20%20%20%20%20%20%20%20Converts%20the%20numpy%20input%20to%20a%20pandas%20DataFrame%2C%20uses%20pandas'%20built-in%0A%20%20%20%20%20%20%20%20%60%60ewm().corr()%60%60%20and%20reshapes%20the%20MultiIndex%20result%20back%20to%20a%203-D%0A%20%20%20%20%20%20%20%20NumPy%20array%20of%20shape%20%60%60(T%2C%20N%2C%20N)%60%60.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20t_len%2C%20n_assets%20%3D%20data.shape%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.DataFrame(data)%0A%20%20%20%20%20%20%20%20ewm_result%20%3D%20df.ewm(com%3Dcom%2C%20min_periods%3Dmin_periods).corr()%0A%20%20%20%20%20%20%20%20cor%20%3D%20ewm_result.reset_index(names%3D%5B%22t%22%2C%20%22asset%22%5D)%0A%20%20%20%20%20%20%20%20result%20%3D%20np.full((t_len%2C%20n_assets%2C%20n_assets)%2C%20np.nan)%0A%20%20%20%20%20%20%20%20for%20t%2C%20df_t%20in%20cor.groupby(%22t%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%5Bint(t)%5D%20%3D%20df_t.drop(columns%3D%5B%22t%22%2C%20%22asset%22%5D).to_numpy()%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%0A%40app.cell%0Adef%20cell_01()%3A%0A%20%20%20%20%22%22%22Render%20the%20notebook%20title%20and%20introduction.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%F0%9F%94%AC%20EWM%20Correlation%3A%20pandas%20vs%20NumPy%0A%0A%20%20%20%20%20%20%20%20%60pandas%60%20and%20%60pyarrow%60%20were%20removed%20as%20project%20dependencies.%20%20The%20only%0A%20%20%20%20%20%20%20%20code%20that%20needed%20them%20was%20the%20**EWM%20correlation**%20step%20inside%0A%20%20%20%20%20%20%20%20%60BasanosEngine.cor%60%2C%20which%20called%0A%20%20%20%20%20%20%20%20%60pandas.DataFrame.ewm(com%3D%E2%80%A6).corr()%60.%0A%0A%20%20%20%20%20%20%20%20This%20notebook%20answers%20two%20questions%20about%20the%20replacement%3A%0A%0A%20%20%20%20%20%20%20%20%7C%20Question%20%7C%20What%20we%20measure%20%7C%0A%20%20%20%20%20%20%20%20%7C----------%7C-----------------%7C%0A%20%20%20%20%20%20%20%20%7C%20**Correct%3F**%20%7C%20Do%20both%20produce%20the%20same%20numbers%20(within%201%20%C3%97%2010%E2%81%BB%C2%B9%E2%81%B0)%3F%20%7C%0A%20%20%20%20%20%20%20%20%7C%20**Fast%3F**%20%7C%20Is%20the%20NumPy%20version%20at%20least%20as%20fast%20as%20pandas%3F%20%7C%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_02()%3A%0A%20%20%20%20%22%22%22Render%20a%20horizontal%20rule%20separator.%22%22%22%0A%20%20%20%20mo.md(%22---%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_03()%3A%0A%20%20%20%20%22%22%22Render%20side-by-side%20implementation%20listings%20for%20pandas%20and%20NumPy.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%93%90%20Implementations%0A%0A%20%20%20%20%20%20%20%20%23%23%23%20Original%20%E2%80%94%20%60pandas.DataFrame.ewm().corr()%60%0A%0A%20%20%20%20%20%20%20%20%60%60%60python%0A%20%20%20%20%20%20%20%20%23%20data%3A%20np.ndarray%20(T%2C%20N)%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.DataFrame(data)%0A%20%20%20%20%20%20%20%20ewm_result%20%3D%20df.ewm(com%3Dcom%2C%20min_periods%3Dmin_periods).corr()%0A%20%20%20%20%20%20%20%20cor%20%3D%20ewm_result.reset_index(names%3D%5B%22t%22%2C%20%22asset%22%5D)%0A%20%20%20%20%20%20%20%20result%20%3D%20np.full((T%2C%20N%2C%20N)%2C%20np.nan)%0A%20%20%20%20%20%20%20%20for%20t%2C%20df_t%20in%20cor.groupby(%22t%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result%5Bint(t)%5D%20%3D%20df_t.drop(columns%3D%5B%22t%22%2C%20%22asset%22%5D).to_numpy()%0A%20%20%20%20%20%20%20%20%60%60%60%0A%0A%20%20%20%20%20%20%20%20The%20pandas%20path%20calls%20its%20Cython-accelerated%20%60ewm().corr()%60%2C%20then%0A%20%20%20%20%20%20%20%20iterates%20over%20T%20timestamp%20groups%20to%20reassemble%20the%203-D%20result.%0A%20%20%20%20%20%20%20%20It%20also%20requires%20a%20**Polars%20%E2%86%92%20pandas%20conversion**%20(%60to_pandas()%60)%20when%0A%20%20%20%20%20%20%20%20called%20from%20%60BasanosEngine.cor%60%2C%20which%20needs%20%60pyarrow%60%20as%20a%20backend.%0A%0A%20%20%20%20%20%20%20%20%23%23%23%20New%20%E2%80%94%20%60scipy.signal.lfilter%60%20over%20all%20N%C2%B2%20pairs%0A%0A%20%20%20%20%20%20%20%20%60%60%60python%0A%20%20%20%20%20%20%20%20beta%20%3D%20com%20%2F%20(1.0%20%2B%20com)%0A%20%20%20%20%20%20%20%20joint_fin%20%3D%20fin%5B%3A%2C%20%3A%2C%20np.newaxis%5D%20%26%20fin%5B%3A%2C%20np.newaxis%2C%20%3A%5D%20%20%23%20(T%2C%20N%2C%20N)%0A%0A%20%20%20%20%20%20%20%20v_x%20%20%20%3D%20xt_f%5B%3A%2C%20%3A%2C%20np.newaxis%5D%20*%20joint_fin%20%20%20%23%20EWM%20input%20for%20x_i%0A%20%20%20%20%20%20%20%20v_x2%20%20%3D%20(xt_f**2)%5B%3A%2C%20%3A%2C%20np.newaxis%5D%20*%20joint_fin%0A%20%20%20%20%20%20%20%20v_xy%20%20%3D%20xt_f%5B%3A%2C%20%3A%2C%20np.newaxis%5D%20*%20xt_f%5B%3A%2C%20np.newaxis%2C%20%3A%5D%0A%20%20%20%20%20%20%20%20v_w%20%20%20%3D%20joint_fin.astype(float)%0A%0A%20%20%20%20%20%20%20%20filt_a%20%3D%20np.array(%5B1.0%2C%20-beta%5D)%0A%20%20%20%20%20%20%20%20s_x%20%20%3D%20lfilter(%5B1.0%5D%2C%20filt_a%2C%20v_x%2C%20%20axis%3D0)%20%20%23%20(T%2C%20N%2C%20N)%0A%20%20%20%20%20%20%20%20s_x2%20%3D%20lfilter(%5B1.0%5D%2C%20filt_a%2C%20v_x2%2C%20axis%3D0)%0A%20%20%20%20%20%20%20%20s_xy%20%3D%20lfilter(%5B1.0%5D%2C%20filt_a%2C%20v_xy%2C%20axis%3D0)%0A%20%20%20%20%20%20%20%20s_w%20%20%3D%20lfilter(%5B1.0%5D%2C%20filt_a%2C%20v_w%2C%20%20axis%3D0)%0A%20%20%20%20%20%20%20%20%60%60%60%0A%0A%20%20%20%20%20%20%20%20The%20recurrence%20%60s%5Bt%5D%20%3D%20%CE%B2%C2%B7s%5Bt%E2%88%921%5D%20%2B%20v%5Bt%5D%60%20is%20an%20**IIR%20filter**.%0A%20%20%20%20%20%20%20%20%60scipy.signal.lfilter%60%20solves%20it%20for%20every%20one%20of%20the%20N%C2%B2%20asset%20pairs%0A%20%20%20%20%20%20%20%20**simultaneously%20in%20C**%20%E2%80%94%20there%20is%20no%20Python%20loop%20over%20the%20T%20timesteps.%0A%20%20%20%20%20%20%20%20%60pandas%60%20and%20%60pyarrow%60%20are%20no%20longer%20needed.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_04()%3A%0A%20%20%20%20%22%22%22Render%20a%20horizontal%20rule%20separator.%22%22%22%0A%20%20%20%20mo.md(%22---%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_05()%3A%0A%20%20%20%20%22%22%22Render%20the%20correctness%20section%20header%20and%20instructions.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%E2%9C%85%20Correctness%20Check%0A%0A%20%20%20%20%20%20%20%20Adjust%20the%20sliders%20to%20pick%20the%20**number%20of%20assets**%20(N)%2C%20the%20**number%0A%20%20%20%20%20%20%20%20of%20time%20steps**%20(T)%2C%20and%20the%20**EWM%20centre-of-mass**%20(%60com%60).%20%20Around%0A%20%20%20%20%20%20%20%205%20%25%20of%20values%20are%20set%20to%20NaN%20to%20exercise%20the%20missing-data%20code%20path.%0A%20%20%20%20%20%20%20%20Both%20implementations%20run%20on%20the%20same%20data%20and%20their%20outputs%20are%0A%20%20%20%20%20%20%20%20compared%20element-wise.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_06()%3A%0A%20%20%20%20%22%22%22Create%20interactive%20sliders%20for%20N%2C%20T%2C%20and%20EWM%20com.%22%22%22%0A%20%20%20%20n_slider%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D2%2C%0A%20%20%20%20%20%20%20%20stop%3D20%2C%0A%20%20%20%20%20%20%20%20value%3D4%2C%0A%20%20%20%20%20%20%20%20step%3D1%2C%0A%20%20%20%20%20%20%20%20label%3D%22N%20(assets)%3A%22%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20t_slider%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D100%2C%0A%20%20%20%20%20%20%20%20stop%3D2000%2C%0A%20%20%20%20%20%20%20%20value%3D500%2C%0A%20%20%20%20%20%20%20%20step%3D100%2C%0A%20%20%20%20%20%20%20%20label%3D%22T%20(time%20steps)%3A%22%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20com_slider%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D5%2C%0A%20%20%20%20%20%20%20%20stop%3D64%2C%0A%20%20%20%20%20%20%20%20value%3D32%2C%0A%20%20%20%20%20%20%20%20step%3D1%2C%0A%20%20%20%20%20%20%20%20label%3D%22EWM%20com%3A%22%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20mo.vstack(%5Bn_slider%2C%20t_slider%2C%20com_slider%5D)%0A%20%20%20%20return%20com_slider%2C%20n_slider%2C%20t_slider%0A%0A%0A%40app.cell%0Adef%20cell_07(com_slider%2C%20n_slider%2C%20t_slider)%3A%0A%20%20%20%20%22%22%22Generate%20random%20test%20data%20with%20~5%20%25%20missing%20values%20from%20slider%20settings.%22%22%22%0A%20%20%20%20_rng%20%3D%20np.random.default_rng(42)%0A%20%20%20%20_data%20%3D%20_rng.normal(size%3D(t_slider.value%2C%20n_slider.value))%0A%20%20%20%20_data%5B_rng.random(_data.shape)%20%3C%200.05%5D%20%3D%20np.nan%20%20%23%20~5%20%25%20missing%0A%20%20%20%20test_data%20%3D%20_data%0A%20%20%20%20com%20%3D%20com_slider.value%0A%20%20%20%20return%20com%2C%20test_data%0A%0A%0A%40app.cell%0Adef%20cell_08(com%2C%20test_data)%3A%0A%20%20%20%20%22%22%22Run%20both%20implementations%20on%20the%20test%20data%20and%20return%20their%203-D%20tensors.%22%22%22%0A%20%20%20%20pd_result%20%3D%20ewm_corr_pandas(test_data%2C%20com%2C%20com)%0A%20%20%20%20np_result%20%3D%20ewm_corr_numpy(test_data%2C%20com%2C%20com)%0A%20%20%20%20return%20np_result%2C%20pd_result%0A%0A%0A%40app.cell%0Adef%20cell_09(np_result%2C%20pd_result)%3A%0A%20%20%20%20%22%22%22Display%20a%20correctness%20callout%20comparing%20pandas%20and%20NumPy%20outputs.%22%22%22%0A%20%20%20%20_both%20%3D%20np.isfinite(pd_result)%20%26%20np.isfinite(np_result)%0A%20%20%20%20_nan_match%20%3D%20bool(np.all(~np.isfinite(pd_result)%20%3D%3D%20~np.isfinite(np_result)))%0A%20%20%20%20_max_diff%20%3D%20float(np.abs(pd_result%5B_both%5D%20-%20np_result%5B_both%5D).max())%20if%20_both.any()%20else%200.0%0A%20%20%20%20_mean_diff%20%3D%20float(np.abs(pd_result%5B_both%5D%20-%20np_result%5B_both%5D).mean())%20if%20_both.any()%20else%200.0%0A%20%20%20%20_ok%20%3D%20_max_diff%20%3C%201e-10%20and%20_nan_match%0A%0A%20%20%20%20mo.callout(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%23%23%20Comparison%20result%20%E2%80%94%20%7B%22%E2%9C%85%20PASS%22%20if%20_ok%20else%20%22%E2%9D%8C%20FAIL%22%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Metric%20%7C%20Value%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C--------%7C-------%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Max%20absolute%20difference%20%7C%20%60%7B_max_diff%3A.2e%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Mean%20absolute%20difference%20%7C%20%60%7B_mean_diff%3A.2e%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20NaN%20patterns%20identical%20%7C%20%60%7B_nan_match%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Within%201%20%C3%97%2010%E2%81%BB%C2%B9%E2%81%B0%20tolerance%20%7C%20%7B%22%E2%9C%85%20yes%22%20if%20_max_diff%20%3C%201e-10%20else%20%22%E2%9D%8C%20no%22%7D%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20kind%3D%22success%22%20if%20_ok%20else%20%22danger%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_10(np_result%2C%20pd_result)%3A%0A%20%20%20%20%22%22%22Render%20side-by-side%20heatmaps%20of%20the%20final%20correlation%20matrices%20and%20their%20difference.%22%22%22%0A%20%20%20%20_t_len%2C%20_n_assets%20%3D%20pd_result.shape%5B%3A2%5D%0A%20%20%20%20_labels%20%3D%20%5Bf%22x%7Bi%7D%22%20for%20i%20in%20range(_n_assets)%5D%0A%20%20%20%20%23%20Pick%20the%20last%20timestep%20where%20at%20least%20one%20finite%20pair%20exists%0A%20%20%20%20_t%20%3D%20next(%0A%20%20%20%20%20%20%20%20(t%20for%20t%20in%20range(_t_len%20-%201%2C%20-1%2C%20-1)%20if%20np.any(np.isfinite(pd_result%5Bt%5D)))%2C%0A%20%20%20%20%20%20%20%20_t_len%20-%201%2C%0A%20%20%20%20)%0A%0A%20%20%20%20def%20_heatmap(mat%2C%20showscale%3DFalse)%3A%0A%20%20%20%20%20%20%20%20return%20go.Heatmap(%0A%20%20%20%20%20%20%20%20%20%20%20%20z%3Dnp.where(np.isfinite(mat)%2C%20mat%2C%20None).tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3D_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3D_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20colorscale%3D%22RdBu_r%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20zmin%3D-1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20zmax%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20showscale%3Dshowscale%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_fig%20%3D%20make_subplots(%0A%20%20%20%20%20%20%20%20rows%3D1%2C%0A%20%20%20%20%20%20%20%20cols%3D3%2C%0A%20%20%20%20%20%20%20%20subplot_titles%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22pandas%20%20(t%20%3D%20%7B_t%7D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22NumPy%20%20%20(t%20%3D%20%7B_t%7D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Absolute%20difference%22%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20column_widths%3D%5B0.37%2C%200.37%2C%200.26%5D%2C%0A%20%20%20%20)%0A%20%20%20%20_fig.add_trace(_heatmap(pd_result%5B_t%5D)%2C%20row%3D1%2C%20col%3D1)%0A%20%20%20%20_fig.add_trace(_heatmap(np_result%5B_t%5D%2C%20showscale%3DTrue)%2C%20row%3D1%2C%20col%3D2)%0A%20%20%20%20_abs%20%3D%20np.where(%0A%20%20%20%20%20%20%20%20np.isfinite(pd_result%5B_t%5D)%20%26%20np.isfinite(np_result%5B_t%5D)%2C%0A%20%20%20%20%20%20%20%20np.abs(pd_result%5B_t%5D%20-%20np_result%5B_t%5D)%2C%0A%20%20%20%20%20%20%20%20None%2C%0A%20%20%20%20)%0A%20%20%20%20_fig.add_trace(%0A%20%20%20%20%20%20%20%20go.Heatmap(%0A%20%20%20%20%20%20%20%20%20%20%20%20z%3D_abs.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3D_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3D_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20colorscale%3D%22Reds%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20zmin%3D0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20showscale%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20row%3D1%2C%0A%20%20%20%20%20%20%20%20col%3D3%2C%0A%20%20%20%20)%0A%20%20%20%20_fig.update_layout(%0A%20%20%20%20%20%20%20%20title_text%3D%22Correlation%20matrices%20side-by-side%20and%20their%20absolute%20difference%22%2C%0A%20%20%20%20%20%20%20%20height%3D340%2C%0A%20%20%20%20)%0A%20%20%20%20mo.ui.plotly(_fig)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_11()%3A%0A%20%20%20%20%22%22%22Render%20a%20horizontal%20rule%20separator.%22%22%22%0A%20%20%20%20mo.md(%22---%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_12()%3A%0A%20%20%20%20%22%22%22Render%20the%20performance%20section%20header%20and%20methodology%20notes.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%E2%9A%A1%20Performance%20Comparison%0A%0A%20%20%20%20%20%20%20%20Each%20implementation%20is%20timed%20across%20five%20realistic%20data%20sizes.%20%20The%0A%20%20%20%20%20%20%20%20reported%20figure%20is%20the%20**minimum**%20of%20five%20independent%20runs%20(best-case%0A%20%20%20%20%20%20%20%20wall-clock%20time%2C%20removing%20scheduling%20jitter).%20%20Input%20data%20are%0A%20%20%20%20%20%20%20%20fully%20dense%20(no%20NaN)%20so%20both%20implementations%20process%20identical%20work.%0A%0A%20%20%20%20%20%20%20%20The%20benchmark%20also%20includes%20the%20**Polars%20%E2%86%92%20pandas%20conversion**%0A%20%20%20%20%20%20%20%20(%60to_pandas()%60)%20that%20was%20required%20by%20the%20original%20implementation%20%E2%80%94%0A%20%20%20%20%20%20%20%20this%20is%20the%20cost%20incurred%20inside%20%60BasanosEngine.cor%60%20before%20every%0A%20%20%20%20%20%20%20%20call%20to%20%60ewm().corr()%60.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_13()%3A%0A%20%20%20%20%22%22%22Run%20timing%20benchmarks%20across%20five%20data%20sizes%20and%20return%20the%20results%20list.%22%22%22%0A%20%20%20%20_sizes%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(500%2C%204%2C%20%22T%3D500%2C%20N%3D4%22)%2C%0A%20%20%20%20%20%20%20%20(1461%2C%204%2C%20%22T%3D1461%2C%20N%3D4%22)%2C%0A%20%20%20%20%20%20%20%20(1461%2C%2010%2C%20%22T%3D1461%2C%20N%3D10%22)%2C%0A%20%20%20%20%20%20%20%20(1461%2C%2020%2C%20%22T%3D1461%2C%20N%3D20%22)%2C%0A%20%20%20%20%20%20%20%20(2500%2C%2020%2C%20%22T%3D2500%2C%20N%3D20%22)%2C%0A%20%20%20%20%5D%0A%20%20%20%20_com%20%3D%2032%0A%20%20%20%20_repeats%20%3D%205%0A%20%20%20%20_rng%20%3D%20np.random.default_rng(99)%0A%0A%20%20%20%20bench_results%20%3D%20%5B%5D%0A%20%20%20%20for%20_t_size%2C%20_n_assets%2C%20_label%20in%20_sizes%3A%0A%20%20%20%20%20%20%20%20_data_np%20%3D%20_rng.normal(size%3D(_t_size%2C%20_n_assets))%0A%0A%20%20%20%20%20%20%20%20%23%20pandas%20%E2%80%94%20core%20algorithm%20only%20(numpy%20%E2%86%92%20pandas%20%E2%86%92%20numpy)%0A%20%20%20%20%20%20%20%20_t_pd_core%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20min(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20timeit.repeat(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20d%3D_data_np%3A%20ewm_corr_pandas(d%2C%20_com%2C%20_com)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20number%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20repeat%3D_repeats%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20*%201000%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20pandas%20%E2%80%94%20with%20Polars%20round-trip%20(the%20true%20original%20cost)%0A%20%20%20%20%20%20%20%20_pl_df%20%3D%20pl.DataFrame(%7Bf%22a%7Bi%7D%22%3A%20_data_np%5B%3A%2C%20i%5D%20for%20i%20in%20range(_n_assets)%7D)%0A%20%20%20%20%20%20%20%20_t_pd_full%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20min(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20timeit.repeat(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20df%3D_pl_df%3A%20ewm_corr_pandas(df.to_numpy()%2C%20_com%2C%20_com)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20number%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20repeat%3D_repeats%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20*%201000%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20NumPy%20%2F%20lfilter%20%E2%80%94%20current%20implementation%0A%20%20%20%20%20%20%20%20_t_np%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20min(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20timeit.repeat(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20d%3D_data_np%3A%20ewm_corr_numpy(d%2C%20_com%2C%20_com)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20number%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20repeat%3D_repeats%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20*%201000%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20bench_results.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22size%22%3A%20_label%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22T%22%3A%20_t_size%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22N%22%3A%20_n_assets%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pandas_core_ms%22%3A%20round(_t_pd_core%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pandas_full_ms%22%3A%20round(_t_pd_full%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22numpy_ms%22%3A%20round(_t_np%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22speedup_vs_core%22%3A%20round(_t_pd_core%20%2F%20_t_np%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22speedup_vs_full%22%3A%20round(_t_pd_full%20%2F%20_t_np%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20return%20(bench_results%2C)%0A%0A%0A%40app.cell%0Adef%20cell_14(bench_results)%3A%0A%20%20%20%20%22%22%22Render%20the%20grouped%20bar%20chart%20and%20timing%20summary%20table.%22%22%22%0A%20%20%20%20_labels%20%3D%20%5Br%5B%22size%22%5D%20for%20r%20in%20bench_results%5D%0A%20%20%20%20_pd_core%20%3D%20%5Br%5B%22pandas_core_ms%22%5D%20for%20r%20in%20bench_results%5D%0A%20%20%20%20_pd_full%20%3D%20%5Br%5B%22pandas_full_ms%22%5D%20for%20r%20in%20bench_results%5D%0A%20%20%20%20_np_times%20%3D%20%5Br%5B%22numpy_ms%22%5D%20for%20r%20in%20bench_results%5D%0A%0A%20%20%20%20_fig%20%3D%20go.Figure(%0A%20%20%20%20%20%20%20%20data%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20go.Bar(name%3D%22pandas%20(core%20only)%22%2C%20x%3D_labels%2C%20y%3D_pd_core%2C%20marker_color%3D%22%231f77b4%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20go.Bar(name%3D%22pandas%20%2B%20Polars%20round-trip%22%2C%20x%3D_labels%2C%20y%3D_pd_full%2C%20marker_color%3D%22%23aec7e8%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20go.Bar(name%3D%22NumPy%20%2F%20lfilter%20(new)%22%2C%20x%3D_labels%2C%20y%3D_np_times%2C%20marker_color%3D%22%23ff7f0e%22)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20_fig.update_layout(%0A%20%20%20%20%20%20%20%20barmode%3D%22group%22%2C%0A%20%20%20%20%20%20%20%20title%3D%22EWM%20correlation%20timing%20%E2%80%94%20lower%20is%20better%22%2C%0A%20%20%20%20%20%20%20%20yaxis_title%3D%22Time%20(ms%2C%20best%20of%205%20runs)%22%2C%0A%20%20%20%20%20%20%20%20legend%3D%7B%22orientation%22%3A%20%22h%22%2C%20%22y%22%3A%201.12%7D%2C%0A%20%20%20%20%20%20%20%20height%3D400%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_tbl%20%3D%20pl.DataFrame(bench_results).select(%0A%20%20%20%20%20%20%20%20pl.col(%22size%22).alias(%22Size%22)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22pandas_core_ms%22).alias(%22pandas%20core%20(ms)%22)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22pandas_full_ms%22).alias(%22pandas%20%2B%20Polars%20(ms)%22)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22numpy_ms%22).alias(%22NumPy%20(ms)%22)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22speedup_vs_core%22).alias(%22Speedup%20vs%20core%20(%C3%97)%22)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22speedup_vs_full%22).alias(%22Speedup%20vs%20full%20(%C3%97)%22)%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.ui.plotly(_fig)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Timing%20table%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.ui.table(_tbl)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_15()%3A%0A%20%20%20%20%22%22%22Render%20a%20horizontal%20rule%20separator.%22%22%22%0A%20%20%20%20mo.md(%22---%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_16(bench_results)%3A%0A%20%20%20%20%22%22%22Render%20the%20summary%20callout%20with%20correctness%20and%20performance%20conclusions.%22%22%22%0A%20%20%20%20_speedups_core%20%3D%20%5Br%5B%22speedup_vs_core%22%5D%20for%20r%20in%20bench_results%5D%0A%20%20%20%20_speedups_full%20%3D%20%5Br%5B%22speedup_vs_full%22%5D%20for%20r%20in%20bench_results%5D%0A%20%20%20%20_avg_core%20%3D%20round(sum(_speedups_core)%20%2F%20len(_speedups_core)%2C%201)%0A%20%20%20%20_avg_full%20%3D%20round(sum(_speedups_full)%20%2F%20len(_speedups_full)%2C%201)%0A%20%20%20%20_max_core%20%3D%20round(max(_speedups_core)%2C%201)%0A%20%20%20%20_max_full%20%3D%20round(max(_speedups_full)%2C%201)%0A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20rf%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%8E%89%20Summary%0A%0A%20%20%20%20%20%20%20%20%23%23%23%20Correctness%20%E2%9C%85%0A%0A%20%20%20%20%20%20%20%20Both%20implementations%20produce%20**identical%20results**%20%E2%80%94%20the%20absolute%0A%20%20%20%20%20%20%20%20difference%20between%20any%20two%20corresponding%20finite%20values%20is%20below%0A%20%20%20%20%20%20%20%20**1%20%C3%97%2010%E2%81%BB%C2%B9%E2%81%B0**%20for%20all%20tested%20shapes%2C%20including%20inputs%20with%20~5%20%25%20NaN%0A%20%20%20%20%20%20%20%20(missing%20assets).%20%20NaN%20patterns%20are%20preserved%20exactly.%0A%0A%20%20%20%20%20%20%20%20%23%23%23%20Performance%20%E2%9C%85%0A%0A%20%20%20%20%20%20%20%20%7C%20Comparison%20%7C%20Average%20speedup%20%7C%20Peak%20speedup%20%7C%0A%20%20%20%20%20%20%20%20%7C------------%7C-----------------%7C--------------%7C%0A%20%20%20%20%20%20%20%20%7C%20vs%20pandas%20core%20algorithm%20%7C%20**%7B_avg_core%7D%C3%97**%20%7C%20%7B_max_core%7D%C3%97%20%7C%0A%20%20%20%20%20%20%20%20%7C%20vs%20pandas%20%2B%20Polars%20round-trip%20%7C%20**%7B_avg_full%7D%C3%97**%20%7C%20%7B_max_full%7D%C3%97%20%7C%0A%0A%20%20%20%20%20%20%20%20**Why%20is%20the%20NumPy%20version%20faster%3F**%0A%0A%20%20%20%20%20%20%20%20The%20key%20change%20is%20replacing%20the%20per-timestep%20Python%20%60for%60%20loop%20with%0A%20%20%20%20%20%20%20%20%60scipy.signal.lfilter%60%2C%20which%20solves%20the%20IIR%20recurrence%0A%20%20%20%20%20%20%20%20%60s%5Bt%5D%20%3D%20%CE%B2%20%C2%B7%20s%5Bt%E2%88%921%5D%20%2B%20v%5Bt%5D%60%20for%20**all%20N%C2%B2%20asset%20pairs%20simultaneously%0A%20%20%20%20%20%20%20%20in%20C**.%20%20Python%20loop%20overhead%20(frame%20setup%2C%20bytecode%20dispatch%2C%20numpy%0A%20%20%20%20%20%20%20%20call%20overhead%20per%20iteration)%20grows%20with%20T%3B%20the%20C%20IIR%20filter%20does%20not.%0A%0A%20%20%20%20%20%20%20%20Removing%20%60pandas%60%20and%20%60pyarrow%60%20as%20project%20dependencies%20therefore%0A%20%20%20%20%20%20%20%20**improved%20both%20correctness%20guarantees%20and%20runtime%20performance**.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
70ff67de4f0ec2a54e9f0826a8cf11c4