updating plotly figure [every several seconds] in jupyter - python

I'm new to the plotly python package and I've faced with such problem:
There is a pandas dataframe that is updating in a loop and I have to plot data from it with plotly.
At the beginning all df.response values are None and then it starts to fill it. Here is an example:
at the beginning
after it starts to fill
I want plotly to react this changes, but I don't know how to do it in the most "canonical" and simple way. (It would be great if data updating loop and plotly updating would work simultaneously, but if plotly will react every several seconds it would be fine too). I found some functions but don't exactly understand how they work:
import plotly.graph_objects as go
import cufflinks as cf
from plotly.offline import init_notebook_mode
init_notebook_mode(connected=True)
cf.go_offline()
fig = go.Figure(data=go.Heatmap(z=df.response,
x=df.currents,
y=df.frequencies))
fig.update_layout(datarevision=???) # This one
...

given you want to update as data arrives you need an event / interrupt handling approach
this example uses time as the event / interupt, a dash Interval
simulates more data by concatenating additional data to dataframe then updates the figure in the callback
import plotly.graph_objects as go
import numpy as np
import pandas as pd
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
# initialise dataframe and figure
df = pd.DataFrame({"response":[], "currents":[], "frequencies":[]})
fig = go.Figure(data=go.Heatmap(z=df.response,
x=df.currents,
y=df.frequencies))
# Build App
app = JupyterDash(__name__)
app.layout = html.Div(
[
dcc.Graph(id="heatmap", figure=fig),
dcc.Interval(id="animateInterval", interval=400, n_intervals=0),
],
)
#app.callback(
Output("heatmap", "figure"),
Input("animateInterval", "n_intervals"),
State("heatmap", "figure")
)
def doUpdate(i, fig):
global df
df = pd.concat([df, pd.DataFrame({"response":np.random.uniform(1,5,100), "currents":np.random.randint(1,20,100), "frequencies":np.random.randint(1,50,100)})])
return go.Figure(fig).update_traces(go.Heatmap(z=df.response,
x=df.currents,
y=df.frequencies))
# Run app and display result inline in the notebook
app.run_server(mode="inline")

I believe this other answer has a less complicated solution than Rob Raymond's. I would personally go with this one. Essentially you just need to wrap the Figure in a FigureWidget—that's it. It responds to any data changes automatically.
Although I would be interested to know if there are benefits of using Dash over Plotly alone (in a Jupyter notebook).

Related

Blank Bar Charts in Plotly

I have a df - Wards - that contains the number of different events that happen on each ward of a hospital. I just want a simple bar chart of the totals of these events. I have used plotly before - I am no means an expert (evidently!) but I can't figure out where I am going wrong! With the code below I am seeing anything with fig.show(). I added the fig.write_image command to test - this returns the correct graph - but I can't figure out why my fig.show() command doesn't work
import plotly.express as px
import matplotlib.pyplot as plt
fig = px.bar(Wards, x='Ward', y='Total_Tasks')
fig.write_image("fig1.png")
fig.show()
I tried to duplicate your code but of course I don't have the data, so I made some up and I also didn't know for sure what modules you are importing, so I used the help at:
https://plotly.com/python/getting-started/
but anyway, here is what I came up with
that seems to work.
import plotly.express as px
import matplotlib.pyplot as plt
fig = px.bar(x=["a", "b", "c"], y=[1, 3, 2])
plt.savefig('fig1.png',bbox_inches="tight",dpi=600)
fig.show()

Python converting waterfall figure to plotly

I am using the waterfall_chart package in Python to create a waterfall figure. The package mainly uses matplotlib in the backend, so I was trying to use the tls.mpl_to_plotly(mpl_fig) function to covert the matplotlib figure into plotly. But when converting, an error pops up. Is there a way to convert waterfall_chart into plotly or is there an easy way to create the chart directly in plotly? I saw some previous discussion on similar chart in plotly, but it involved pretty manual coding of the chart number.
You could use the following code to recreate the chart.
import waterfall_chart
import matplotlib.pyplot as plt
import plotly.tools as tls
a = ['sales','returns','credit fees','rebates','late charges','shipping']
b = [10,-30,-7.5,-25,95,-7]
mpl_fig = plt.figure()
waterfall_chart.plot(a, b)
plt.show()
waterfall chart
But when I try to convert to plotly using mpl_to_plotly(), there is an error:
plotly_fig = tls.mpl_to_plotly(mpl_fig)
ValueError: min() arg is an empty sequence
The detail of the waterfall_chart package could be found here: https://github.com/chrispaulca/waterfall/blob/master/waterfall_chart.py
My answer addresses
[...] or is there an easy way to create the chart directly in plotly?
With newer versions of plotly you can use plotly.graph_objs.Waterfall.
Below is a basic example with your data sample with a setup that uses iplot in an off-line Jupyter Notebook:
Plot:
Code:
# imports
import plotly
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
from IPython.core.display import display, HTML
import plotly.figure_factory as ff
import plotly.graph_objs as go
import pandas as pd
import numpy as np
# setup
display(HTML("<style>.container { width:35% !important; } .widget-select > select {background-color: gainsboro;}</style>"))
init_notebook_mode(connected=True)
np.random.seed(1)
import plotly.offline as py
import plotly.graph_objs as go
py.init_notebook_mode(connected = False)
# your values
a = ['sales','returns','credit fees','rebates','late charges','shipping']
b = [10,-30,-7.5,-25,95,-7]
# waterfall trace
trace = go.Waterfall(
x = a,
textposition = "outside",
text = [str(elem) for elem in b],
y = b,
connector = {"line":{"color":"rgb(63, 63, 63)"}},
)
layout = go.Layout(
title = "Waterfall chart, plotly version 3.9.0",
showlegend = True
)
iplot(go.Figure([trace], layout))
Check your version with:
import plotly
plotly.__version__
Update your version in a cmd console using:
pip install plotly --upgrade
List a has a length of 6, list b has a length of 5.
Matplotlib refuses to display an empty array, list or whatever.
Solve it to add a number or 0 to b or add an if to your code, to avoid matplotlib gets an empty sequence.

