pandas styling add attribute for cell/column - python

I need to color specific columns in the table and convert it to html. I know it's possible to apply pandas styling to required subsets of data; for example, the next code snippet seems to work just fine. But the problem is that I need to set the same styling using bgcolor html attribute, not CSS. And I have found only Styler.set_table_attributes, which doesn't really help in my case. My current approach: I'm converting the html obtained from pandas to BeautifulSoup and adding attributes there, but it's not really convenient.
import numpy as np
import pandas as pd
np.random.seed(0)
df = pd.DataFrame(np.random.randn(10,4), columns=['A','B','C','D'])
st = df.style
def highlight(c):
color = 'green' if (c > 0) else 'red'
return f'background-color:{color}'
st.applymap(highlight, subset=['C', 'D'])
with open('out.html','w') as f:
f.write(str(st.to_html()))
# how i'm doing this now:
from bs4 import BeautifulSoup
res = BeautifulSoup(df.to_html(index=False), features='html.parser')
for header in ['C', 'D']:
index = df.columns.get_loc(header)
for tr in res.table.tbody.select('tr'):
td = tr.select('td')[index]
c = float(td.text)
td.attrs['bgcolor'] = 'green' if (c > 0) else 'red'
with open('out2.html','w') as f:
f.write(str(res))

You can set the bgcolor attribute using pandas styling by defining a custom function that returns the bgcolor attribute in an HTML style tag. You can then use this function with the Styler.applymap() method to apply the styling to the required subset of data.
Example:
import numpy as np
import pandas as pd
np.random.seed(0)
df = pd.DataFrame(np.random.randn(10,4), columns=['A','B','C','D'])
st = df.style
def highlight(c):
color = 'green' if (c > 0) else 'red'
return f'bgcolor: {color}'
st.applymap(highlight, subset=['C', 'D'])
with open('out.html','w') as f:
f.write(st.to_html())
The render() method of the Styler object will return the styled DataFrame as an HTML string with the bgcolor attribute set using the style tag.

Related

using applymap method to color cells based on condition

I would like to color some cells (in a data frame) based on their content using pandas, I did some tries but with no required results
this is my last failed try :
import pandas as pd
import dataframe_image as dfi
df = pd.read_excel('splice traitment.xlsx', sheet_name='Sheet4', usecols="B,C,D,E,F,G,H,I,J,K")
def color_cells(val):
color = 'red' if val == 7 else ''
return 'background-color: {}'.format(color)
df.style.applymap(color_cells)
dfi.export(df,"table.png")
Thank you very much
You can pass styled DataFrame to export method:
dfi.export(df.style.applymap(color_cells),"table.png")
Or asign to variable styled:
styled = df.style.applymap(color_cells)
dfi.export(styled,"table.png")

Apply multiple styles to cell in pandas

So, basically I want to apply styles to cell and render to html.
This is what I have done so far:
import pandas as pd
import numpy as np
data = [dict(order='J23-X23', num=5, name='foo', value='YES'),
dict(order='Y23-X24', num=-4, name='bar', value='NO'),
dict(order='J24-X24', num=6, name='baz', value='NO'),
dict(order='X25-X24', num=-2, name='foobar', value='YES')]
df = pd.DataFrame(data)
def condformat(row):
cellBGColor = 'green' if row.value == 'YES' else 'blue'
color = 'background-color: {}'.format(cellBGColor)
return (color, color, color, color)
s = df.style.apply(condformat, axis=1)
with open('c:/temp/test.html', 'w') as f:
f.write(s.render())
My problem is two fold:
(1) Am I using the right rendering? Or is there a better way to do this? As in what does to_html give me?
(2) What if I have to add another style, say make -ve numbers in the format (35) in red color?
So, after battling with trial-error:
s = df.style.apply(condformat, axis=1).applymap(dataframe_negative_coloring)
with a new method:
def dataframe_negative_coloring(value):
''' Colors Dataframe rows with alternate colors.'''
color = 'red' if value < 0 else 'black'
return 'color: %s' % color
Any purists out there, feel free to advice if I can make this better

How to hide dataframe index on streamlit?

I want to use some pandas style resources and I want to hide table indexes on streamlit.
I tryed this:
import streamlit as st
import pandas as pd
table1 = pd.DataFrame({'N':[10, 20, 30], 'mean':[4.1, 5.6, 6.3]})
st.dataframe(table1.style.hide_index().format(subset=['mean'],
decimal=',', precision=2).bar(subset=['mean'], align="mid"))
but regardless the .hide_index() I got this:
Ideas to solve this?
Documentation for st.dataframe shows "Styler support is experimental!"
and maybe this is the problem.
But I can get table without index if I use .to_html() and st.write()
import streamlit as st
import pandas as pd
df = pd.DataFrame({'N':[10, 20, 30], 'mean':[4.1, 5.6, 6.3]})
styler = df.style.hide_index().format(subset=['mean'], decimal=',', precision=2).bar(subset=['mean'], align="mid")
st.write(styler.to_html(), unsafe_allow_html=True)
#st.write(df.to_html(index=False), unsafe_allow_html=True)
Another option is using a CSS selector to remove the index column. Like explained in the docs, you can do the following with st.table:
# import packages
import streamlit as st
import pandas as pd
# table
table1 = pd.DataFrame({'N':[10, 20, 30], 'mean':[4.1, 5.6, 6.3]})
# CSS to inject contained in a string
hide_table_row_index = """
<style>
thead tr th:first-child {display:none}
tbody th {display:none}
</style>
"""
# Inject CSS with Markdown
st.markdown(hide_table_row_index, unsafe_allow_html=True)
# Display a static table
st.table(table1.style.format(subset=['mean'],
decimal=',', precision=2).bar(subset=['mean'], align="mid"))
Output:
As you can see the index is gone. Keep in mind that the table function takes the full page.

