Skip to content

ENH: Implement Keyword Aggregation for DataFrame.agg and Series.agg #29116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 148 commits into from
Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
148 commits
Select commit Hold shift + click to select a range
7e461a1
remove \n from docstring
charlesdong1991 Dec 3, 2018
1314059
fix conflicts
charlesdong1991 Jan 19, 2019
8bcb313
Merge remote-tracking branch 'upstream/master'
charlesdong1991 Jul 30, 2019
7bc368d
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Oct 20, 2019
cf5c6c3
Implement agg for DataFrame
charlesdong1991 Oct 20, 2019
5298331
fix conflict
charlesdong1991 Oct 20, 2019
4fb74b5
remove unused import
charlesdong1991 Oct 20, 2019
97209be
remove print
charlesdong1991 Oct 20, 2019
ca273ff
fix test
charlesdong1991 Oct 20, 2019
1d2ab15
fix typo
charlesdong1991 Oct 20, 2019
3ca193c
add keyword agg for series
charlesdong1991 Oct 20, 2019
c8f80ed
fix linting
charlesdong1991 Oct 20, 2019
8c738e9
fix PY35 issue
charlesdong1991 Oct 21, 2019
d4d9ea4
try to fix py35 order issue
charlesdong1991 Oct 21, 2019
2a6de27
test if fixed
charlesdong1991 Oct 21, 2019
21e09f9
test again
charlesdong1991 Oct 21, 2019
058a8e9
simpler code
charlesdong1991 Oct 21, 2019
0da68d8
test py35
charlesdong1991 Oct 22, 2019
15e3659
fix conflict
charlesdong1991 Oct 22, 2019
438398d
test PY35
charlesdong1991 Oct 23, 2019
d47b790
try to fix py35
charlesdong1991 Oct 23, 2019
832b8d9
find py35 output
charlesdong1991 Oct 23, 2019
5a3b690
test py35
charlesdong1991 Oct 23, 2019
4fb86f0
retest py35
charlesdong1991 Oct 23, 2019
a1369bf
retest py35
charlesdong1991 Oct 23, 2019
ef981a3
try to fix py35
charlesdong1991 Oct 23, 2019
82c8960
try to fix py35
charlesdong1991 Oct 23, 2019
c610391
try one more time
charlesdong1991 Oct 23, 2019
679ba59
fix typo
charlesdong1991 Oct 23, 2019
2ee2628
py35
charlesdong1991 Oct 23, 2019
31f7033
skip PY35
charlesdong1991 Oct 23, 2019
2acb244
skip py35
charlesdong1991 Oct 23, 2019
dfbd67a
fix typo
charlesdong1991 Oct 23, 2019
ff5e60f
skip all py35
charlesdong1991 Oct 23, 2019
7c6c891
skip py35 for series
charlesdong1991 Oct 23, 2019
3e55fcb
fix test
charlesdong1991 Oct 23, 2019
6d74b29
skip series py35
charlesdong1991 Oct 23, 2019
532337e
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Oct 29, 2019
400ff3e
merge master and remove helper
charlesdong1991 Nov 8, 2019
05af2de
remove helper
charlesdong1991 Nov 8, 2019
6206fa4
remove py36
charlesdong1991 Nov 8, 2019
34199ad
put back imports
charlesdong1991 Nov 8, 2019
15d099c
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Nov 8, 2019
c56f05f
avoid circular dependency
charlesdong1991 Nov 8, 2019
d3f0620
fix linting
charlesdong1991 Nov 8, 2019
20ecfda
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Dec 19, 2019
89b8e6b
code change based on review
charlesdong1991 Dec 20, 2019
8aa1cc9
remove util
charlesdong1991 Dec 20, 2019
091ca75
Add docstring
charlesdong1991 Dec 20, 2019
c2d5104
fix circular import
charlesdong1991 Dec 20, 2019
50ebdaf
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jan 3, 2020
0484f5e
reorg and deduplicate
charlesdong1991 Jan 3, 2020
425c802
remove used imports
charlesdong1991 Jan 3, 2020
d5c2c6c
fix linting
charlesdong1991 Jan 3, 2020
8bb9714
fix wrong import
charlesdong1991 Jan 3, 2020
2607c5d
fix conflict
charlesdong1991 Jan 3, 2020
0545231
isort
charlesdong1991 Jan 3, 2020
0a27889
fix mypy
charlesdong1991 Jan 3, 2020
a66053e
Code change based on review
charlesdong1991 Jan 6, 2020
7311ef0
dropna
charlesdong1991 Jan 6, 2020
da2ff37
fix logic
charlesdong1991 Jan 7, 2020
bcc5bc3
fix logic
charlesdong1991 Jan 7, 2020
0825027
remove unused
charlesdong1991 Jan 7, 2020
d3c35f5
fix linting
charlesdong1991 Jan 7, 2020
cef2b50
simpler python
charlesdong1991 Jan 7, 2020
b96a942
fix conflicts
charlesdong1991 Jan 8, 2020
3123284
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jan 9, 2020
7bb3bd0
fix conflicts
charlesdong1991 Jan 21, 2020
3da2e2a
fix merge error
charlesdong1991 Jan 21, 2020
3ce91fc
fixup
charlesdong1991 Jan 21, 2020
1426ee2
fix annotation
charlesdong1991 Jan 21, 2020
5893a0e
fix annotation
charlesdong1991 Jan 21, 2020
cc85db4
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jan 23, 2020
0f55073
move code
charlesdong1991 Jan 25, 2020
90d52ba
move it back
charlesdong1991 Jan 25, 2020
381a697
fixup
charlesdong1991 Jan 25, 2020
238b4cc
add docstring
charlesdong1991 Jan 25, 2020
f8e1891
add func
charlesdong1991 Jan 25, 2020
66e9b38
isort
charlesdong1991 Jan 25, 2020
f4d8a4f
fix linting
charlesdong1991 Jan 26, 2020
c3e34a0
fix linting
charlesdong1991 Jan 26, 2020
0c0dbad
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jan 26, 2020
61f6201
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jan 28, 2020
88c7751
code change on JR reviews
charlesdong1991 Feb 2, 2020
e2b957a
move
charlesdong1991 Feb 2, 2020
99f75b2
linting
charlesdong1991 Feb 2, 2020
30b7296
isort
charlesdong1991 Feb 2, 2020
baea583
code change
charlesdong1991 Feb 12, 2020
04bffe6
add docstring
charlesdong1991 Feb 12, 2020
42091c3
add None back
charlesdong1991 Feb 12, 2020
fc13e19
fix annotation
charlesdong1991 Feb 12, 2020
1403426
better annotation
charlesdong1991 Feb 12, 2020
3d9655e
fix annotation
charlesdong1991 Feb 12, 2020
d78c57c
fix annotation
charlesdong1991 Feb 12, 2020
0487928
fix linting
charlesdong1991 Feb 16, 2020
7435ac5
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Feb 19, 2020
f1cd16c
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Feb 23, 2020
469691c
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Feb 24, 2020
1bb35b5
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Feb 27, 2020
7a6f496
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Mar 15, 2020
3730f7d
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Mar 30, 2020
cd8d00f
simpler python
charlesdong1991 Mar 30, 2020
6dddd55
simpler python
charlesdong1991 Mar 30, 2020
96dc3ed
fixup
charlesdong1991 Mar 30, 2020
075b85b
simplification
charlesdong1991 Mar 30, 2020
a44471c
better docs
charlesdong1991 Mar 30, 2020
0e2eae4
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Apr 10, 2020
2fb4b27
add docs
charlesdong1991 Apr 10, 2020
5e04185
focs
charlesdong1991 Apr 10, 2020
56d0f89
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Apr 10, 2020
7f4839e
fix doc
charlesdong1991 Apr 10, 2020
65d578b
fixup
charlesdong1991 Apr 10, 2020
3e6a06c
fix up
charlesdong1991 Apr 10, 2020
8651447
fix doctest
charlesdong1991 Apr 10, 2020
a7439fe
doctest
charlesdong1991 Apr 11, 2020
449d40f
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Apr 11, 2020
9fd8ec5
rebuild
charlesdong1991 Apr 11, 2020
736bea2
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Apr 15, 2020
d20be20
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 May 10, 2020
35b2b17
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 May 22, 2020
74d6169
fixup and resolve conflict and merge master
charlesdong1991 Jun 16, 2020
0546224
cleaner code
charlesdong1991 Jun 16, 2020
f5f0e68
rename
charlesdong1991 Jun 16, 2020
54ff962
linting
charlesdong1991 Jun 16, 2020
ac57023
init
charlesdong1991 Jun 16, 2020
484e42c
better doc
charlesdong1991 Jun 17, 2020
47e6598
complex case
charlesdong1991 Jun 17, 2020
f28b452
linting
charlesdong1991 Jun 17, 2020
81b4186
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jun 17, 2020
1fd4b5b
resolve conflict and merge master
charlesdong1991 Jun 19, 2020
47dc5fe
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jun 20, 2020
9190f7f
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jun 20, 2020
89de59e
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jun 23, 2020
8493383
add typing
charlesdong1991 Jun 25, 2020
9a9dd7f
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jun 25, 2020
c75c882
black
charlesdong1991 Jun 25, 2020
fa61db7
remove line
charlesdong1991 Jun 25, 2020
00a1ccf
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jun 27, 2020
26b380a
simplify annotation
charlesdong1991 Jul 5, 2020
165ea83
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jul 5, 2020
d6923f2
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jul 8, 2020
f6a5cc1
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jul 9, 2020
a747ab6
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jul 10, 2020
44405e8
deprivate relabel_result
charlesdong1991 Jul 10, 2020
faea906
cleaner annotations
charlesdong1991 Jul 10, 2020
7e30a61
Merge remote-tracking branch 'upstream/master' into nested_renaming_agg
charlesdong1991 Jul 10, 2020
3d20524
fix import sorting
charlesdong1991 Jul 10, 2020
05921af
move defined annotation inside aggregation.py
charlesdong1991 Jul 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -6588,16 +6588,51 @@ def _gotitem(
**_shared_doc_kwargs
)
@Appender(_shared_docs["aggregate"])
def aggregate(self, func, axis=0, *args, **kwargs):
def aggregate(self, func=None, axis=0, *args, **kwargs):
from pandas.core.groupby.generic import (
_is_multi_agg_with_relabel,
_maybe_mangle_lambdas,
_normalize_keyword_aggregation,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

importing private functions is sub-optimal. it looks like all of the stuff that actually uses these functions doesn't rely on self. could a public function be made in groupby.generic (or possibly SelectionMixin per @jreback's comment) that consists of basically 6600-6611?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, i made it in the base class

