Can Plotly timeline be used / reproduced in Jupyter Notebook Widget? - python

The plotly plotly.express.timeline is marvelous, but creates it's own figure. It seems like I need to embed this visual in a FigureWidget to get it to play nice with the layout in a Jupyter Notebook. So I am trying to re-create the plot using the plotly.graph_objects.Bar() that px.timeline() is built upon.
Unfortunately, I can't figure out how to accomplish this. It appears that the values for the bars are added to the 'base' vector (as a relative value) not used as absolute positions. Plotly does not appear to understand datetime.timedelta() objects. Printing the timeline() figure version shows the values as
an array of floating point values which it isn't clear how they are computed. I've tried simply copying them, but this ends up with plotly thinking the x axis isn't a datetime axis.
Any clue would be most welcome. Either how to use the Box() to draw the appropriate figure, or how to embed/animate/layout the px.timeline() figure in a notebook.
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime
# the data:
df = pd.DataFrame([
dict(Task="one", Start=datetime(2009,1,1), Finish=datetime(2009,4,28)),
dict(Task="two", Start=datetime(2009,5,5), Finish=datetime(2009,7,15)),
dict(Task="three", Start=datetime(2009,7,20), Finish=datetime(2009,9,30))
])
# working plotly express figure:
pxfig = px.timeline(df, x_start="Start", x_end="Finish", y="Task")
pxfig.show() # looks great
# Broken bar figure:
plainfig = go.Figure()
plainfig.add_bar(base=df['Start'],
# x=pxfig.data[0].x, # this breaks the axis as they are not of type datetime.
# x=df['Finish']-df['Start'], # this doesn't produce the right plot
x=df['Finish'], # these appear to be relative to base, not absolute
y=df['Task'], orientation='h')
plainfig.show()
# looking at the two shows interesting differences in the way the x data is stored
print(pxfig)
print(plainfig)
Figure({
'data': [{'alignmentgroup': 'True',
'base': array([datetime.datetime(2009, 1, 1, 0, 0),
datetime.datetime(2009, 5, 5, 0, 0),
datetime.datetime(2009, 7, 20, 0, 0)], dtype=object),
'x': array([1.01088e+10, 6.13440e+09, 6.22080e+09]),
'xaxis': 'x',
'y': array(['one', 'two', 'three'], dtype=object),
'yaxis': 'y'}],
'layout': {'barmode': 'overlay',
'legend': {'tracegroupgap': 0},
'margin': {'t': 60},
'template': '...',
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'type': 'date'},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'Task'}}}
})
Figure({
'data': [{'base': array([datetime.datetime(2009, 1, 1, 0, 0),
datetime.datetime(2009, 5, 5, 0, 0),
datetime.datetime(2009, 7, 20, 0, 0)], dtype=object),
'orientation': 'h',
'type': 'bar',
'x': array([datetime.datetime(2009, 4, 28, 0, 0),
datetime.datetime(2009, 7, 15, 0, 0),
datetime.datetime(2009, 9, 30, 0, 0)], dtype=object),
'y': array(['one', 'two', 'three'], dtype=object)}],
'layout': {'template': '...'}
})

I can't answer how to embed the timeline in a FigureWidget, but I think I have the answer to your original problem of getting the timeline to play nicely with the jupyter notebook layout. I'm guessing you want to be able to update the timeline interactively?
I have gotten around this problem by embedding the figure produced by px.timeline in an output widget. Then whenever I need the figure to be updated (from a button callback, for example) I just clear the output in the output widget, create a new timeline figure and display that new figure. It's not the most elegant way of doing things but it gets the job done.
import ipywidgets as widgets
from IPython.display import display, clear_output
import pandas as pd
import plotly.express as px
from datetime import datetime
output = widgets.Output()
df = pd.DataFrame([
dict(Task="one", Start=datetime(2009,1,1), Finish=datetime(2009,4,28)),
dict(Task="two", Start=datetime(2009,5,5), Finish=datetime(2009,7,15)),
dict(Task="three", Start=datetime(2009,7,20), Finish=datetime(2009,9,30))
])
updated_df = pd.DataFrame([
dict(Task="one", Start=datetime(2009,1,1), Finish=datetime(2009,4,28)),
dict(Task="two", Start=datetime(2009,5,5), Finish=datetime(2009,7,15)),
dict(Task="three", Start=datetime(2009,7,20), Finish=datetime(2009,9,30)),
dict(Task="four", Start=datetime(2009,10,5), Finish=datetime(2009,10,10))
])
# display the original timeline figure
pxfig = px.timeline(df, x_start="Start", x_end="Finish", y="Task")
with output:
display(pxfig)
# create a button which when pressed will update the timeline figure
button = widgets.Button(description='update figure')
def on_click(button):
with output:
clear_output()
new_pxfig = px.timeline(updated_df, x_start="Start", x_end="Finish", y="Task")
display(new_pxfig)
button.on_click(on_click)
display(button)

