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 58 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
225 changes: 225 additions & 0 deletions pandas/core/aggregation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
from collections import defaultdict
from functools import partial
from typing import Any, DefaultDict, Sequence

from pandas.core.dtypes.common import is_dict_like, is_list_like

from pandas.core.base import SpecificationError
import pandas.core.common as com
from pandas.core.indexes.api import Index


def reconstruct_func(func, *args, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

Can you annotate this signature? The return is pretty complicated so would be better to explicitly state what that is

Copy link
Member Author

@charlesdong1991 charlesdong1991 Jan 25, 2020

Choose a reason for hiding this comment

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

ok, I will try to make it!

"""
This is the internal function to reconstruct func given if there is relabeling
or not. And also normalize the keyword to get new order of columns;

If relabeling is True, will return relabeling, reconstructed func, column
names, and the reconstructed order of columns.
If relabeling is False, the columns and order will be None.
"""
relabeling = func is None and is_multi_agg_with_relabel(**kwargs)
if relabeling:
func, columns, order = normalize_keyword_aggregation(kwargs)

elif isinstance(func, list) and len(func) > len(set(func)):
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 these conditions at the top? it makes the logic easier to grok

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, moved it up


# GH 28426 will raise error if duplicated function names are used and
# there is no reassigned name
raise SpecificationError(
"Function names must be unique if there is no new column names assigned"
)
elif func is None:
# nicer error message
raise TypeError("Must provide 'func' or tuples of '(column, aggfunc).")

func = maybe_mangle_lambdas(func)
if not relabeling:
columns = None
order = None

return relabeling, func, columns, order


def is_multi_agg_with_relabel(**kwargs) -> bool:
"""
Check whether kwargs passed to .agg look like multi-agg with relabeling.

Parameters
----------
**kwargs : dict

Returns
-------
bool

Examples
--------
>>> _is_multi_agg_with_relabel(a='max')
False
>>> _is_multi_agg_with_relabel(a_max=('a', 'max'),
... a_min=('a', 'min'))
True
>>> _is_multi_agg_with_relabel()
False
"""
return all(isinstance(v, tuple) and len(v) == 2 for v in kwargs.values()) and (
len(kwargs) > 0
)


def normalize_keyword_aggregation(kwargs):
"""
Normalize user-provided "named aggregation" kwargs.

Transforms from the new ``Mapping[str, NamedAgg]`` style kwargs
to the old Dict[str, List[scalar]]].

Parameters
----------
kwargs : dict

Returns
-------
aggspec : dict
The transformed kwargs.
columns : List[str]
The user-provided keys.
col_idx_order : List[int]
List of columns indices.

Examples
--------
>>> _normalize_keyword_aggregation({'output': ('input', 'sum')})
Copy link
Contributor

Choose a reason for hiding this comment

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

needs updating here

Copy link
Member Author

Choose a reason for hiding this comment

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

changed this kind of mistakes for all in docstring #30856

({'input': ['sum']}, ('output',), [('input', 'sum')])
"""
# Normalize the aggregation functions as Mapping[column, List[func]],
# process normally, then fixup the names.
# TODO: aggspec type: typing.Dict[str, List[AggScalar]]
# May be hitting https://github.com/python/mypy/issues/5958
# saying it doesn't have an attribute __name__
aggspec: DefaultDict = defaultdict(list)
order = []
columns, pairs = list(zip(*kwargs.items()))

for name, (column, aggfunc) in zip(columns, pairs):
aggspec[column].append(aggfunc)
order.append((column, com.get_callable_name(aggfunc) or aggfunc))

# uniquify aggfunc name if duplicated in order list
uniquified_order = _make_unique(order)

# GH 25719, due to aggspec will change the order of assigned columns in aggregation
# uniquified_aggspec will store uniquified order list and will compare it with order
# based on index
aggspec_order = [
(column, com.get_callable_name(aggfunc) or aggfunc)
for column, aggfuncs in aggspec.items()
for aggfunc in aggfuncs
]
uniquified_aggspec = _make_unique(aggspec_order)

# get the new indice of columns by comparison
col_idx_order = Index(uniquified_aggspec).get_indexer(uniquified_order)
return aggspec, columns, col_idx_order


def _make_unique(seq):
"""Uniquify aggfunc name of the pairs in the order list

Examples:
--------
>>> _make_unique([('a', '<lambda>'), ('a', '<lambda>'), ('b', '<lambda>')])
[('a', '<lambda>_0'), ('a', '<lambda>_1'), ('b', '<lambda>')]
"""
return [
(pair[0], "_".join([pair[1], str(seq[:i].count(pair))]))
if seq.count(pair) > 1
else pair
for i, pair in enumerate(seq)
]


# TODO: Can't use, because mypy doesn't like us setting __name__
# error: "partial[Any]" has no attribute "__name__"
# the type is:
# typing.Sequence[Callable[..., ScalarResult]]
# -> typing.Sequence[Callable[..., ScalarResult]]:


def _managle_lambda_list(aggfuncs: Sequence[Any]) -> Sequence[Any]:
"""
Possibly mangle a list of aggfuncs.

Parameters
----------
aggfuncs : Sequence

Returns
-------
mangled: list-like
A new AggSpec sequence, where lambdas have been converted
to have unique names.

Notes
-----
If just one aggfunc is passed, the name will not be mangled.
"""
if len(aggfuncs) <= 1:
# don't mangle for .agg([lambda x: .])
return aggfuncs
i = 0
mangled_aggfuncs = []
for aggfunc in aggfuncs:
if com.get_callable_name(aggfunc) == "<lambda>":
aggfunc = partial(aggfunc)
aggfunc.__name__ = f"<lambda_{i}>"
i += 1
mangled_aggfuncs.append(aggfunc)

return mangled_aggfuncs


def maybe_mangle_lambdas(agg_spec: Any) -> Any:
"""
Make new lambdas with unique names.

Parameters
----------
agg_spec : Any
An argument to GroupBy.agg.
Non-dict-like `agg_spec` are pass through as is.
For dict-like `agg_spec` a new spec is returned
with name-mangled lambdas.

Returns
-------
mangled : Any
Same type as the input.

Examples
--------
>>> _maybe_mangle_lambdas('sum')
'sum'

>>> _maybe_mangle_lambdas([lambda: 1, lambda: 2]) # doctest: +SKIP
[<function __main__.<lambda_0>,
<function pandas...._make_lambda.<locals>.f(*args, **kwargs)>]
"""
is_dict = is_dict_like(agg_spec)
if not (is_dict or is_list_like(agg_spec)):
return agg_spec
mangled_aggspec = type(agg_spec)() # dict or OrderdDict

if is_dict:
for key, aggfuncs in agg_spec.items():
if is_list_like(aggfuncs) and not is_dict_like(aggfuncs):
mangled_aggfuncs = _managle_lambda_list(aggfuncs)
else:
mangled_aggfuncs = aggfuncs

mangled_aggspec[key] = mangled_aggfuncs
else:
mangled_aggspec = _managle_lambda_list(agg_spec)

return mangled_aggspec
26 changes: 25 additions & 1 deletion pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@

from pandas.core import algorithms, common as com, nanops, ops
from pandas.core.accessor import CachedAccessor
from pandas.core.aggregation import reconstruct_func
from pandas.core.arrays import Categorical, ExtensionArray
from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin as DatetimeLikeArray
from pandas.core.arrays.sparse import SparseFrameAccessor
Expand Down Expand Up @@ -6618,16 +6619,39 @@ 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):
axis = self._get_axis_number(axis)

relabeling, func, columns, order = reconstruct_func(func, *args, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

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

is this function also used in groupby? did you already make the appropriate changes there?

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, this is exactly the same as part of agg in groupby/generic.py, and later should be used for resample/rolling agg. Shall I replace the corresponding part in groupby/generic.py with this function in this PR? i was supposed to do that in a follow-up to make this PR smaller, but maybe it's better to do it here, will do then!

if relabeling:
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


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:
reordered_indexes = [
pair[0] for pair in sorted(zip(columns, order), key=lambda t: t[1])
]

# 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=columns)
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
Loading