ENH: Styler column and row styles
State: closed

- [x] closes #35605 
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

Labels:
name: Styler
description: conditional formatting using DataFrame.style
name: Stale

Comments:
> i am not sure we want to be adding api like this, rather can you use a keyword arg to .set_table_styles?

One of the problems with set_table_styles is that it is a one-time function. Any repeated activity will simply overwrite previous styling, so the user is required to custom build up all style functions and then add at the end. No column argument will avoid this complexity.

One of the functions in this commit is literally just an extension, `extend_table_styles` which doesn't overwrite. I think this will be necessary then even if an argument solution to the problem is sought or not.

I could modify the code to accept an argument `column` into `extend_table_styles`, which repeats the code from the secondary function:

```
def extend_table_styles(table_styles, column=None):
    if column is not None:
        _styles = []
        for s in table_styles:
            c = str(self.data.columns.get_loc(col))
            _styles.append(
                    {"selector": s["selector"] + ".col" + c, "props": s["props"]}
            )
    else:
        _styles = table_styles
    if self.table_styles is None:
            return self.set_table_styles(table_styles)
    self.table_styles.extend(table_styles)
    return self
```

This would work for a single column and could be integrated into a user loop over different columns.

##Discussion

But since the purpose of Styler is to provide display capability, and it currently does not offer the ability to directly attach named CSS classes to cells, or rows or columns, I do not understand why you would shy away from providing the functionality to directly style a column in the most efficient manner. This can be extended to rows later.

It seems to be quite a basic functionality that has been requested in other instances that is easily crafted with current tools, and the direct purpose of styling columns is quite an easy user functionality to grasp???
    


I had a think on your comment @jreback. The addition of multiple arguments to the `set_table_styles` method allowed all of this functionality to be rolled up into the same function, which I think is what you are after, and in hindsight I think it is better also.

It also had the advantage of being able to address row styling at the same time as column styling.

Tests and examples have been amended.
conversations/changes resolved or comments added, all green, @jreback .. ping.
Seems good to me.

On Fri, Aug 14, 2020 at 3:51 AM attack68 <notifications@github.com> wrote:

> conversations/changes resolved or comments added, all green, @jreback
> <https://github.com/jreback> .. ping.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/pull/35607#issuecomment-673967424>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAKAOIVAMEQX334OFN7337DSAT3KBANCNFSM4PXTRRDA>
> .
>

@jreback reviving this after a couple of weeks, all green, and changes requested implemented.
@jreback keeping this one alive...
can you merge master. I understand what `overwrite` means here, but we don't actually use this *anywhere* else in pandas.

I think it might be better to use `inplace` (though not a fan of this either), and prefer chaining to append.

cc @TomAugspurger 
This is a strange one.

The problem with `inplace` is that Pandas typically have object methods which return new objects, and the underlying object is unaffected. If `inplace` is True then the underlying object is permanently affected and the return is `None`.

In `Styler`, this is not really how it was designed or works. I would say there are 6 possibilities for how this method might work. For `set_table_styles(..)`:

i) 
The underlying `Styler` is **unaffected** and a new `Styler` is returned with **only** the argument attributes set.

ii) The underlying `Styler` is **affected with only** the argument attributes set and a self `Styler` returned .
iii) 
The underlying `Styler` is **affected with only** the argument attributes set and no return value.

iv) 
The underlying `Styler` is **unaffected** and a new `Styler` is returned with the **new attributes extended** to the underlying.

v) The underlying `Styler` is **affected with new attributes extended** and a self `Styler` is returned.
vi) 
The underlying `Styler` is **affected with new attributes extended** to the underlying and no return value.


Currently, `Styler` has no methods that ever do i) or iv). All methods always seem to update itself `inplace`.<br>
Also `Styler` never returns None, so this would seem to rule out iii) and vi).<br>

`set_table_styles` actually does ii) which is a kind of `inplace=True` with a return, which seems quite unconventional for Pandas. That is why I don't think the `inplace` argument really works here. And to get the additional behaviour of v) which is what we want left the only other options of either: adding the `overwrite` argument (see here.. https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.combine.html), or creating another method: `extend_table_styles` or `add_table_styles` would be better??

FYI part of the design structure I think was to maintain a self contained object as a property: `df.style` which then developed the ability to be manipulated but always `inplace` by design.
Fixed the merge conflict in the whatsnew. I think this was OK last I looked.
This pull request is stale because it has been open for thirty days with no activity. Please update or respond to this comment if you're still interested in working on this.
I merged master into this again.

Only outstanding issue is acceptance, or not, of the `override` keyword. 

I stand by this, rather than `inplace`, which I think is misplaced here, as explained above.
thanks @attack68