Related

Plotly: How to add text labels to a histogram?

Is there a way how to display the counted value of the histogram aggregate in the Plotly.Express histogram?
px.histogram(pd.DataFrame({"A":[1,1,1,2,2,3,3,3,4,4,4,5]}),x="A")
If I would use regular histogram, I can specify text parameter which direct to the column which contain the value to display.
px.bar(pd.DataFrame({"val":[1,2,3,4,5], "height": [3,2,3,3,1]}), x="val", y="height", text="height")
But with histograms, this value is calculated and it's not even part of the fig.to_dict(). Is there a way to add the text labels into histogram?
Using the answers below, I've summarized the finding to an article - https://towardsdatascience.com/histograms-with-plotly-express-complete-guide-d483656c5ad7
The text_auto parameter set to True will do what you want.
Taking your example code, this is what i get :
fig = px.histogram(pd.DataFrame({"A":[1,1,1,2,2,3,3,3,4,4,4,5]}),x="A",
text_auto=True)
fig.show()
Being a new member i cannot embed the screenshot yet, but here is a link.
Histogram
A bit late but hope this will help.
As far as I know, plotly histograms do not have a text attribute. It also turns out that it's complicated if at all possible to retrieve the applied x and y values and just throw them into appropriate annotations. Your best option seems to be to take care of the binning using numpy.histogram and the set up your figure using go.Bar. The code snippet below will produce the following plot:
Complete code:
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
# sample data
df = px.data.tips()
# create bins
bins = [0, 10, 20, 30, 40, 50]
counts, bins = np.histogram(df.total_bill, bins=bins)
#bins2 = 0.5 * (bins1[:-1] + bins2[1:])
fig = go.Figure(go.Bar(x=bins, y=counts))
fig.data[0].text = counts
fig.update_traces(textposition='inside', textfont_size=8)
fig.update_layout(bargap=0)
fig.update_traces(marker_color='blue', marker_line_color='blue',
marker_line_width=1, opacity=0.4)
fig.show()
I had his same problem this morning while trying to plot a histogram of TDD percentages. Using plotly, I wanted to normalize (histnorm: 'percent') so I could see percentages of my monthly TDD values instead of the counts. I found this solution by simply doing a print(tdd_hist)
First, I printed the histogram to the console and saw this output...
Figure({
'data': [{'alignmentgroup': 'True',
'bingroup': 'x',
'histnorm': 'percent',
'hovertemplate': 'Total Demand Distortion TDD %=%{x}<br>count=%{y}<extra></extra>',
'legendgroup': '',
'marker': {'color': '#636efa'},
'name': '',
'offsetgroup': '',
'orientation': 'v',
'showlegend': False,
'type': 'histogram',
'x': array([0.67, 0.68, 0.68, ..., 2.41, 2.48, 2.01]),
'xaxis': 'x',
'yaxis': 'y'}],
'layout': {'barmode': 'relative',
'legend': {'tracegroupgap': 0},
'template': '...',
'title': {'text': 'Percent Histogram of TDD%'},
'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'Total Demand Distortion TDD %'}},
'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'count'}, 'type': 'log'}}
Now I can clearly see that to change this, I do a
tdd_hist.layout.yaxis.title.text = 'Percent'
And it works!

Plotly: How to update one specific trace using updatemenus?

This is a follow-up question to Plotly: Plotly: How do the buttons for the update menus really work?
Consider the following plotly figure produced by the code snippet below:
Plot:
Code:
# imports
import plotly.graph_objs as go
import pandas as pd
import numpy as np
# data
df1 = pd.DataFrame({'index': ['1','2','3'], 'A': [10,10,12], 'B': [11,11,11]})
df2 = pd.DataFrame({'index': ['1','2','3'], 'A': [10,10,10], 'B': [11,11,12]})
# plotly figure setup
fig=go.Figure()
fig.add_trace(go.Scatter(x=df1['index'], y=df1['A'], mode='lines'))
fig.add_trace(go.Scatter(x=df1['index'], y=df1['B'], mode='lines'))
f=fig.to_dict()
#fig.show()
buttons=list([dict(args=[{'y':[df1['A'],df1['B']]}],
label="df1",
method="restyle"
),
dict(args=[{'y':[df2['A'], df2['B']]}],
label="df2",
method="restyle"
)
])
fig.update_layout(
updatemenus=[
go.layout.Updatemenu(
buttons=buttons,
direction="down",
pad={"r": 10, "t": 10},
showactive=True,
x=-0.25,
xanchor="left",
y=1,
yanchor="top"
),
]
)
fig.show()
In the snippet above, I'm updating the 'y' values using buttons and dict(args=[{'y':[df2['A'], df2['B']]}]. This assigns new values to both traces specified within the figure like this fig-to_dict:
'data': [{'mode': 'lines',
'x': array(['1', '2', '3'], dtype=object),
'y': array([10, 10, 12], dtype=int64),
'type': 'scatter'},
{'mode': 'lines',
'x': array(['1', '2', '3'], dtype=object),
'y': array([11, 11, 11], dtype=int64),
'type': 'scatter'}]
Since I've assigned the list [df2['A'], df2['B']] to 'y', plotly knows that I intend to update both instances of 'y' in the snippet above. But within the context of buttons and update menus, is there a way I can specify which 'y' to update (in other words: what specific trace or line).
If I assign only one reference (array or pandas dataframe in this case), both traces will show the same values. So changing the following part:
args=[{'y':[df2['A'], df2['B']]}]
...with this:
args=[{'y':[df2['A']]}]
... will produce the following plot upon clicking df2:
And I'd really like to keep all unspecified 'y' and traces unchanged.
Thank you for any suggestions!
In the list you are passing to args for each button, you can add an integer after the dict to indicate which trace you want to update. For example the following will update the first trace only (i.e. the one at index=0)
buttons=list([dict(args=[{'y':[df1['A'],df1['B']]}, [0]], # note the `, [0]` here!
label="df1",
method="restyle"
),
dict(args=[{'y':[df2['A'], df2['B']]}, [0], # note the `, [0]` here!
label="df2",
method="restyle"
)
])

Transition not showing for animated polarbar plot with plotly (offline)

I am trying to make a polar barplot rotate smoothly using plotly in offline mode. Following the examples available in the docs, I do this by creating a button with as method "animate" and setting the transition time to a value >0 ms.
The same problem occurred when using a scatterpolar type plot instead of a barplot, however the animation did work for a non-polar type scatter plot.
import plotly.graph_objs as go
import plotly.offline as offline
import pandas as pd
import numpy as np
offline.init_notebook_mode()
#some data to plot:
df = pd.DataFrame({'artist':['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
'birth': pd.to_datetime(pd.Series(['1990-04-01T00:00:00.000000000', '1945-12-01T00:00:00.000000000',
'1955-01-01T00:00:00.000000000', '1956-01-01T00:00:00.000000000',
'1976-12-01T00:00:00.000000000', '1930-05-01T00:00:00.000000000',
'1942-01-01T00:00:00.000000000', '1936-11-01T00:00:00.000000000',
'1971-12-01T00:00:00.000000000', '1952-12-01T00:00:00.000000000'])),
'death': pd.to_datetime(pd.Series(['2012-04-01T00:00:00.000000000', '2015-12-01T00:00:00.000000000',
'2010-01-01T00:00:00.000000000', '2017-01-01T00:00:00.000000000',
'2016-12-01T00:00:00.000000000', '2017-05-01T00:00:00.000000000',
'2010-01-01T00:00:00.000000000', '2015-11-01T00:00:00.000000000',
'2014-12-01T00:00:00.000000000', '2013-12-01T00:00:00.000000000']))} )
#creating the barplot:
shift = df['birth'] - pd.datetime(1970, 1 ,1)
trace = {
'name': "to",
'r': (df['death']- shift).dt.date,
'theta': np.linspace(0,360,11),
'base':df['birth'].dt.date,
'type': 'barpolar'
}
data = [trace]
nsteps = 20
tracedicts = []
start_thetas = np.linspace(0,360,nsteps)
for i in start_thetas:
tracedicts.append(trace.copy())
tracedicts[-1]['theta'] = np.linspace(i,360+i,11)
frames = [{'data': [tracei]} for tracei in tracedicts]
layout = {
'polar':{
'angularaxis':{
'visible': False,
},
'radialaxis':{
'showgrid': True,
'type': 'date',
'hoverformat': '%m-%Y',
}
},
'updatemenus': [{
'type': 'buttons',
'x': 0.1,
'y': 0,
'buttons':[{'label':'Play', 'method':'animate',
'args':[None, {'frame':{'duration':600, 'redraw':True},
'transition':{'duration':400},
'fromcurrent':True,
'easing': 'linear'}]}]
}],
}
fig = go.Figure(data=data, layout=layout, frames = frames)
offline.iplot(fig,auto_play=False)
The animation works as far as showing the different frames, but the transition does not work.
Is this a feature that simply does not exist for all polar and/or bar plots?
(You'll also notice that I set 'redraw':True - this is because otherwise the animation only worked when setting auto_play=True at the end.)

Python: How to get data from linked brushes in mlpd3, Bokeh, Plotly?

Using the code below I can get a 2x2 graph with 4 plots. With brushes, I can select some data points. The question I have is how do get the selected data points as a JSON array or cvs. This code uses mlpd3, but bokeh can do similar selections with brushes.. But there is no example of selecting the data points. I am trying to get selected data as object to continue processing with python. It would be nice to see the data in a cell.
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mat
import mpld3
mpld3.enable_notebook()
from mpld3 import plugins
fig, ax = plt.subplots(2, 2, figsize=(10, 8))
fig.subplots_adjust(hspace=0.1, wspace=0.1)
ax = ax[::-1]
X = np.random.normal(size=(2, 100))
for i in range(2):
for j in range(2):
ax[i, j].xaxis.set_major_formatter(plt.NullFormatter())
ax[i, j].yaxis.set_major_formatter(plt.NullFormatter())
points = ax[i, j].scatter(X[j], X[i])
plugins.connect(fig, plugins.LinkedBrush(points))
Bokeh has similar behavior in CustomJS for Selections
http://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#userguide-interaction-jscallbacks-customjs-interactions
Whichever one is easier to extract the selected item -- would work.. If there is a Plotly solution, that would also work.
You can get the selected data from a Plotly chart by using Plotly's new Dash framework.
There is an example in the docs here under "Graph Crossfiltering" https://plot.ly/dash/getting-started-part-2
I've pasted the full example below just for preservation of history.
In each of the callbacks below, you have access to the either the selected points, the points that you just hovered over, or the points that you just clicked on. This app simply displays the values of the points in the app, but you could do anything with the points (e.g. compute something else).
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import json
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Graph(
id='basic-interactions',
figure={
'data': [
{
'x': [1, 2, 3, 4],
'y': [4, 1, 3, 5],
'text': ['a', 'b', 'c', 'd'],
'customdata': ['c.a', 'c.b', 'c.c', 'c.d'],
'name': 'Trace 1',
'mode': 'markers',
'marker': {'size': 12}
},
{
'x': [1, 2, 3, 4],
'y': [9, 4, 1, 4],
'text': ['w', 'x', 'y', 'z'],
'customdata': ['c.w', 'c.x', 'c.y', 'c.z'],
'name': 'Trace 2',
'mode': 'markers',
'marker': {'size': 12}
}
]
}
),
html.Div([
dcc.Markdown("""
**Hover Data**
Mouse over values in the graph.
""".replace(' ', '')),
html.Pre(id='hover-data')
], style=styles['column']),
html.Div([
dcc.Markdown("""
**Click Data**
Click on points in the graph.
""".replace(' ', '')),
html.Pre(id='click-data'),
], style=styles['column']),
html.Div([
dcc.Markdown("""
**Selection Data**
Choose the lasso or rectangle tool in the graph's menu
bar and then select points in the graph.
""".replace(' ', '')),
html.Pre(id='selected-data'),
])
])
#app.callback(
Output('hover-data', 'children'),
[Input('basic-interactions', 'hoverData')])
def display_hover_data(hoverData):
#
# This is where you can access the hover data
# This function will get called automatically when you hover over points
# hoverData will be equal to an object with that data
# You can compute something off of this data, and return it to the front-end UI
#
return json.dumps(hoverData, indent=2)
#app.callback(
Output('click-data', 'children'),
[Input('basic-interactions', 'clickData')])
def display_click_data(clickData):
# Similarly for data when you click on a point
return json.dumps(clickData, indent=2)
#app.callback(
Output('selected-data', 'children'),
[Input('basic-interactions', 'selectedData')])
def display_selected_data(selectedData):
# Similarly for data when you select a region
return json.dumps(selectedData, indent=2)
if __name__ == '__main__':
app.run_server(debug=True)
This is outside of ipython but you can run flask or django in conjunction with d3.js and jquery to get the data back into python.

how to create barchart in python xlsxwriter that does not start at 0?

I found the following image that consists exactly the chart that I want to be able to create, however I can't figure out how to start the bar somewhere else than zero. Does anybody know how to solve this?
as mentioned below in the comments it is a line chart with up_down_bars.
How can I get an example of this to work?
import xlsxwriter
workbook = xlsxwriter.Workbook('chart_line.xlsx')
worksheet = workbook.add_worksheet()
bold = workbook.add_format({'bold': 1})
# Add the worksheet data that the charts will refer to.
headings = ['Number', 'Batch 1', 'Batch 2']
data = [
[2, 3, 4, 5, 6, 7],
[10, 40, 50, 20, 10, 50],
[30, 60, 70, 50, 40, 30],
]
worksheet.write_row('A1', headings, bold)
worksheet.write_column('A2', data[0])
worksheet.write_column('B2', data[1])
worksheet.write_column('C2', data[2])
# Create a new chart object. In this case an embedded chart.
chart1 = workbook.add_chart({'type': 'line'})
# Configure the first series.
chart1.add_series({
'name': '=Sheet1!$B$1',
'categories': '=Sheet1!$A$2:$A$7',
'values': '=Sheet1!$B$2:$B$7',
})
# Configure second series. Note use of alternative syntax to define ranges.
chart1.add_series({
'name': ['Sheet1', 0, 2],
'categories': ['Sheet1', 1, 0, 6, 0],
'values': ['Sheet1', 1, 2, 6, 2],
})
# Add a chart title and some axis labels.
chart1.set_title ({'name': 'Results of sample analysis'})
chart1.set_x_axis({'name': 'Test number'})
chart1.set_y_axis({'name': 'Sample length (mm)'})
chart1.set_up_down_bars({
'up': {
'fill': {'color': '#00B050'},
'border': {'color': 'black'}
},
'down': {
'fill': {'color': 'red'},
'border': {'color': 'black'},
},
})
# Set an Excel chart style. Colors with white outline and shadow.
chart1.set_style(10)
# Insert the chart into the worksheet (with an offset).
worksheet.insert_chart('D2', chart1, {'x_offset': 25, 'y_offset': 10})
workbook.close()
I'm going to guess that by "can't figure out how to start the bar somewhere else than zero" you mean that you don't want the Y-Axis to start from 0.
You can change that in the same way that you would in Excel: by changing the minimum value for the axis range. See the set_x_axis() section of the docs and also Working with Charts.
chart.set_y_axis({'min': 10})
If that isn't what you are looking for then you probably need to clarify your question.
Also, to avoid confusion, the above image isn't a bar chart. It is a line chart with Up-Down bars. If you need a bar chart that is also shown in the docs and the examples.

Categories