@@ -12,6 +12,7 @@ import (
1212type SeriesGroupByResult struct {
1313 lhsLabel string
1414 rhsLabel string
15+ // TODO(dustmop): convert to map[string]Series
1516 grouping map [string ][]string
1617}
1718
2425var seriesGroupByResultMethods = map [string ]* starlark.Builtin {
2526 "count" : starlark .NewBuiltin ("count" , seriesGroupByResultCount ),
2627 "sum" : starlark .NewBuiltin ("sum" , seriesGroupByResultSum ),
28+ "apply" : starlark .NewBuiltin ("apply" , seriesGroupByResultApply ),
2729}
2830
2931// Freeze has no effect on the immutable SeriesGroupByResult
@@ -113,6 +115,55 @@ func seriesGroupByResultCount(_ *starlark.Thread, b *starlark.Builtin, args star
113115 return newSeriesFromInts (vals , index , self .rhsLabel ), nil
114116}
115117
118+ // apply method returns a Series that is built by calling the given
119+ // function, and passing each grouped series as an argument to it
120+ func seriesGroupByResultApply (thread * starlark.Thread , b * starlark.Builtin , args starlark.Tuple , kwargs []starlark.Tuple ) (starlark.Value , error ) {
121+ var (
122+ funcVal starlark.Value
123+ self = b .Receiver ().(* SeriesGroupByResult )
124+ )
125+
126+ if err := starlark .UnpackArgs ("apply" , args , kwargs ,
127+ "function" , & funcVal ,
128+ ); err != nil {
129+ return nil , err
130+ }
131+
132+ funcObj , ok := funcVal .(* starlark.Function )
133+ if ! ok {
134+ return nil , fmt .Errorf ("first argument must be a function" )
135+ }
136+
137+ sortedKeys := getSortedKeys (self .grouping )
138+ builder := newTypedSliceBuilder (len (sortedKeys ))
139+ indexNames := make ([]string , len (sortedKeys ))
140+
141+ for i , groupName := range sortedKeys {
142+ values := self .grouping [groupName ]
143+ // TODO(dustmop): Pass actual index here
144+ index := NewIndex (nil , groupName )
145+ series := newSeriesFromStrings (values , index , groupName )
146+ arguments := starlark.Tuple {series }
147+ // Call function, passing the series to it
148+ res , err := starlark .Call (thread , funcObj , arguments , nil )
149+ if err != nil {
150+ return nil , err
151+ }
152+ obj , ok := toScalarMaybe (res )
153+ if ! ok {
154+ return nil , fmt .Errorf ("could not convert: %v" , res )
155+ }
156+ // Accumulate the new series, and build the new index
157+ builder .push (obj )
158+ indexNames [i ] = groupName
159+ }
160+ if err := builder .error (); err != nil {
161+ return nil , err
162+ }
163+ s := builder .toSeries (NewIndex (indexNames , self .lhsLabel ), self .rhsLabel )
164+ return & s , nil
165+ }
166+
116167func getSortedKeys (m map [string ][]string ) []string {
117168 keys := make ([]string , 0 , len (m ))
118169 for k := range m {
0 commit comments