and because of this, I think in the follow up PR, I could do a cleaning on the other one in core/groupby/generic.py.

)

axis = self._get_axis_number(axis)

relabeling = func is None and _is_multi_agg_with_relabel(**kwargs)
if relabeling:
func, indexes, order = _normalize_keyword_aggregation(kwargs)
reordered_indexes = [
pair[0] for pair in sorted(zip(indexes, order), key=lambda t: t[1])
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of this is already implemented in the SelectionMixin
you should not have to add much code

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emm, i think this implementation is sort of post-processing after aggregation from SelectionMixin? no?
i will try my best to shorten the code anyway! thanks! @jreback

kwargs = {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need to do this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, removed this, not needed

elif func is None:
# nicer error message
raise TypeError("Must provide 'func' or tuples of '(column, aggfunc).")

func = _maybe_mangle_lambdas(func)

result = None
try:
result, how = self._aggregate(func, axis=axis, *args, **kwargs)
except TypeError:
pass
if result is None:
return self.apply(func, axis=axis, args=args, **kwargs)

if relabeling:

# when there are more than one column being used in aggregate, the order
# of result will be reversed, and in case the func is not used by other
# columns, there might be NaN values, so separate these two cases
reordered_result = DataFrame(index=indexes)
idx = 0
for col, funcs in func.items():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is extremely inefficient.

instead try appending to a list and concat at the end.

why do you need .values? that is not very idiomatic here, nor is the dropna

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can also just reorder the indicies and do an indexing selection would be way better

Copy link
Member Author

@charlesdong1991 charlesdong1991 Jan 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I reduced half of the code in this loop, i am pretty sure the previous version was due to some order issue.
However, this seems still a little bit complex, but this is to keep two things unchanged: 1. the order to first occurrence of columns to be aggregated, 2. the order of the new named column occurrence.

for instance in the example below, i think a correct behaviour should be:

df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4], "C": [3,4,5,6]})
df.agg(ab=("C", np.max), foo=("A", max), bar=("B", "mean"), cat=("A", "min"), 
            dat=("B", "max"), f=("A", "max"), g=("C", "min"))

