Skip to content
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

Styler class should have a `pipe()` method, akin to DataFrame.pipe() #23229

Closed
nmusolino opened this issue Oct 18, 2018 · 3 comments

Comments

Projects
None yet
3 participants
@nmusolino
Copy link
Contributor

commented Oct 18, 2018

Problem description

Users may want to create functions that apply customized styling to a dataframe or styler object.

In order to make these functions composable, a convenient signature is f(styler) -> Styler, so that a user can chain multiple such functions together, in a "mix-and-match" fashion. (N.B. Such functions can obtain the underlying dataframe using the styler.data attribute.)

A method Styler.pipe() would provide an ergonomic way to apply these functions sequentially, after a series of Dataframe-related manipulations.

Usage example:

def right_align(styler):
    return styler.set_properties(**{'text-align': 'right'})

def highlight_rows_gt(styler, colname, threshold):
    return ...        # implementation omitted

>>> (df.groupby('A')[['B', 'C']].sum()
...        .assign(D=lambda x: x['B'] / x['C'])
...        .style
...        .format('{:.2f}')
...        .pipe(highlight_rows_gt, colname='D', threshold=0.95)
...        .pipe(right_align))
@gfyoung

This comment has been minimized.

Copy link
Member

commented Oct 27, 2018

I think this proposition seems reasonable, unless there is some workaround already.

cc @jreback @TomAugspurger

@nmusolino

This comment has been minimized.

Copy link
Contributor Author

commented Oct 28, 2018

Thanks very much, @gfyoung, for the feedback and the review on the corresponding pull request (#23384).

I can elaborate on the two major use cases I had in mind for this:
I. An explicit goal for the style functionality is extensibility. As noted above, users (or library developers) can write functions that perform certain style transformations, possibly application-specific, using the core Styler functions in pandas. The pipe() method would provide an ergonomic way to compose or "mix-and-match" these user-defined transformations.

II. As a variant of (I), users may find themselves attempting to style many different views of data in a consistent way within a notebook. For example, users might want to present two different kinds of results (e.g. summary results and detailed results) while grouping by three different columns in their data frame, for a total of six tables being created/styled. In that case, the Styler.pipe() method can help reduce the "style boilerplate": a user can place all the common styling operations in a function within the notebook (or elsewhere), and perform specific styling operations (such as captions, or highlighting specific data problems) in each individual cell.

Here's an example (untested code). Note that in general, there may be a series of operations performed on a data frame to get the final result frame, before any styling is applied.

def format_summary(styler, grouping_text):
    return (styler.set_properties(**{'text-align': 'right'})
                  .format({'N': '{:,d}', 'ResponseRate': '{:.1%}', 'Date': '{:%Y-%m-%d}')
                  .set_caption("Summary of results, grouped by {}.".format(grouping_text)))   

# Notebook cell
(df.groupby('A').apply(calculate_summary)
   .style.pipe(format_summary, grouping_text="attribute A"))

# Notebook cell
(df.groupby('B').apply(calculate_summary)
   .style.pipe(format_summary, grouping_text="attribute B"))

# Notebook cell.  This was a special case, so apply extra styling to explain details to readers.
(df.groupby('C').apply(calculate_summary)
   .style.pipe(format_summary, grouping_text="C"))
   .set_caption("Summary of results, grouped by 'C'.  Note that the group with C equal to 4 had insufficient number of samples to estimate results.")
   .apply(lambda row: ['background-color: yellow'] * len(row), subset=pandas.IndexSlice[4, :], axis=0))
@gfyoung

This comment has been minimized.

Copy link
Member

commented Oct 28, 2018

@nmusolino : Thanks for the explanation! Now if you could take some of this and put it in the whatsnew, that would be great. 👍

TomAugspurger added a commit that referenced this issue Nov 28, 2018

ENH: Add Styler.pipe() method (#23229) (#23384)
* Add Styler.pipe() method, akin to DataFrame.pipe()

Pingviinituutti added a commit to Pingviinituutti/pandas that referenced this issue Feb 28, 2019

ENH: Add Styler.pipe() method (pandas-dev#23229) (pandas-dev#23384)
* Add Styler.pipe() method, akin to DataFrame.pipe()

Pingviinituutti added a commit to Pingviinituutti/pandas that referenced this issue Feb 28, 2019

ENH: Add Styler.pipe() method (pandas-dev#23229) (pandas-dev#23384)
* Add Styler.pipe() method, akin to DataFrame.pipe()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.