Pop-up chart with standard python script

import plotly.plotly as py
import plotly.graph_objs as go
from plotly.offline import iplot
import plotly.offline as py_offline
import pandas_datareader as web
from datetime import datetime
py_offline.init_notebook_mode()
df = web.DataReader("aapl", 'morningstar').reset_index()
trace = go.Candlestick(x=df.Date,
open=df.Open,
high=df.High,
low=df.Low,
close=df.Close)
data = [trace]
iplot(data, filename='simple_candlestick')
That code worked pretty well inside a jupyter notebook. Now, I want to execute it inside a standard python script. Once it is executed, I wanted a window to pop-up to see the graph related to that code, but it failed. How could I modify this code so that it works?
Instead of iplot() in the last line, using py_offline.plot() should open the plot in a browser window.
py_offline.plot(data, filename='simple_candlestick')
or
py_offline.plot(data)

Making dashboards using altair

I would like to use the excellent altair library to create dashboards. Is there a way to create the dashboards and not show any code? I see some really nice examples here: https://altair-viz.github.io/case_studies/exploring-weather.html but the code is visible too. Also, what is the best (well tested/easy to use) frontend for altair? Colab? Jupyter?
Any Altair chart can be saved as HTML using chart.save("filename.html"). If you open the resulting file with a web browser, you'll see the chart without any of the associated Python code.
Alternatively, you can use chart.to_json() to get out the JSON chart specification, which can then be embedded in any web page using vega-embed... this is exactly what is done in the page exported by chart.save.
As to your second question (please in the future try to limit your StackOverflow posts to a single question): Altair works with JupyterLab, Jupyter notebook, CoLab, nteract, and Hydrogen. You can use any of these frontends, though some require some extra setup. See https://altair-viz.github.io/getting_started/installation.html for details. I use JupyterLab, and would suggest starting with that.
In addition to creating standalone HTML files and using vega-embed, Altair is also compatible with common dashboarding packages such as Panel, Streamlit, and Dash. I have provided a simple example for each below:
Panel
Panel works both in the notebook and as a standalone dashboard. It also lets you embed dashboards in single HTML files.
import altair as alt
from vega_datasets import data
import panel as pn
from panel.interact import interact
pn.extension('vega')
cars = data.cars()
def scatter_plot(x_column):
chart = alt.Chart(cars).mark_point().encode(
x=x_column,
y='Displacement')
return chart
interact(scatter_plot, x_column=cars.select_dtypes('number').columns)
The capability to listen to Vega events and define custom callback for e.g. selected points was recently merged in Panel and is included in the 0.13 release! This is the only Python dashboarding package that supports custom callbacks on selections in Altair charts. Here is an example from the docs:
penguins_url = "https://raw.githubusercontent.com/vega/vega/master/docs/data/penguins.json"
brush = alt.selection_interval(name='brush') # selection of type "interval"
chart = alt.Chart(penguins_url).mark_point().encode(
x=alt.X('Beak Length (mm):Q', scale=alt.Scale(zero=False)),
y=alt.Y('Beak Depth (mm):Q', scale=alt.Scale(zero=False)),
color=alt.condition(brush, 'Species:N', alt.value('lightgray'))
).properties(
width=250,
height=250
).add_selection(
brush
)
vega_pane = pn.pane.Vega(chart, debounce=10)
vega_pane
df = pd.read_json(penguins_url)
def filtered_table(selection):
if not selection:
return '## No selection'
query = ' & '.join(
f'{crange[0]:.3f} <= `{col}` <= {crange[1]:.3f}'
for col, crange in selection.items()
)
return pn.Column(
f'Query: {query}',
pn.pane.DataFrame(df.query(query), width=600, height=300)
)
pn.Row(vega_pane, pn.bind(filtered_table, vega_pane.selection.param.brush))
Streamlit
Streamlit aims to be as close as possible to a regular Python script without having to focus programming a front end.
from vega_datasets import data
import streamlit as st
import altair as alt
cars = data.cars()
x_column = st.selectbox('Select x-axis column', cars.select_dtypes('number').columns)
chart = alt.Chart(cars).mark_point().encode(
x=x_column,
y='Displacement')
st.altair_chart(chart, use_container_width=True)
Dash
Dash is more verbose than the other two as it requires you to be explicit about many things in the frontend and the callbacks (which also gives more fine-grained control). There is no specific object for Altair graphs, so we're plugging the HTML plot into an iframe (I've asked about this).
import altair as alt
from vega_datasets import data
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
cars = data.cars()
# Setup app and layout/frontend
app = dash.Dash(__name__, external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css'])
app.layout = html.Div([
dcc.Dropdown(
id='x_column-widget',
value='Miles_per_Gallon', # REQUIRED to show the plot on the first page load
options=[{'label': col, 'value': col} for col in cars.columns]),
html.Iframe(
id='scatter',
style={'border-width': '0', 'width': '100%', 'height': '400px'})])
# Set up callbacks/backend
#app.callback(
Output('scatter', 'srcDoc'),
Input('x_column-widget', 'value'))
def plot_altair(x_column):
chart = alt.Chart(cars).mark_point().encode(
x=x_column,
y='Displacement',
tooltip='Horsepower').interactive()
return chart.to_html()
if __name__ == '__main__':
app.run_server(debug=True)
In addition to what suggested by #jakevdp, I found really useful to export the chart definition in json format and render it within the (still beta) Observable Notebook from Mike Bostock: graphs/interactions are produced with altair while boilerplate UI are easily added in plain HTML or javascript in a "reactive" environment (i.e. ... cells are automatically re-evaluated in topological order whenever their inputs change). The code is almost entirely hidden there, and, at the same time, you could exploit the idea of "computational essay" that has made Jupyter so popular. Create a reasonably complex and clean UI/Dashboard was for me easier there than using Jupyter + widgets and, thanks to altair, without the effort to program "complex" charts by hand.

