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

QST: Styler puts all CSS ids on a single attribute which is not rendered #39400

Closed
NewtonXu opened this issue Jan 25, 2021 · 4 comments
Closed
Labels
Styler conditional formatting using DataFrame.style Usage Question

Comments

@NewtonXu
Copy link

  • [X ] I have searched the [pandas] tag on StackOverflow for similar questions.

  • [X ] I have asked my usage related question on StackOverflow.


Question about pandas

I am trying to use the pandas dataframe styler to colour cells of the dataframe and present the result on a webpage.
The rendered CSS shows up as follows:

#T_394319f6_5c20_11eb_832b_04ed338ce712row0_col6,#T_394319f6_5c20_11eb_832b_04ed338ce712row0_col7, 
...
#T_394319f6_5c20_11eb_832b_04ed338ce712row1_col6 { background-colour: red }

For large tables, the number of ids can exceed 4096. When processed on the web-browser, the id's after 4096 are ignored and the cells are not coloured. Resulting in partially coloured HTML tables.

I have verified my style function works by downloading the Excel file of the styled object, which is coloured correctly. It seems to be a limitation on various web-browsers (Chrome, Firefox, Edge).

I have also tried randomizing the exact hexcode used to colour the cells, which fixes the problem.

What can I do?

@NewtonXu NewtonXu added Needs Triage Issue that has not been reviewed by a pandas team member Usage Question labels Jan 25, 2021
@attack68
Copy link
Contributor

import pandas as pd
from pandas.io.formats.style import Styler
import numpy as np

def color(v):
    if v > 0.75:
        return 'color: red;'
    return None
def bold(v):
    if v > 0.6 and v < 0.85:
        return 'font-weight: bold;'
    return None

df = pd.DataFrame(np.random.rand(5000, 5))
s = Styler(df, uuid_len=0, cell_ids=False).applymap(color).applymap(bold)

I can see from your id that you are using an older version of pandas. Recent updates have made the id shorter for greater efficiency. I rendered the above table with 25,000 cells in chrome with no issues.

I would highlight a few things to you:

  1. Use cell_ids=False and uuid_len=0. This prevents writing id attributes for cells that need no styling and avoids stupidly long names. In my case:
Styler(df, uuid_len=0, cell_ids=False).. --> 2.8 Mb transfer
Styler(df, uuid_len=32, cell_ids=True).. --> 4.3 Mb transfer
  1. Use table_styles where you can: these add classes to rows or columns (not individual cells) so if you have those groupings its best to use them.

  2. There is a new method set_td_classes which allows you to refer to external css classes. (a bugfix is in 1.3.0) so you could also make changes like the following:

cls1 = pd.DataFrame(np.where(df.values > 0.75, 'cls1 ', ''))
cls2 = pd.DataFrame(np.where((df.values > 0.6) & (df.values < 0.85), 'cls2', ''))
s = Styler(df, uuid_len=0, cell_ids=False).set_td_classes(cls1 + cls2)

And then make sure you prepended the manual external css classes so:

render = "<style type="text/css">
  .cls1 {color: red;}
  .cls2 {font-weight: bold;}
</style>" + s.render()

Let us know how you get on - its useful for development..

@simonjayhawkins simonjayhawkins added Styler conditional formatting using DataFrame.style and removed Needs Triage Issue that has not been reviewed by a pandas team member labels Jan 26, 2021
@NewtonXu
Copy link
Author

NewtonXu commented Jan 27, 2021

Styler(df, uuid_len=0, cell_ids=False).apply(highlight_min_max, axis=1)

def highlight_min_max(row):
    ret = ["" for _ in row.index]
    if row['key1'] > row['key2']:
        ret[row.index.get_loc('key3')] = 'background-color: red'
    return ret

Here is an example of what I am trying to do. In my highlight_min_max, the colour I highlight cells with are determined by other cells, hence why I use the apply function. So I unfortunately cannot take advantage of applying colouring to entire rows or columns.

I have updated my pandas to 1.2.1 and I still see the same problem with the above code.

@attack68
Copy link
Contributor

attack68 commented Jan 27, 2021

My example previously didn't have enough elements apparently.

def write_html(s):
    html1 = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body>'
    html2 = '</body></html>'
    with open("output.html", "w") as file:
        file.write(html1 + s.render() + html2)

def highlight_min_max(col, c1, c2):
    """styles a column based on elements in c1 compared with c2"""
    return np.where(c1 < c2 + 1, 'background-color: yellow;', None)

df = pd.DataFrame(np.random.rand(20000, 3), columns=['key1', 'key2', 'key3'])
s = Styler(df, uuid_len=0, cell_ids=False).apply(highlight_min_max, axis=0, c1=df['key1'], c2=df['key2'], subset=['key3'])
write_html(s)

generates an HTML file where there are only 8192 (13 bits) styled cells of 20000 that should be styled. There is nothing wrong with the HTML. Therefore I suggest this is a browser limitation issue, not pandas. Update: In Opera and Chrome this fails after row 8192, but Safari and Firefox actually render the full 20000 rows.

The below code uses css classes to achieve the result (you might need to wait for pandas 1.3.0 for a bugfix to set_td_classes)

df = pd.DataFrame(np.random.rand(20000, 3), columns=['key1', 'key2', 'key3'])
cls = pd.DataFrame(np.where(df['key1'] < df['key2'] + 1, 'cls-bg', ''), columns=['key3'])
s = Styler(df, uuid_len=0, cell_ids=False).set_td_classes(cls).set_table_styles([
    {'selector': '.cls-bg', 'props': [('background-color', 'yellow')]}
])
write_html(s)

This HTML renders correctly since there is only a small style element: <style type="text/css"> #T__ .cls-bg { background-color: yellow; }</style>

It is also optimised from an HTML transfer perspective.

@NewtonXu
Copy link
Author

Yes, it is a limitation with the browsers, but in the sense that pandas should not be putting that many id's on a single attribute.
It seems your suggestion to use a class would be the best approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Styler conditional formatting using DataFrame.style Usage Question
Projects
None yet
Development

No branches or pull requests

3 participants