7

I have a Pandas dataframe inside of a Jupyter / IPython notebook. The dataframe's style as an HTML table inside of Jupyter is pretty nice. The header row has bold style, the font is nice, and the table borders are thin.

enter image description here

I then export the dataframe to an HTML file (following instructions here and here):

df.to_html('myfile.html')

But the resulting HTML file's table styling is not good.

enter image description here

The HTML in that file is plain:

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Id</th>
      <th>Index</th>
      <th>Feature</th>
      <th>Timestamp</th>
      <th>Feature2</th>
    </tr>
  </thead>

How do I modify the styling of this exported table directly from my Python / Pandas code?

stackoverflowuser2010
  • 38,621
  • 48
  • 169
  • 217
  • You can create a custom stylesheet with classes and [pass them to the method](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.to_html.html#pandas.DataFrame.to_html) – 4d11 Dec 07 '17 at 22:02
  • 6
    @J.C.Rocamonde: Because I spent the last 24 hours creating the solution, so I thought I would share the result. – stackoverflowuser2010 Dec 08 '17 at 23:04
  • Yea you’re right lol I did not intend to be rude, just saying that it was not actually a problem, just a matter of getting it done –  Dec 08 '17 at 23:05
  • 2
    It could be that there is an easier or more elegant solution out there, can't hurt to ask right @stackoverflowuser2010? – gosuto Apr 29 '18 at 20:10
  • Thanks for posting your answer mate. It was helpful. – Suraj Jun 18 '21 at 09:31

2 Answers2

29

I wrote a Python function that basically adds an HTML <style> to the dataframe's HTML representation so that the resulting HTML table looks nice.

import pandas as pd

def write_to_html_file(df, title='', filename='out.html'):
    '''
    Write an entire dataframe to an HTML file with nice formatting.
    '''

    result = '''
<html>
<head>
<style>

    h2 {
        text-align: center;
        font-family: Helvetica, Arial, sans-serif;
    }
    table { 
        margin-left: auto;
        margin-right: auto;
    }
    table, th, td {
        border: 1px solid black;
        border-collapse: collapse;
    }
    th, td {
        padding: 5px;
        text-align: center;
        font-family: Helvetica, Arial, sans-serif;
        font-size: 90%;
    }
    table tbody tr:hover {
        background-color: #dddddd;
    }
    .wide {
        width: 90%; 
    }

</style>
</head>
<body>
    '''
    result += '<h2> %s </h2>\n' % title
    if type(df) == pd.io.formats.style.Styler:
        result += df.render()
    else:
        result += df.to_html(classes='wide', escape=False)
    result += '''
</body>
</html>
'''
    with open(filename, 'w') as f:
        f.write(result)

Here's the resulting HTML when you write it to an .html file. Note how the dataframe's to_html() output fits into the middle.

enter image description here

Below is some example usage of my function. I first load up a dataset from sklearn to demonstrate.

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris

iris = load_iris()
data1 = pd.DataFrame(data=np.c_[iris['data'], iris['target']],
                     columns=iris['feature_names'] + ['target'])
data1.head()

In Jupyter / IPython Notebook, the table looks pretty nice:

enter image description here

I can write out the dataframe to an HTML file with the usual to_html() function like this:

data1.to_html('iris.html')

However, the result doesn't look good, as shown below. The border is thick and font is not pleasant because this is just a <table> ... </table> with no styling.

enter image description here

To make the dataframe look better in HTML, I used my function above.

write_to_html_file(data1, 'Iris data set', 'iris2.html')

The table looks much nicer now because I applied styling. I also added row highlighting.

enter image description here

MuhsinFatih
  • 1,891
  • 2
  • 24
  • 31
stackoverflowuser2010
  • 38,621
  • 48
  • 169
  • 217
  • This styling won't work in few email client i.e Gmail. Though it will work with Safari. There are couple of email client who don't expect styling in headers but require inline css. – Aman sharma May 17 '19 at 18:39
  • is there a way to include conditionals in the method? e.g. make a column red if values are negative, otherwise green. – guy May 22 '19 at 20:47
  • @guy, go to [here](https://github.com/fomightez/dataframe2img) & launch a session. You'll see at the bottom of the page I combine something like this solution with [conditional formatting using Pandas' styling](http://pandas.pydata.org/pandas-docs/stable/user_guide/style.html) feature to color cells depending on values. – Wayne Jun 18 '19 at 17:38
0

Option A

You can use the border-collapse CSS property with the .render() method from the Styler object.

[for Pandas >= 1.4 use Styler.to_html()]


You pass the borders style with .style.set_table_styles().


Example that works on Pandas 1.1.3:

import pandas as pd
from sklearn.datasets import load_iris

iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
dh = df.head()

borders = [{
              'selector': 'td, th, table'
            , 'props'   : [  ('border', '1px solid lightgrey')
                           , ('border-collapse', 'collapse')
                          ]
            }]


html_fragment = dh.style.hide_index().set_table_styles(borders).render()

html_fragment renders to:

<style  type="text/css" >
    #T_da4a1131_90ff_11ec_9603_dcfb48c86a17 td, th, table {
          border: 1px solid lightgrey;
          border-collapse: collapse;
    }</style><table id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17" ><thead>    <tr>        <th class="col_heading level0 col0" >sepal length (cm)</th>        <th class="col_heading level0 col1" >sepal width (cm)</th>        <th class="col_heading level0 col2" >petal length (cm)</th>        <th class="col_heading level0 col3" >petal width (cm)</th>    </tr></thead><tbody>
                <tr>
                                <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row0_col0" class="data row0 col0" >5.100000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row0_col1" class="data row0 col1" >3.500000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row0_col2" class="data row0 col2" >1.400000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row0_col3" class="data row0 col3" >0.200000</td>
            </tr>
            <tr>
                                <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row1_col0" class="data row1 col0" >4.900000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row1_col1" class="data row1 col1" >3.000000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row1_col2" class="data row1 col2" >1.400000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row1_col3" class="data row1 col3" >0.200000</td>
            </tr>
            <tr>
                                <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row2_col0" class="data row2 col0" >4.700000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row2_col1" class="data row2 col1" >3.200000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row2_col2" class="data row2 col2" >1.300000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row2_col3" class="data row2 col3" >0.200000</td>
            </tr>
            <tr>
                                <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row3_col0" class="data row3 col0" >4.600000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row3_col1" class="data row3 col1" >3.100000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row3_col2" class="data row3 col2" >1.500000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row3_col3" class="data row3 col3" >0.200000</td>
            </tr>
            <tr>
                                <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row4_col0" class="data row4 col0" >5.000000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row4_col1" class="data row4 col1" >3.600000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row4_col2" class="data row4 col2" >1.400000</td>
                        <td id="T_da4a1131_90ff_11ec_9603_dcfb48c86a17row4_col3" class="data row4 col3" >0.200000</td>
            </tr>
    </tbody></table>

which gets displayed as (on Chromium 85.0):

using Styler object display

Note that the syntax for passing style properties in later versions for Pandas has slightly changed.

Option B

In case you want something quicker without tinkering too much with CSS, you can use:

.to_html(border=0) from the DataFrame object, which sets <table border="0" >.

Using the code above:

html_pure = dh.to_html(border=0, index=False)

which renders as:

<table border="2" class="dataframe">\n  <thead>\n    <tr style="text-align: right;">\n      <th>sepal length (cm)</th>\n      <th>sepal width (cm)</th>\n      <th>petal length (cm)</th>\n      <th>petal width (cm)</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>5.1</td>\n      <td>3.5</td>\n      <td>1.4</td>\n      <td>0.2</td>\n    </tr>\n    <tr>\n      <td>4.9</td>\n      <td>3.0</td>\n      <td>1.4</td>\n      <td>0.2</td>\n    </tr>\n    <tr>\n      <td>4.7</td>\n      <td>3.2</td>\n      <td>1.3</td>\n      <td>0.2</td>\n    </tr>\n    <tr>\n      <td>4.6</td>\n      <td>3.1</td>\n      <td>1.5</td>\n      <td>0.2</td>\n    </tr>\n    <tr>\n      <td>5.0</td>\n      <td>3.6</td>\n      <td>1.4</td>\n      <td>0.2</td>\n    </tr>\n  </tbody>\n</table>

and is useful if you want to style your tables externally and without having too much generated clutter in your table html code.

html_pure displays like:

.to_html(border=0) display

LBNL, tables and style properties have always been tricky.

You may want to spend some time with the html inspector inside your browser to see what's really happening.

The code above works generally fine on a pretty good array of (modern) browsers/applications.

Obernil
  • 31
  • 3