the column of new aggregated df is : ['C', 'A', 'B'] other than ['A', 'B', 'C'] since it is the order of the first occurrence of them. And the index should be ['ab', 'foo', 'bar', 'cat', 'dat', 'f', 'g'] since it is the order in the agg function.

Any advice to simply the code would be much appreciated!

v = reordered_indexes[idx : idx + len(funcs)]
if len(func) > 1:
reordered_result.loc[v, col] = result[col][::-1].dropna().values
else:
reordered_result.loc[v, col] = result[col].values
idx = idx + len(funcs)
result = reordered_result
return result

def _aggregate(self, arg, axis=0, *args, **kwargs):
Expand Down
10 changes: 9 additions & 1 deletion pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -3800,9 +3800,17 @@ def _gotitem(self, key, ndim, subset=None):
**_shared_doc_kwargs
)
@Appender(generic._shared_docs["aggregate"])
def aggregate(self, func, axis=0, *args, **kwargs):
def aggregate(self, func=None, axis=0, *args, **kwargs):
# Validate the axis parameter
self._get_axis_number(axis)

if func is None:
# This is due to order issue of dictionary in PY35, e.g. if {"foo"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

py35 has been dropped; can this be simplified?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, OrderDict is no longer needed

# : "sum", "bar": "min"}, then it will take "bar" first because it
# b is before f
func = OrderedDict(kwargs.items())
kwargs = {}

