Skip to content

Commit a088c7b

Browse files
topper-123jreback
authored andcommitted
Let Resampler objects use have the pipe method (#18940)
1 parent 22601f7 commit a088c7b

File tree

5 files changed

+121
-50
lines changed

5 files changed

+121
-50
lines changed

doc/source/api.rst

+1
Original file line numberDiff line numberDiff line change
@@ -2274,6 +2274,7 @@ Function application
22742274
Resampler.apply
22752275
Resampler.aggregate
22762276
Resampler.transform
2277+
Resampler.pipe
22772278

22782279
Upsampling
22792280
~~~~~~~~~~

doc/source/whatsnew/v0.23.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ Other Enhancements
142142
- ``Categorical.rename_categories``, ``CategoricalIndex.rename_categories`` and :attr:`Series.cat.rename_categories`
143143
can now take a callable as their argument (:issue:`18862`)
144144
- :class:`Interval` and :class:`IntervalIndex` have gained a ``length`` attribute (:issue:`18789`)
145+
- ``Resampler`` objects now have a functioning :attr:`~pandas.core.resample.Resampler.pipe` method.
146+
Previously, calls to ``pipe`` were diverted to the ``mean`` method (:issue:`17905`).
145147

146148
.. _whatsnew_0230.api_breaking:
147149

pandas/core/groupby.py

+77-48
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,60 @@
191191
dtype: int64
192192
""")
193193

194+
_pipe_template = """\
195+
Apply a function ``func`` with arguments to this %(klass)s object and return
196+
the function's result.
197+
198+
%(versionadded)s
199+
200+
Use ``.pipe`` when you want to improve readability by chaining together
201+
functions that expect Series, DataFrames, GroupBy or Resampler objects.
202+
Instead of writing
203+
204+
>>> h(g(f(df.groupby('group')), arg1=a), arg2=b, arg3=c)
205+
206+
You can write
207+
208+
>>> (df.groupby('group')
209+
... .pipe(f)
210+
... .pipe(g, arg1=a)
211+
... .pipe(h, arg2=b, arg3=c))
212+
213+
which is much more readable.
214+
215+
Parameters
216+
----------
217+
func : callable or tuple of (callable, string)
218+
Function to apply to this %(klass)s object or, alternatively,
219+
a ``(callable, data_keyword)`` tuple where ``data_keyword`` is a
220+
string indicating the keyword of ``callable`` that expects the
221+
%(klass)s object.
222+
args : iterable, optional
223+
positional arguments passed into ``func``.
224+
kwargs : dict, optional
225+
a dictionary of keyword arguments passed into ``func``.
226+
227+
Returns
228+
-------
229+
object : the return type of ``func``.
230+
231+
Notes
232+
-----
233+
See more `here
234+
<http://pandas.pydata.org/pandas-docs/stable/groupby.html#pipe>`_
235+
236+
Examples
237+
--------
238+
%(examples)s
239+
240+
See Also
241+
--------
242+
pandas.Series.pipe : Apply a function with arguments to a series
243+
pandas.DataFrame.pipe: Apply a function with arguments to a dataframe
244+
apply : Apply function to each group instead of to the
245+
full %(klass)s object.
246+
"""
247+
194248
_transform_template = """
195249
Call function producing a like-indexed %(klass)s on each group and
196250
return a %(klass)s having the same indexes as the original object
@@ -676,6 +730,29 @@ def __getattr__(self, attr):
676730
raise AttributeError("%r object has no attribute %r" %
677731
(type(self).__name__, attr))
678732

733+
@Substitution(klass='GroupBy',
734+
versionadded='.. versionadded:: 0.21.0',
735+
examples="""\
736+
>>> df = pd.DataFrame({'A': 'a b a b'.split(), 'B': [1, 2, 3, 4]})
737+
>>> df
738+
A B
739+
0 a 1
740+
1 b 2
741+
2 a 3
742+
3 b 4
743+
744+
To get the difference between each groups maximum and minimum value in one
745+
pass, you can do
746+
747+
>>> df.groupby('A').pipe(lambda x: x.max() - x.min())
748+
B
749+
A
750+
a 2
751+
b 2""")
752+
@Appender(_pipe_template)
753+
def pipe(self, func, *args, **kwargs):
754+
return _pipe(self, func, *args, **kwargs)
755+
679756
plot = property(GroupByPlot)
680757

681758
def _make_wrapper(self, name):
@@ -1779,54 +1856,6 @@ def tail(self, n=5):
17791856
mask = self._cumcount_array(ascending=False) < n
17801857
return self._selected_obj[mask]
17811858

1782-
def pipe(self, func, *args, **kwargs):
1783-
""" Apply a function with arguments to this GroupBy object,
1784-
1785-
.. versionadded:: 0.21.0
1786-
1787-
Parameters
1788-
----------
1789-
func : callable or tuple of (callable, string)
1790-
Function to apply to this GroupBy object or, alternatively, a
1791-
``(callable, data_keyword)`` tuple where ``data_keyword`` is a
1792-
string indicating the keyword of ``callable`` that expects the
1793-
GroupBy object.
1794-
args : iterable, optional
1795-
positional arguments passed into ``func``.
1796-
kwargs : dict, optional
1797-
a dictionary of keyword arguments passed into ``func``.
1798-
1799-
Returns
1800-
-------
1801-
object : the return type of ``func``.
1802-
1803-
Notes
1804-
-----
1805-
Use ``.pipe`` when chaining together functions that expect
1806-
Series, DataFrames or GroupBy objects. Instead of writing
1807-
1808-
>>> f(g(h(df.groupby('group')), arg1=a), arg2=b, arg3=c)
1809-
1810-
You can write
1811-
1812-
>>> (df
1813-
... .groupby('group')
1814-
... .pipe(f, arg1)
1815-
... .pipe(g, arg2)
1816-
... .pipe(h, arg3))
1817-
1818-
See more `here
1819-
<http://pandas.pydata.org/pandas-docs/stable/groupby.html#pipe>`_
1820-
1821-
See Also
1822-
--------
1823-
pandas.Series.pipe : Apply a function with arguments to a series
1824-
pandas.DataFrame.pipe: Apply a function with arguments to a dataframe
1825-
apply : Apply function to each group instead of to the
1826-
full GroupBy object.
1827-
"""
1828-
return _pipe(self, func, *args, **kwargs)
1829-
18301859

18311860
GroupBy._add_numeric_operations()
18321861

pandas/core/resample.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from pandas.core.base import AbstractMethodError, GroupByMixin
99

1010
from pandas.core.groupby import (BinGrouper, Grouper, _GroupBy, GroupBy,
11-
SeriesGroupBy, groupby, PanelGroupBy)
11+
SeriesGroupBy, groupby, PanelGroupBy,
12+
_pipe_template)
1213

