has2k1 / plotnine

A Grammar of Graphics for Python
https://plotnine.org
MIT License
3.97k stars 213 forks source link

Plotnine export of interactive plots like Plotly? #262

Open heoa opened 5 years ago

heoa commented 5 years ago

In R, Plotly has added ggplot2 bindings to export to interactive plotly objects, more here. In Python, I want to use ggplot2 -style plotting so I chose Plotnine but I cannot find a way to generate interactive Plotly plots from Plotnine.

In Python, how can I generate interactive plots in Plotnine (interactive like in Plotly so easy to export to HTML for visual investigation)?

JarnoRFB commented 5 years ago

I believe that is not supported in plotnine. You can of course asked the plotly people to add a feature like that. I guess it would be a cool addition. Meanwhile you might want to have a look at altair, which also has a grammar of graphics approach and directly renders to interactive HTML plots.

JarnoRFB commented 5 years ago

Just discovered that plotly even supports conversion from matplotlib figures to plotly objects, see https://plot.ly/matplotlib/getting-started/. However, that does not seem to work with the figures created by plotnine.

has2k1 commented 5 years ago

... However, that does not seem to work with the figures created by plotnine.

I have not used plotly but I would have expected it to work (may be not perfectly) with figures created in plotnine, since it is all matplotlib.

mneilly commented 5 years ago

FWIW - here are examples of a working matplotlib graph and a failing plotnine graph. I do get a graph but no data. The key message appears to be:

Dang! That path collection is out of this world. I totally don't know what to do with it yet! Plotly can only import path collections linked to 'data' coordinates

The working matplotlib graph:

import dash_core_components as dcc
import dash_html_components as html
from plotly.tools import mpl_to_plotly

import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import numpy as np

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

n = 50
x, y, z, s, ew = np.random.rand(5, n)
c, ec = np.random.rand(2, n, 4)
area_scale, width_scale = 500, 5

fig, ax = plt.subplots()
sc = ax.scatter(x, y, c=c,
                s=np.square(s)*area_scale,
                edgecolor=ec,
                linewidth=ew*width_scale)
ax.grid()

plotly_fig = mpl_to_plotly(plt.gcf())
graph = dcc.Graph(id='myGraph', figure=plotly_fig)

app.layout = html.Div([graph])

if __name__ == '__main__':
    app.run_server(debug=True)

The failing plotnine graph:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd

from plotly.tools import mpl_to_plotly
from plotnine import *

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# plotnine example from gallery
# https://plotnine.readthedocs.io/en/stable/generated/plotnine.geoms.geom_col.html#two-variable-bar-plot

df = pd.DataFrame({
    'variable': ['gender', 'gender', 'age', 'age', 'age', 'income', 'income', 'income', 'income'],
    'category': ['Female', 'Male', '1-24', '25-54', '55+', 'Lo', 'Lo-Med', 'Med', 'High'],
    'value': [60, 40, 50, 30, 20, 10, 25, 25, 40],
})
df['variable'] = pd.Categorical(df['variable'], categories=['gender', 'age', 'income'])

graph = ggplot(df, aes(x='variable', y='value', fill='category')) + geom_col()

# get matplotlib figure

fig = graph.draw()

# dash graph

plotly_fig = mpl_to_plotly(fig)
graph = dcc.Graph(id='myGraph', figure=plotly_fig)
app.layout = html.Div([graph])

if __name__ == '__main__':
    app.run_server(debug=True)

This is the error from running the dash app:

/home/mneilly/PycharmProjects/plotly/venv/bin/python /home/mneilly/PycharmProjects/plotly/plotlyplotnineexample.py
/home/mneilly/PycharmProjects/plotly/venv/lib/python3.6/site-packages/plotly/matplotlylib/renderer.py:451: UserWarning:

Dang! That path collection is out of this world. I totally don't know what to do with it yet! Plotly can only import path collections linked to 'data' coordinates

Running on http://127.0.0.1:8050/
Debugger PIN: 573-582-988
 * Serving Flask app "plotlyplotnineexample" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
Running on http://127.0.0.1:8050/
Debugger PIN: 075-361-477
/home/mneilly/PycharmProjects/plotly/venv/lib/python3.6/site-packages/plotly/matplotlylib/renderer.py:451: UserWarning:

Dang! That path collection is out of this world. I totally don't know what to do with it yet! Plotly can only import path collections linked to 'data' coordinates

Which results in a blank graph:

image

mneilly commented 5 years ago

Actually, this example appears to work:

import dash
import dash_core_components as dcc
import dash_html_components as html

from plotnine import *
from plotnine.data import *
from plotly.tools import mpl_to_plotly

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

p = ggplot(aes(x='displ', y='cty'), mpg)
p += geom_point()

# get matplotlib figure

fig = p.draw()

# dash graph

plotly_fig = mpl_to_plotly(fig)
graph = dcc.Graph(id='myGraph', figure=plotly_fig)
app.layout = html.Div([graph])

if __name__ == '__main__':
    app.run_server(debug=True)

image

mneilly-et commented 4 years ago

FYI/FWIW - regarding the conversion of matplotlib to plotly.

There are many limitations in mpl_to_plotly and we haven’t updated this code in years. The code is pretty gnarly and we decided to double down on the native plotly syntax instead of continuing to work on these converters.

has2k1 commented 4 years ago

@mneilly-et, thanks for that link. Then I think best solution would be to have Plotly as a separate backend. It would be a lot of work, but it is worth thinking about; including the non-technical issues like copyright and licensing.

GitHunter0 commented 3 years ago

@has2k1 it would be a really awesome feature for plotnine, but for sure there is a lot of work involved

krassowski commented 3 years ago

FYI you can easily export plotnine to interactive HTML plots using mpld3.

tr8dr commented 3 years ago

@krassowski have you tried rendering with mpld3 in dash? I have attempted the following (within a callback to fill a div child target):

@app.callback(Output('graph1','children'), [Input("tenor1", "value")])
def update_graph(value):
    Tbase = pd.to_datetime("2010-1-1")
    dates = [Tbase + pd.DateOffset(days=10*i) for i in range(0,230)]
    s1 = pd.Series(np.random.normal(0.2, 1.0, 230)).cumsum()
    s2 = pd.Series(np.random.normal(0.0, 1.0, 230)).cumsum()

    df = pd.concat([
        pd.DataFrame({'date': dates, 'value': s1, 'what': 'strategy'}),
        pd.DataFrame({'date': dates, 'value': s2, 'what': 'spy'}),
    ])

    v = (ggplot() +
         geom_line(aes(x='date', y='value', color='what'), data=df))

    # use mpld3 to render the graph to html
    fhtml = fig_to_html(v.draw())
    return dash_dangerously_set_inner_html.DangerouslySetInnerHTML(fhtml)

Where the target is just:

html.Div(id='graph1')

I can see that the Githubissues.

  • Githubissues is a development platform for aggregating issues.