result, how = self._aggregate(func, *args, **kwargs)
if result is None:

Expand Down
93 changes: 93 additions & 0 deletions pandas/tests/frame/test_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -1367,3 +1367,96 @@ def test_consistency_of_aggregates_of_columns_with_missing_values(self, df, meth
tm.assert_series_equal(
none_in_first_column_result, none_in_second_column_result
)


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you create a new test file for these, maybe test_apply_relabling.py (alt we can make a pandas/tests/frame/apply/..... subdir)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok with moving existing functions later, but pls create a new file / subdir now.

class TestDataFrameNamedAggregate:

# GH 26513
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually these go inside the test func

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, sorry, i put this outside was because i wanted it for all functions in the entire class, since they are all coming from the same issue. I changed and put them under each function.

def test_agg_relabel(self):
df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4], "C": [3, 4, 5, 6]})

# simplest case with one column, one func
result = df.agg(foo=("B", "sum"))
expected = pd.DataFrame({"B": [10]}, index=pd.Index(["foo"]))
tm.assert_frame_equal(result, expected)

# test on same column with different methods
result = df.agg(foo=("B", "sum"), bar=("B", "min"))
expected = pd.DataFrame({"B": [10, 1]}, index=pd.Index(["foo", "bar"]))

tm.assert_frame_equal(result, expected)

