Skip to content

Commit 65ae0c6

Browse files
author
Marco Gorelli
committed
🐛 aggregations were getting overwritten if they had the same name
1 parent 6437f5e commit 65ae0c6

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

doc/source/whatsnew/v1.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,7 @@ Groupby/resample/rolling
10171017
- Bug in :meth:`DataFrame.groupby` when using nunique on axis=1 (:issue:`30253`)
10181018
- Bug in :meth:`GroupBy.quantile` with multiple list-like q value and integer column names (:issue:`30289`)
10191019
- Bug in :meth:`GroupBy.pct_change` and :meth:`core.groupby.SeriesGroupBy.pct_change` causes ``TypeError`` when ``fill_method`` is ``None`` (:issue:`30463`)
1020+
- Bug in :meth:`SeriesGroupBy._aggregate_multiple_funcs` was resulting in aggregations being overwritten when they shared the same name (:issue:`30092`)
10201021

10211022
Reshaping
10221023
^^^^^^^^^

pandas/core/groupby/generic.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ def _aggregate_multiple_funcs(self, arg):
308308

309309
arg = zip(columns, arg)
310310

311-
results = {}
311+
results = []
312+
Result = namedtuple("result", ["name", "aggregation"])
312313
for name, func in arg:
313314
obj = self
314315

@@ -318,13 +319,18 @@ def _aggregate_multiple_funcs(self, arg):
318319
obj = copy.copy(obj)
319320
obj._reset_cache()
320321
obj._selection = name
321-
results[name] = obj.aggregate(func)
322+
results.append(Result(name, obj.aggregate(func)))
322323

323-
if any(isinstance(x, DataFrame) for x in results.values()):
324+
if any(isinstance(x.aggregation, DataFrame) for x in results):
324325
# let higher level handle
325-
return results
326+
return dict(results)
326327

327-
return DataFrame(results, columns=columns)
328+
# If there are multiple aggregations with the same name, we need to pass
329+
# them to the constructor with different keys.
330+
df = DataFrame({n: x.aggregation for n, x in enumerate(results)}).rename(
331+
columns={n: column for n, column in enumerate(columns)}
332+
)
333+
return df
328334

329335
def _wrap_series_output(
330336
self, output: Mapping[base.OutputKey, Union[Series, np.ndarray]], index: Index

pandas/tests/groupby/aggregate/test_aggregate.py

+54
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,60 @@ def test_agg_multiple_functions_maintain_order(df):
239239
tm.assert_index_equal(result.columns, exp_cols)
240240

241241

242+
def test_agg_multiple_functions_same_name(df):
243+
np.random.seed(1)
244+
df = tm.makeTimeDataFrame()
245+
result = df.resample("3D").agg(
246+
{
247+
"A": [
248+
functools.partial(np.quantile, q=0.9999),
249+
functools.partial(np.quantile, q=0.90),
250+
]
251+
}
252+
)
253+
expected_index = pd.DatetimeIndex(
254+
[
255+
"2000-01-03",
256+
"2000-01-06",
257+
"2000-01-09",
258+
"2000-01-12",
259+
"2000-01-15",
260+
"2000-01-18",
261+
"2000-01-21",
262+
"2000-01-24",
263+
"2000-01-27",
264+
"2000-01-30",
265+
"2000-02-02",
266+
"2000-02-05",
267+
"2000-02-08",
268+
"2000-02-11",
269+
],
270+
dtype="datetime64[ns]",
271+
freq="3D",
272+
)
273+
expected_columns = pd.MultiIndex.from_tuples([("A", "quantile"), ("A", "quantile")])
274+
expected_values = [
275+
[1.62391486, 1.19384194],
276+
[0.86521379, 0.67157],
277+
[1.74440713, 1.34017672],
278+
[0.31892541, 0.2053572],
279+
[1.46210794, 1.46210794],
280+
[-0.32242953, -0.33474463],
281+
[1.13376944, 1.13376944],
282+
[-0.17256929, -0.31351425],
283+
[0.58276115, 0.52875507],
284+
[1.14449918, 0.92018942],
285+
[0.90159057, 0.90144377],
286+
[-0.68372786, -0.68372786],
287+
[-0.12291923, -0.1518898],
288+
[0.53035547, 0.53035547],
289+
]
290+
expected = pd.DataFrame(
291+
expected_values, columns=expected_columns, index=expected_index
292+
)
293+
tm.assert_frame_equal(result, expected)
294+
295+
242296
def test_multiple_functions_tuples_and_non_tuples(df):
243297
# #1359
244298
funcs = [("foo", "mean"), "std"]

0 commit comments

Comments
 (0)