Updating a chart with Plotly offline with Python

I am using Plotly offline on Jupyter.
I am plotting curves:
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
from plotly.graph_objs import *
import datetime as dt
list_date = [dt.datetime(2016,1,1).date(), dt.datetime(2016,1,2).date(), dt.datetime(2016,1,3).date(), dt.datetime(2016,1,4).date()]
data = []
for i in range(3) :
list = [i/2+1, i/2+2, i/2+3, i/2+4]
data.append(Scatter(x=list_date, y=list, name='y'+str(i)))
figure = Figure(data=data)
iplot(figure)
And I get a very nice graph!
In the latter case, the user wants to add a bar graph on it (in addition to the two lines already there).
list_bar = [0.5, 1.5, 2.5, 3.5]
data = [Bar(x=list_date, y=list_bar, name='bar')]
figure.update(data=data)
iplot(figure)
But I have only the bar chart, not the previous 2 lines. How to have offline the equivalent of the online function fileopt='append'?
py.plot(data, filename='append plot', fileopt='append')
In the latest plotly version 3, a FigureWidget has been added specifically to handle your problem of wanting to update an existing offline figure.
For me, running pip install plotly --upgrade got me the latest version within my Anaconda environment.
I've modified your example code below to use the new FigureWidget, and left in your old code that needed changing with comments. The new FigureWidget is meant to be compatible with the ordinary Figure that you were using.
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
from plotly.graph_objs import *
import datetime as dt
# list_date = [dt.datetime(2016,1,1).date(), dt.datetime(2016,1,2).date(), dt.datetime(2016,1,3).date(), dt.datetime(2016,1,4).date()]
list_date = [dt.datetime(2016,1,1), dt.datetime(2016,1,2), dt.datetime(2016,1,3), dt.datetime(2016,1,4)]
data = []
for i in range(3) :
list = [i/2+1, i/2+2, i/2+3, i/2+4]
data.append(Scatter(x=list_date, y=list, name='y'+str(i)))
# figure = Figure(data=data)
# iplot(figure)
figure = FigureWidget(data=data)
figure
I've commented out the portions that were changed so you can see them for reference.
One other thing to note, due to a problem within ipykernel.json_util, the json_clean function that serializes Plotly JSON objects to show on your Jupyter screen doesn't know what to do with a datetime.date object -- only datetime objects. If you don't remove the .date you will get an exception and no graph. I'm guessing this would happen for datetime.time objects as well because it seems it is also unhandled in the current ipykernel code.
When you're ready to run your updated code, you simple create your data and use the add_trace function:
list_bar = [0.5, 1.5, 2.5, 3.5]
figure.add_trace(Bar(x=list_date, y=list_bar, name='bar'))
And your plot automatically updates in the previous cell with the added trace.
Lastly, there's a good guide about the new FigureWidget for those interested.

Categories

Resources