Skip to content

ENH: Add Styler.pipe() method (#23229) #23384

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 18 commits into from
Nov 28, 2018
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
1 change: 1 addition & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2481,6 +2481,7 @@ Style Application
Styler.set_properties
Styler.set_uuid
Styler.clear
Styler.pipe

Builtin Styles
~~~~~~~~~~~~~~
Expand Down
26 changes: 26 additions & 0 deletions doc/source/whatsnew/v0.24.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,31 @@ array, but rather an ``ExtensionArray``:
This is the same behavior as ``Series.values`` for categorical data. See
:ref:`whatsnew_0240.api_breaking.interval_values` for more.


.. _whatsnew_0240.enhancements.styler_pipe:

New ``Styler.pipe()`` method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :class:`~pandas.io.formats.style.Styler` class has gained a
:meth:`~pandas.io.formats.style.Styler.pipe` method (:issue:`23229`). This provides a
convenient way to apply users' predefined styling functions, and can help reduce
"boilerplate" when using DataFrame styling functionality repeatedly within
a notebook.

.. ipython:: python

def set_summary_style(styler):
return (styler.format({'N': '{:,}', 'X': '{:.1%}'})
.set_properties(**{'text-align': 'right'}))

(df.style.set_precision(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

This code is executed. I think the exception at https://travis-ci.org/pandas-dev/pandas/jobs/453316565#L2108 is from this. I'd recommend defining df in this code block.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

.pipe(set_summary_style)
.set_caption('Summary of results.'))

Similar methods already exist for other classes in pandas, including :meth:`DataFrame.pipe`,
:meth:`Groupby.pipe`, and :meth:`Resampler.pipe`.


.. _whatsnew_0240.enhancements.rename_axis:

Renaming names in a MultiIndex
Expand All @@ -202,6 +227,7 @@ Example:

See the :ref:`advanced docs on renaming<advanced.index_names>` for more details.


.. _whatsnew_0240.enhancements.other:

Other Enhancements
Expand Down
72 changes: 72 additions & 0 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,78 @@ class MyStyler(cls):

return MyStyler

def pipe(self, func, *args, **kwargs):
"""
Apply ``func(self, *args, **kwargs)``, and return the result.

.. versionadded:: 0.24.0

Parameters
----------
func : function
Function to apply to the Styler.
``*args``, and ``**kwargs`` are passed into ``func``.
Alternatively a ``(callable, data_keyword)`` tuple where
``data_keyword`` is a string indicating the keyword of
``callable`` that expects the Styler.
*args : iterable, optional
Positional arguments passed into ``func``.
**kwargs : dict, optional
Dictionary of keyword arguments passed into ``func``.
Copy link
Member

Choose a reason for hiding this comment

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

You can merge args and kwargs in a single line, and you don't need types or optional, as those are always the caseÑ:

*args, **kwargs
   Arguments passed to `func`.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed as suggested. As a result of these changes, the validate_docstring.py script reports an error, but I agree this is better.


Returns
-------
result : object
The value returned by ``func``.

See Also
--------
DataFrame.pipe : Analogous method for DataFrame.
Styler.apply : Apply a function row-wise, column-wise, or table-wise to
modify the dataframe's styling.

Notes
-----
Like :meth:`DataFrame.pipe`, this method can simplify the
application of several user-defined functions to a styler. Instead
of writing:

.. code-block:: python

f(g(df.style.set_precision(3), arg1=a), arg2=b, arg3=c)

users can write:

.. code-block:: python

(df.style.set_precision(3)
.pipe(g, arg1=a)
.pipe(f, arg2=b, arg3=c))

In particular, this allows users to define functions that take a
styler object, along with other parameters, and return the styler after
making styling changes (such as calling :meth:`Styler.apply` or
:meth:`Styler.set_properties`). Using ``.pipe``, these user-defined
style "transformations" can be interleaved with calls to the built-in
Styler interface.

Examples
--------
>>> def set_standard_formatting(styler):
... return (styler.set_properties(**{'text-align': 'right'})
... .format({'X': '{:.1%}'}))

The user-defined highlight function above can be called within a
sequence of other style modifications:

>>> df = pd.DataFrame({'A': list(range(-1, 4)), 'X': np.arange(0.2, 1.2, 0.2)})
Copy link
Member

Choose a reason for hiding this comment

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

Use something that looks real, no range fuctions, and no a, x, foo columns. We start to have many examples with animal names (e.g. cat, penguin, falcon...). I'd use that for consistency (hopefully at some point we have all the examples with similar data). Just have couple of columns that are percentages, and 2 or 3 rows.

Then rename set_standard_formatting to percentage_format (if I understand correctly, that's what it is, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made some changes here, and fixed the reference in the text, which was incorrect. I changed the function name as you suggested, while trying to make it clear that the function was user-defined and application-specific.

I'll be honest, using animal names in examples seems a little silly to me, but that's up to the pandas developers.

In this case, using numeric data rather than strings made more sense to me, since numeric data presents styling/formatting choices, and special values (min/max/null) can be highlighted.

>>> (df.style
... .set_properties(subset=['X'], **{'background-color': 'yellow'})
... .pipe(set_standard_formatting)
... .set_caption("Results with column 'X' highlighted."))
"""
return com._pipe(self, func, *args, **kwargs)


def _is_visible(idx_row, idx_col, lengths):
"""
Expand Down
16 changes: 16 additions & 0 deletions pandas/tests/io/formats/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,22 @@ def test_hide_columns_mult_levels(self):
assert ctx['body'][1][2]['is_visible']
assert ctx['body'][1][2]['display_value'] == 3

def test_pipe(self):
def set_caption_from_template(styler, a, b):
return styler.set_caption(
'Dataframe with a = {a} and b = {b}'.format(a=a, b=b))

styler = self.df.style.pipe(set_caption_from_template, 'A', b='B')
assert 'Dataframe with a = A and b = B' in styler.render()

# Test with an argument that is a (callable, keyword_name) pair.
def f(a, b, styler):
return (a, b, styler)

styler = self.df.style
result = styler.pipe((f, 'styler'), a=1, b=2)
assert result == (1, 2, styler)


@td.skip_if_no_mpl
class TestStylerMatplotlibDep(object):
Expand Down