3737 # Pandas cumcount counts nulls while Polars does not
3838 # So, instead of using "cumcount" we use "cumsum" on notna() to get the same result
3939 "cum_count" : "cumsum" ,
40+ "rolling_sum" : "sum" ,
4041 "shift" : "shift" ,
4142 "rank" : "rank" ,
4243 "diff" : "diff" ,
@@ -58,6 +59,12 @@ def window_kwargs_to_pandas_equivalent(
5859 }
5960 elif function_name .startswith ("cum_" ): # Cumulative operation
6061 pandas_kwargs = {"skipna" : True }
62+ elif function_name .startswith ("rolling_" ): # Rolling operation
63+ pandas_kwargs = {
64+ "min_periods" : kwargs ["min_samples" ],
65+ "window" : kwargs ["window_size" ],
66+ "center" : kwargs ["center" ],
67+ }
6168 else : # e.g. std, var
6269 pandas_kwargs = kwargs
6370 return pandas_kwargs
@@ -220,11 +227,11 @@ def func(df: PandasLikeDataFrame) -> Sequence[PandasLikeSeries]:
220227 raise NotImplementedError (msg )
221228 else :
222229 function_name : str = re .sub (r"(\w+->)" , "" , self ._function_name )
223- if pandas_function_name : = WINDOW_FUNCTIONS_TO_PANDAS_EQUIVALENT .get (
224- function_name , AGGREGATIONS_TO_PANDAS_EQUIVALENT . get ( function_name , None )
225- ):
226- pass
227- else :
230+ pandas_function_name = WINDOW_FUNCTIONS_TO_PANDAS_EQUIVALENT .get (
231+ function_name ,
232+ AGGREGATIONS_TO_PANDAS_EQUIVALENT . get ( function_name ),
233+ )
234+ if pandas_function_name is None :
228235 msg = (
229236 f"Unsupported function: { function_name } in `over` context.\n \n "
230237 f"Supported functions are { ', ' .join (WINDOW_FUNCTIONS_TO_PANDAS_EQUIVALENT )} \n "
@@ -237,7 +244,6 @@ def func(df: PandasLikeDataFrame) -> Sequence[PandasLikeSeries]:
237244
238245 def func (df : PandasLikeDataFrame ) -> Sequence [PandasLikeSeries ]:
239246 output_names , aliases = evaluate_output_names_and_aliases (self , df , [])
240-
241247 if function_name == "cum_count" :
242248 plx = self .__narwhals_namespace__ ()
243249 df = df .with_columns (~ plx .col (* output_names ).is_null ())
@@ -260,9 +266,16 @@ def func(df: PandasLikeDataFrame) -> Sequence[PandasLikeSeries]:
260266 elif reverse :
261267 columns = list (set (partition_by ).union (output_names ))
262268 df = df [columns ][::- 1 ]
263- res_native = df ._native_frame .groupby (partition_by )[
264- list (output_names )
265- ].transform (pandas_function_name , ** pandas_kwargs )
269+ if function_name .startswith ("rolling" ):
270+ rolling = df ._native_frame .groupby (partition_by )[
271+ list (output_names )
272+ ].rolling (** pandas_kwargs )
273+ assert pandas_function_name is not None # help mypy # noqa: S101
274+ res_native = getattr (rolling , pandas_function_name )()
275+ else :
276+ res_native = df ._native_frame .groupby (partition_by )[
277+ list (output_names )
278+ ].transform (pandas_function_name , ** pandas_kwargs )
266279 result_frame = df ._from_native_frame (res_native ).rename (
267280 dict (zip (output_names , aliases ))
268281 )
@@ -331,6 +344,18 @@ def cum_max(self: Self, *, reverse: bool) -> Self:
331344 def cum_prod (self : Self , * , reverse : bool ) -> Self :
332345 return self ._reuse_series ("cum_prod" , call_kwargs = {"reverse" : reverse })
333346
347+ def rolling_sum (
348+ self : Self , window_size : int , * , min_samples : int , center : bool
349+ ) -> Self :
350+ return self ._reuse_series (
351+ "rolling_sum" ,
352+ call_kwargs = {
353+ "window_size" : window_size ,
354+ "min_samples" : min_samples ,
355+ "center" : center ,
356+ },
357+ )
358+
334359 def rank (
335360 self : Self ,
336361 method : Literal ["average" , "min" , "max" , "dense" , "ordinal" ],
0 commit comments