1314
from pandas.tseries.frequencies import to_offset, is_subperiod, is_superperiod
1415
from pandas.core.indexes.datetimes import DatetimeIndex, date_range
@@ -26,7 +27,7 @@
2627
from pandas._libs.lib import Timestamp
2728
from pandas._libs.tslibs.period import IncompatibleFrequency
2829

29-
from pandas.util._decorators import Appender
30+
from pandas.util._decorators import Appender, Substitution
3031
from pandas.core.generic import _shared_docs
3132
_shared_docs_kwargs = dict()
3233

@@ -257,6 +258,29 @@ def _assure_grouper(self):
257258
""" make sure that we are creating our binner & grouper """
258259
self._set_binner()
259260

261+
@Substitution(klass='Resampler',
262+
versionadded='.. versionadded:: 0.23.0',
263+
examples="""
264+
>>> df = pd.DataFrame({'A': [1, 2, 3, 4]},
265+
... index=pd.date_range('2012-08-02', periods=4))
266+
>>> df
267+
A
268+
2012-08-02 1
269+
2012-08-03 2
270+
2012-08-04 3
271+
2012-08-05 4
272+
273+
To get the difference between each 2-day period's maximum and minimum value in
274+
one pass, you can do
275+
276+
>>> df.resample('2D').pipe(lambda x: x.max() - x.min())
277+
A
278+
2012-08-02 1
279+
2012-08-04 1""")
280+
@Appender(_pipe_template)
281+
def pipe(self, func, *args, **kwargs):
282+
return super(Resampler, self).pipe(func, *args, **kwargs)
283+
260284
def plot(self, *args, **kwargs):
261285
# for compat with prior versions, we want to
262286
# have the warnings shown here and just have this work

pandas/tests/test_resample.py

+15
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,21 @@ def test_groupby_resample_on_api(self):
235235
result = df.groupby('key').resample('D', on='dates').mean()
236236
assert_frame_equal(result, expected)
237237

238+
def test_pipe(self):
239+
# GH17905
240+
241+
# series
242+
r = self.series.resample('H')
243+
expected = r.max() - r.mean()
244+
result = r.pipe(lambda x: x.max() - x.mean())
245+
tm.assert_series_equal(result, expected)
246+
247+
# dataframe
248+
r = self.frame.resample('H')
249+
expected = r.max() - r.mean()
250+
result = r.pipe(lambda x: x.max() - x.mean())
251+
tm.assert_frame_equal(result, expected)
252+
238253
@td.skip_if_no_mpl
239254
def test_plot_api(self):
240255
# .resample(....).plot(...)

0 commit comments

Comments
 (0)