# test on multiple columns with multiple methods
result = df.agg(
foo=("A", "sum"),
bar=("B", "mean"),
cat=("A", "min"),
dat=("B", "max"),
f=("A", "max"),
g=("C", "min"),
)
expected = pd.DataFrame(
{
"A": [6.0, np.nan, 1.0, np.nan, 2.0, np.nan],
"B": [np.nan, 2.5, np.nan, 4.0, np.nan, np.nan],
"C": [np.nan, np.nan, np.nan, np.nan, np.nan, 3.0],
},
index=pd.Index(["foo", "bar", "cat", "dat", "f", "g"]),
)
tm.assert_frame_equal(result, expected)

# test on partial, functools or more complex cases
result = df.agg(foo=("A", np.mean), bar=("A", "mean"), cat=("A", min))
expected = pd.DataFrame(
{"A": [1.5, 1.5, 1.0]}, index=pd.Index(["foo", "bar", "cat"])
)
tm.assert_frame_equal(result, expected)

result = df.agg(
foo=("A", min),
bar=("A", np.min),
cat=("B", max),
dat=("C", "min"),
f=("B", np.sum),
)
expected = pd.DataFrame(
{
"A": [1.0, 1.0, np.nan, np.nan, np.nan],
"B": [np.nan, np.nan, 10.0, np.nan, 4.0],
"C": [np.nan, np.nan, np.nan, 3.0, np.nan],
},
index=pd.Index(["foo", "bar", "cat", "dat", "f"]),
)
tm.assert_frame_equal(result, expected)

def test_agg_namedtuple(self):
df = pd.DataFrame({"A": [0, 1], "B": [1, 2]})
result = df.agg(
foo=pd.NamedAgg("B", "sum"),
bar=pd.NamedAgg("B", min),
cat=pd.NamedAgg(column="B", aggfunc="count"),
fft=pd.NamedAgg("B", aggfunc="max"),
)

expected = pd.DataFrame(
{"B": [3, 1, 2, 2]}, index=pd.Index(["foo", "bar", "cat", "fft"])
)
tm.assert_frame_equal(result, expected)

result = df.agg(
foo=pd.NamedAgg("A", "min"),
bar=pd.NamedAgg(column="B", aggfunc="max"),
cat=pd.NamedAgg(column="A", aggfunc="max"),
)
expected = pd.DataFrame(
{"A": [0.0, np.nan, 1.0], "B": [np.nan, 2.0, np.nan]},
index=pd.Index(["foo", "bar", "cat"]),
)
tm.assert_frame_equal(result, expected)

def test_agg_raises(self):
df = pd.DataFrame({"A": [0, 1], "B": [1, 2]})
msg = "Must provide"

with pytest.raises(TypeError, match=msg):
df.agg()
31 changes: 31 additions & 0 deletions pandas/tests/series/test_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,3 +750,34 @@ def test_apply_scaler_on_date_time_index_aware_series(self):
series = tm.makeTimeSeries(nper=30).tz_localize("UTC")
result = pd.Series(series.index).apply(lambda x: 1)
tm.assert_series_equal(result, pd.Series(np.ones(30), dtype="int64"))


class TestNamedAggregation:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same can you move to a new file

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do in followup PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you do here

def test_relabel_no_duplicated_method(self):
# this is to test there is no duplicated method used in agg
df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4]})

result = df["A"].agg(foo="sum")
expected = df["A"].agg({"foo": "sum"})
tm.assert_series_equal(result, expected)

result = df.B.agg(foo="min", bar="max")
expected = df.B.agg({"foo": "min", "bar": "max"})
tm.assert_series_equal(result, expected)

result = df.B.agg(foo=sum, bar=min, cat="max")
expected = df.B.agg({"foo": sum, "bar": min, "cat": "max"})
tm.assert_series_equal(result, expected)

def test_relabel_duplicated_method(self):
# this is to test with nested renaming, duplicated method can be used
# if they are assigned with different new names
df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4]})

result = df.A.agg(foo="sum", bar="sum")
expected = pd.Series([6, 6], index=["foo", "bar"], name="A")
tm.assert_series_equal(result, expected)

result = df.B.agg(foo=min, bar="min")
expected = pd.Series([1, 1], index=["foo", "bar"], name="B")
tm.assert_series_equal(result, expected)