@@ -105,7 +105,12 @@ impl ApplyExpr {
105105 & self ,
106106 mut ac : AggregationContext < ' a > ,
107107 ) -> PolarsResult < AggregationContext < ' a > > {
108+ dbg ! ( "start apply_single_group_aware" ) ; //kdn
109+ dbg ! ( & self . expr) ;
110+ // dbg!(&ac);
111+
108112 let s = ac. get_values ( ) ;
113+ let name = s. name ( ) . clone ( ) ;
109114
110115 #[ allow( clippy:: nonminimal_bool) ]
111116 {
@@ -116,20 +121,6 @@ impl ApplyExpr {
116121 ) ;
117122 }
118123
119- let name = s. name ( ) . clone ( ) ;
120- let agg = ac. aggregated ( ) ;
121- // Collection of empty list leads to a null dtype. See: #3687.
122- if agg. is_empty ( ) {
123- // Create input for the function to determine the output dtype, see #3946.
124- let agg = agg. list ( ) . unwrap ( ) ;
125- let input_dtype = agg. inner_dtype ( ) ;
126- let input = Column :: full_null ( name. clone ( ) , 0 , input_dtype) ;
127-
128- let output = self . eval_and_flatten ( & mut [ input] ) ?;
129- let ca = ListChunked :: full ( name, output. as_materialized_series ( ) , 0 ) ;
130- return self . finish_apply_groups ( ac, ca) ;
131- }
132-
133124 let f = |opt_s : Option < Series > | match opt_s {
134125 None => Ok ( None ) ,
135126 Some ( mut s) => {
@@ -144,6 +135,36 @@ impl ApplyExpr {
144135 } ,
145136 } ;
146137
138+ // In case of overlapping (rolling) groups, we build groups in a lazy manner to avoid
139+ // memory explosion.
140+ // kdn TODO: TBD - do we want to follow this path for *all* Slice
141+ if matches ! ( ac. agg_state( ) , AggState :: NotAggregated ( _) )
142+ && let GroupsType :: Slice { rolling : true , .. } = ac. groups . as_ref ( ) . as_ref ( )
143+ {
144+ let ca: ChunkedArray < _ > = ac
145+ . iter_groups_lazy ( false )
146+ . map ( |opt| opt. map ( |s| s. as_ref ( ) . clone ( ) ) )
147+ . map ( f)
148+ . collect :: < PolarsResult < _ > > ( ) ?;
149+
150+ return self . finish_apply_groups ( ac, ca. with_name ( name) ) ;
151+ }
152+
153+ // At this point, calling aggregated will not lead to memory explosion.
154+ let agg = ac. aggregated ( ) ;
155+
156+ // Collection of empty list leads to a null dtype. See: #3687.
157+ if agg. is_empty ( ) {
158+ // Create input for the function to determine the output dtype, see #3946.
159+ let agg = agg. list ( ) . unwrap ( ) ;
160+ let input_dtype = agg. inner_dtype ( ) ;
161+ let input = Column :: full_null ( name. clone ( ) , 0 , input_dtype) ;
162+
163+ let output = self . eval_and_flatten ( & mut [ input] ) ?;
164+ let ca = ListChunked :: full ( name, output. as_materialized_series ( ) , 0 ) ;
165+ return self . finish_apply_groups ( ac, ca) ;
166+ }
167+
147168 let ca: ListChunked = if self . allow_threading {
148169 let dtype = if self . output_field . dtype . is_known ( ) && !self . output_field . dtype . is_null ( )
149170 {
0 commit comments