44

I am using Flask but this probably applies to a lot of similar frameworks.

I construct a pandas Dataframe, e.g.

@app.route('/analysis/<filename>')
def analysis(filename):
    x = pd.DataFrame(np.random.randn(20, 5))
    return render_template("analysis.html", name=filename, data=x)

The template analysis.html looks like

{% extends "base.html" %}
{% block content %}
<h1>{{name}}</h1>
{{data}}
{% endblock %}

This works but the output looks horrible. It doesn't use linebreaks etc. I have played with data.to_html() and data.to_string() What's the easiest way to display a frame?

user2314737
  • 27,088
  • 20
  • 102
  • 114
tschm
  • 2,905
  • 6
  • 33
  • 45
  • 1
    have you tried displaying it in `
    ` blocks?
    – MattDMo Mar 04 '14 at 19:21
  • 1
    Also, what were the issues you were having with [`pd.DataFrame.to_html()`](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.to_html.html)? – MattDMo Mar 04 '14 at 19:25
  • It doesn't render the html. But having said that I know nothing about html. Maybe I should invest some time here.
     works for me...
    – tschm Mar 04 '14 at 19:27
  • 3
    A tip for everyone: see if your DataFrame contains non-unicode strings, and convert them to unicode; otherwise Jinja2 will fail to render a template whenever there are some unexpected characters. Or is `to_html()` taking care of that? I have personally used [Datatables.js](http://datatables.net/) on top of Jinja2 to simplify displaying DataFrames --- just for internal admin use --- both with loading everything right away vs. reloading via ajax on every sorting/filtering. Though, I am looking for better solutions. – Sergey Orshanskiy Dec 22 '14 at 00:01

4 Answers4

62

The following should work:

@app.route('/analysis/<filename>')
def analysis(filename):
    x = pd.DataFrame(np.random.randn(20, 5))
    return render_template("analysis.html", name=filename, data=x.to_html())
                                                                # ^^^^^^^^^

Check the documentation for additional options like CSS styling.

Additionally, you need to adjust your template like so:

{% extends "base.html" %}
{% block content %}
<h1>{{name}}</h1>
{{data | safe}}
{% endblock %}

in order to tell Jinja you're passing in markup. Thanks to @SeanVieira for the tip.

Sobigen
  • 2,038
  • 15
  • 23
MattDMo
  • 100,794
  • 21
  • 241
  • 231
  • It prints on the webpage
    0
    – tschm Mar 04 '14 at 19:43
  • 1
    @tschm - make sure you don't make both changes at the same time - either use your original function and add the `
    ` tags, **or** convert the frame using `to_html()`.
    – MattDMo Mar 04 '14 at 20:09
  • well,
     kind of works, but it's not very nice formatted. The to_html() still gives a lot of html but it's not rendered. That's certainly me being stupid with html rather than a problem with pandas or flask. I just thought there must be a working example around...
    – tschm Mar 04 '14 at 20:40
  • I am trying harder with html now, I get this within the source code of the page <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>0</th> <th>1</th> <th>2</th> <th>3</th> – tschm Mar 04 '14 at 20:47
  • 3
    @tschm - If you are passing the data in as HTML you have to tell Jinja not to escape it. Try `{{ data | safe}}` (without any `pre` tags). – Sean Vieira Mar 04 '14 at 21:29
  • 1
    @tschm - I've updated my answer above to hopefully give you the full solution, including Sean Vieira's advice about the Jinja template. If you feel that it helps solve your problem, please feel free to click the check mark and select it as the correct answer. Thanks! – MattDMo Mar 04 '14 at 22:16
  • The result in html still looks a bit disappointing (certainly less attractive than the plain text). However, I think that's an exercise in choosing the right *.css? – tschm Mar 04 '14 at 22:56
  • 1
    @tschm - definitely. The table by default has `class="dataframe"`, so combining that with selectors to style the `` elements in the `` row, and the `` elements in the rest of the rows, you can get some pretty fancy results. You can even highlight every other row, get mouseover color changes, all sorts of stuff. All possible with the powers of CSS3 :) – MattDMo Mar 04 '14 at 23:25
  • do you have a complete example? – tschm Mar 05 '14 at 06:14
  • 1
    @tschm check out http://www.csstablegenerator.com/ - you can design your own parameters, then (hopefully) adapt the CSS to the generated HTML. If you need to add class names to various rows (you may not need to, it depends on how they generate the CSS selectors), then you can just do a little processing of the string generated by `frame.to_html()` before passing it to the template rendering engine. – MattDMo Mar 05 '14 at 14:53
30

Ok, I have managed to get some very nice results by now combining the hints I got here. In the actual Python viewer I use

@app.route('/analysis/<filename>')
def analysis(filename):
    x = pd.DataFrame(np.random.randn(20, 5))
    return render_template("analysis.html", name=filename, data=x)

e.g. I send the complete dataframe to the html template. My html template is based on bootstrap. Hence I can simply write

{% extends "base.html" %}
{% block content %}
<h1>{{name}}</h1>
{{ data.to_html(classes="table table-striped") | safe}}
{% endblock %}

There are numerous other options with bootstrap, check out here: http://getbootstrap.com/css/#tables

Base.html is essentially copied from here http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xii-facelift

The next question is obviously how to plot such a frame. Anyone any experience with Bokeh?

Thank you both to Matt and Sean.

thomas

tschm
  • 2,905
  • 6
  • 33
  • 45
  • 3
    What's also helpful is to use the na_rep option, e.g. data.to_html(classes="table table-striped", na_rep="-"). Using datatables.net the column can then be correctly sorted (assuming all entries are numbers). – tschm Jun 10 '14 at 07:14
4

Iterating over the rows of a df

If you need to have the df in a format that can iterate over the rows in your html, then use to_dict(orient='records'), which produces a dict in a format:

‘records’ : list like [{column -> value}, … , {column -> value}]

That way you can use your own way of displaying the data in your html. The sample code would now look like this:

Python code using flask

@app.route('/analysis/<filename>')
def analysis(filename):
    x = pd.DataFrame(np.random.randn(20, 5))
    return render_template("analysis.html", name=filename, data=x.to_dict(orient='records'))

HTML code with jinja

{% extends "base.html" %}
    {% block content %}
    <table class="table">
        <thead>
            <tr>
                <th scope="col">Column name 1</th>
                <th scope="col">Column name 2</th>
                <th scope="col">Column name 3</th>
            </tr>
        </thead>
        <tbody>
        {% for row in data %}
            <tr>
                <td>{{row['Column name 1']}}</td>
                <td>{{row['Column name 2']}}</td>
                <td>{{row['Column name 2']}}</td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
    {% endblock %}
Mark
  • 934
  • 1
  • 10
  • 25
2

You can use enaml-web to display and interact with pandas dataframes.

A few examples:

Note: Interaction (sorting, filtering, etc...) requires a server with websocket support.

frmdstryr
  • 20,142
  • 3
  • 38
  • 32