Pandas style: How to highlight diagonal elements

I was wondering how to highlight diagonal elements of pandas dataframe using df.style method.
I found this official link where they discuss how to highlight maximum value, but I am having difficulty creating function to highlight the diagonal elements.
Here is an example:
import numpy as np
import pandas as pd
df = pd.DataFrame({'a':[1,2,3,4],'b':[1,3,5,7],'c':[1,4,7,10],'d':[1,5,9,11]})
def highlight_max(s):
'''
highlight the maximum in a Series yellow.
'''
is_max = s == s.max()
return ['background-color: yellow' if v else '' for v in is_max]
df.style.apply(highlight_max)
This gives following output:
I am wanting a yellow highlight across the diagonal elements 1,3,7,11 only.
How to do that?
Using axis=None we can use numpy to easily set the diagonal styles (Credit for this goes to #CJR)
import numpy as np
import pandas as pd
def highlight_diag(df):
a = np.full(df.shape, '', dtype='<U24')
np.fill_diagonal(a, 'background-color: yellow')
return pd.DataFrame(a, index=df.index, columns=df.columns)
df.style.apply(highlight_diag, axis=None)
Original, really hacky solution
a = np.full(df.shape, '', dtype='<U24')
np.fill_diagonal(a, 'background-color: yellow')
df_diag = pd.DataFrame(a,
index=df.index,
columns=df.columns)
def highlight_diag(s, df_diag):
return df_diag[s.name]
df.style.apply(highlight_diag, df_diag=df_diag)
The trick is to use the axis=None parameter of the df.style.apply function in order to access the entire dataset:
import numpy as np
import pandas as pd
df = pd.DataFrame({'a':[1,2,3,4],'b':[1,3,5,7],'c':[1,4,7,10],'d':[1,5,9,11]})
def highlight_diag(data, color='yellow'):
'''
highlight the diag values in a DataFrame
'''
attr = 'background-color: {}'.format(color)
# create a new dataframe of the same structure with default style value
df_style = data.replace(data, '')
# fill diagonal with highlight color
np.fill_diagonal(df_style.values, attr)
return df_style
df.style.apply(highlight_diag, axis=None)
The other answer is pretty good but I already wrote this so....
def style_diag(data):
diag_mask = pd.DataFrame("", index=data.index, columns=data.columns)
min_axis = min(diag_mask.shape)
diag_mask.iloc[range(min_axis), range(min_axis)] = 'background-color: yellow'
return diag_mask
df = pd.DataFrame({'a':[1,2,3,4],'b':[1,3,5,7],'c':[1,4,7,10],'d':[1,5,9,11]})
df.style.apply(style_diag, axis=None)

How to solve the FunctionError and MapError

Python 3.6 pycharm
import prettytable as pt
import numpy as np
import pandas as pd
a=np.random.randn(30,2)
b=a.round(2)
df=pd.DataFrame(b)
df.columns=['data1','data2']
tb = pt.PrettyTable()
def func1(columns):
def func2(column):
return tb.add_column(column,df[column])
return map(func2,columns)
column1=['data1','data2']
print(column1)
print(func1(column1))
I want to get the results are:
tb.add_column('data1',df['data1'])
tb.add_column('data2',df['data2'])
As a matter of fact,the results are:
<map object at 0x000001E527357828>
I am trying find the answer in Stack Overflow for a long time, some answer tell me can use list(func1(column1)), but the result is [None, None].
Based on the tutorial at https://ptable.readthedocs.io/en/latest/tutorial.html, PrettyTable.add_column modifies the PrettyTable in-place. Such functions generally return None, not the modified object.
You're also overcomplicating the problem by trying to use map and a fancy wrapper function. The below code is much simpler, but produces the desired result.
import prettytable as pt
import numpy as np
import pandas as pd
column_names = ['data1', 'data2']
a = np.random.randn(30, 2)
b = a.round(2)
df = pd.DataFrame(b)
df.columns = column_names
tb = pt.PrettyTable()
for col in column_names:
tb.add_column(col, df[col])
print(tb)
If you're still interesting in learning about the thing that map returns, I suggest reading about iterables and iterators. map returns an iterator over the results of calling the function, and does not actually do any work until you iterate over it.

Categories