Skip to content

Commit 92a1da6

Browse files
committed
Move _groupby_function inside GroupBy
Add support for __qualname__
1 parent 0520fc6 commit 92a1da6

File tree

2 files changed

+98
-76
lines changed

2 files changed

+98
-76
lines changed

pandas/core/groupby.py

+72-68
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
)
1313

1414
from pandas import compat
15-
from pandas.compat.numpy import function as nv
16-
from pandas.compat.numpy import _np_version_under1p8
15+
from pandas.compat.numpy import function as nv, _np_version_under1p8
16+
from pandas.compat import set_function_name
1717

1818
from pandas.types.common import (is_numeric_dtype,
1919
is_timedelta64_dtype, is_datetime64_dtype,
@@ -154,64 +154,6 @@
154154
'cummin', 'cummax'])
155155

156156

157-
def _groupby_function(name, alias, npfunc, numeric_only=True,
158-
_convert=False):
159-
160-
_local_template = "Compute %(f)s of group values"
161-
162-
@Substitution(name='groupby', f=name)
163-
@Appender(_doc_template)
164-
@Appender(_local_template)
165-
def f(self, **kwargs):
166-
if 'numeric_only' not in kwargs:
167-
kwargs['numeric_only'] = numeric_only
168-
self._set_group_selection()
169-
try:
170-
return self._cython_agg_general(alias, alt=npfunc, **kwargs)
171-
except AssertionError as e:
172-
raise SpecificationError(str(e))
173-
except Exception:
174-
result = self.aggregate(lambda x: npfunc(x, axis=self.axis))
175-
if _convert:
176-
result = result._convert(datetime=True)
177-
return result
178-
179-
f.__name__ = name
180-
181-
return f
182-
183-
184-
def _first_compat(x, axis=0):
185-
186-
def _first(x):
187-
188-
x = np.asarray(x)
189-
x = x[notnull(x)]
190-
if len(x) == 0:
191-
return np.nan
192-
return x[0]
193-
194-
if isinstance(x, DataFrame):
195-
return x.apply(_first, axis=axis)
196-
else:
197-
return _first(x)
198-
199-
200-
def _last_compat(x, axis=0):
201-
def _last(x):
202-
203-
x = np.asarray(x)
204-
x = x[notnull(x)]
205-
if len(x) == 0:
206-
return np.nan
207-
return x[-1]
208-
209-
if isinstance(x, DataFrame):
210-
return x.apply(_last, axis=axis)
211-
else:
212-
return _last(x)
213-
214-
215157
class Grouper(object):
216158
"""
217159
A Grouper allows the user to specify a groupby instruction for a target
@@ -1166,14 +1108,74 @@ def size(self):
11661108
result.name = getattr(self, 'name', None)
11671109
return result
11681110

1169-
sum = _groupby_function('sum', 'add', np.sum)
1170-
prod = _groupby_function('prod', 'prod', np.prod)
1171-
min = _groupby_function('min', 'min', np.min, numeric_only=False)
1172-
max = _groupby_function('max', 'max', np.max, numeric_only=False)
1173-
first = _groupby_function('first', 'first', _first_compat,
1174-
numeric_only=False, _convert=True)
1175-
last = _groupby_function('last', 'last', _last_compat, numeric_only=False,
1176-
_convert=True)
1111+
@classmethod
1112+
def _add_numeric_operations(cls):
1113+
""" add numeric operations to the GroupBy generically """
1114+
1115+
def _groupby_function(name, alias, npfunc,
1116+
numeric_only=True, _convert=False):
1117+
1118+
_local_template = "Compute %(f)s of group values"
1119+
1120+
@Substitution(name='groupby', f=name)
1121+
@Appender(_doc_template)
1122+
@Appender(_local_template)
1123+
def f(self, **kwargs):
1124+
if 'numeric_only' not in kwargs:
1125+
kwargs['numeric_only'] = numeric_only
1126+
self._set_group_selection()
1127+
try:
1128+
return self._cython_agg_general(alias, alt=npfunc, **kwargs)
1129+
except AssertionError as e:
1130+
raise SpecificationError(str(e))
1131+
except Exception:
1132+
result = self.aggregate(lambda x: npfunc(x, axis=self.axis))
1133+
if _convert:
1134+
result = result._convert(datetime=True)
1135+
return result
1136+
1137+
set_function_name(f, name, cls)
1138+
1139+
return f
1140+
1141+
def _first_compat(x, axis=0):
1142+
1143+
def _first(x):
1144+
1145+
x = np.asarray(x)
1146+
x = x[notnull(x)]
1147+
if len(x) == 0:
1148+
return np.nan
1149+
return x[0]
1150+
1151+
if isinstance(x, DataFrame):
1152+
return x.apply(_first, axis=axis)
1153+
else:
1154+
return _first(x)
1155+
1156+
1157+
def _last_compat(x, axis=0):
1158+
def _last(x):
1159+
1160+
x = np.asarray(x)
1161+
x = x[notnull(x)]
1162+
if len(x) == 0:
1163+
return np.nan
1164+
return x[-1]
1165+
1166+
if isinstance(x, DataFrame):
1167+
return x.apply(_last, axis=axis)
1168+
else:
1169+
return _last(x)
1170+
1171+
cls.sum = _groupby_function('sum', 'add', np.sum)
1172+
cls.prod = _groupby_function('prod', 'prod', np.prod)
1173+
cls.min = _groupby_function('min', 'min', np.min, numeric_only=False)
1174+
cls.max = _groupby_function('max', 'max', np.max, numeric_only=False)
1175+
cls.first = _groupby_function('first', 'first', _first_compat,
1176+
numeric_only=False, _convert=True)
1177+
cls.last = _groupby_function('last', 'last', _last_compat, numeric_only=False,
1178+
_convert=True)
11771179

11781180
@Substitution(name='groupby')
11791181
@Appender(_doc_template)
@@ -1585,6 +1587,8 @@ def tail(self, n=5):
15851587
mask = self._cumcount_array(ascending=False) < n
15861588
return self._selected_obj[mask]
15871589

1590+
GroupBy._add_numeric_operations()
1591+
15881592

15891593
@Appender(GroupBy.__doc__)
15901594
def groupby(obj, by, **kwds):

pandas/tests/groupby/test_groupby.py

+26-8
Original file line numberDiff line numberDiff line change
@@ -3849,19 +3849,36 @@ def test_groupby_whitelist(self):
38493849
'nsmallest',
38503850
])
38513851

3852-
names_dont_match_pair = (self.DF_METHOD_NAMES_THAT_DONT_MATCH_ATTRIBUTE,
3853-
self.S_METHOD_NAMES_THAT_DONT_MATCH_ATTRIBUTE)
3854-
for obj, whitelist, names_dont_match in zip((df, s), (df_whitelist, s_whitelist), names_dont_match_pair):
3852+
names_dont_match_pair = (
3853+
self.DF_METHOD_NAMES_THAT_DONT_MATCH_ATTRIBUTE,
3854+
self.S_METHOD_NAMES_THAT_DONT_MATCH_ATTRIBUTE)
3855+
for obj, whitelist, names_dont_match in (
3856+
zip((df, s),
3857+
(df_whitelist, s_whitelist),
3858+
names_dont_match_pair)):
3859+
38553860
gb = obj.groupby(df.letters)
3856-
self.assertEqual(whitelist, gb._apply_whitelist)
3861+
3862+
assert whitelist == gb._apply_whitelist
38573863
for m in whitelist:
38583864
f = getattr(type(gb), m)
3865+
3866+
# name
38593867
try:
38603868
n = f.__name__
38613869
except AttributeError:
38623870
continue
38633871
if m not in names_dont_match:
3864-
self.assertEqual(n, m)
3872+
assert n == m
3873+
3874+
# qualname
3875+
if compat.PY3:
3876+
try:
3877+
n = f.__qualname__
3878+
except AttributeError:
3879+
continue
3880+
if m not in names_dont_match:
3881+
assert n.endswith(m)
38653882

38663883
def test_groupby_method_names_that_dont_match_attribute(self):
38673884
from string import ascii_lowercase
@@ -3873,9 +3890,10 @@ def test_groupby_method_names_that_dont_match_attribute(self):
38733890
gb = df.groupby(df.letters)
38743891
s = df.floats
38753892

3876-
names_dont_match_pair = (self.DF_METHOD_NAMES_THAT_DONT_MATCH_ATTRIBUTE,
3877-
self.S_METHOD_NAMES_THAT_DONT_MATCH_ATTRIBUTE)
3878-
for obj, names_dont_match in zip((df, s), names_dont_match_pair):
3893+
names_dont_match_pair = (
3894+
self.DF_METHOD_NAMES_THAT_DONT_MATCH_ATTRIBUTE,
3895+
self.S_METHOD_NAMES_THAT_DONT_MATCH_ATTRIBUTE)
3896+
for obj, names_dont_match in zip((df, s), names_dont_match_pair):
38793897
gb = obj.groupby(df.letters)
38803898
for m in names_dont_match:
38813899
f = getattr(gb, m)

0 commit comments

Comments
 (0)