I have to create a graph with various curves to plot. My goal is to create a dropdown widget where I can select the curve I want to plot, i.e. choose the data my iplot is going to plot.
I am using Jupyter Notebook. I imported iplot and initialized notebook mode.
from plotly.offline import iplot, init_notebook_mode
My x axis is always the same: X = [1,2,3,4,5,6,7,8,9,10,11,12].
My y data is what I want to be able to change through the widget: I have a list of lists, and I want to be able to choose the index of the list I want to plot.
So far, I have a graph where I am ploting all the data and a dropdown widget that does nothing:
k=len(Y)
updatemenus = [
{
'buttons': [
{
'method': 'restyle',
'label': str(i),
'args': [
{},
]
} for i in range(k)
],
'direction': 'down',
'showactive': True,
}
]
iplot({
'data':
[{'x': [1,2,3,4,5,6,7,8,9,10,11,12],
'y': Y[i],
'name': str(i)
} for i in range(k)],
'layout': {'title': 'Title',
'updatemenus': updatemenus}
})
I have the sense that I have to write something in the 'args' of the updatemenus, but I can't find what it would be. I've tried things like 'y': Y[i], but it just plots a straight line as if there was no 'y' axis indicated in the iplot.
So I have found a solution, although it is probably not ideal: in the args option of updatemenus, I've now written {'visible': [False]*i+[True]+[False]*(k-i-1)}.
And for the graph to only plot the first curve when I execute the program, I've modified the iplot command like this :
iplot({
'data':
[{'x': [1,2,3,4,5,6,7,8,9,10,11,12],
'y': Y[i],
'name': str(i),
'visible': i==0
} for i in range(k)],
'layout': {'title': 'Title',
'updatemenus': updatemenus}
})
Related
I would like to install a dropdown filter in python plotly - without using dash.
The solution I came up with works in case that xaxis/yaxis ranges do not change, e.g. a heatmap. In case the ranges change, it does not work well anymore.
Example: A barchart with country information. Works well for Italy and Canada. If I switch to Germany with higher population numbers than the initial country Italy, the yaxis range does not increase. How could this be fixed? Or is there a more efficient way in general?
Thanks a lot for helpful suggestions!
Example:
# Data
df = px.data.gapminder()
df = df[['country','year', 'pop']]
df.head(3)
# Create Barchart
def generate_barchart(ins):
fig = px.bar(df[df['country']==ins], x='year', y='pop')
return fig
# Dropdown: Content
uplist1 = ['Italy', 'Canada', 'Germany']
uplist2 = [generate_barchart(ins) for ins in uplist1]
# Dropdown: Implementation
upfilter = [{'method': 'animate', 'label': i1, 'args': [i2]} for i1, i2 in zip(uplist1, uplist2)]
updatemenus = [{'buttons': upfilter}]
# Initial barchart
fig = go.Figure(uplist2[0])
# Add dropdown
fig.update_layout(updatemenus=updatemenus)
# Result
fig
Works well for Italy and Canada:
Outside range:
I see two options how to make the Y axis right (besides manually resetting it). Both set range for Y axis.
This alreay almost it, except the animation is a bit off (bars may go
beyond or out of the viewport).
def generate_barchart(ins):
plot_df = df[df['country'] == ins]
fig = px.bar(plot_df, x='year', y='pop')
fig.update_yaxes(range=[plot_df['pop'].min(), plot_df['pop'].max()])
return fig
To fix it you can disable animation effect by making buttons this way.
upfilter = [
{
'method': 'animate',
'label': i1,
'args': [
i2,
{
'frame': {'duration': 0, 'redraw': False},
'mode': 'immediate',
'transition': {'duration': 0},
},
]
}
for i1, i2 in zip(uplist1, uplist2)
]
Alternatively you can define the range across countries.
def generate_barchart(ins):
fig = px.bar(df[df['country'] == ins], x='year', y='pop')
fig.update_yaxes(range=[
df[df['country'].isin(uplist1)]['pop'].min(),
df[df['country'].isin(uplist1)]['pop'].max(),
])
return fig
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!
I am building a Dash app and attempting to utilize Plotly's category_order and color_discrete_sequence to assign a specific color to unique string values that reside in a dataframe column. These parameters seem to be described as taking non-numerical data (e.g. strings) and assigning them colors in an order the user specifies. However, the code doesn't seem to take and I only get monochromatic black dots for all datapoints (image below). Hoping someone can see what I'm missing and offer guidance how to use these parameters, or another process, to get the intended result.
My process is as follows:
I have a dataframe containing the columns "band," "lat," and "lon," where lat and lon will specify the location of a point in a scattermapbox plot and band takes on a list of values such as:
data = {'band':['10m','10m','6m','2m','2m','2m','1.25m','70cm','33cm','33cm','33cm','23cm'],
'Lat': ['35.5','34.2','35.9','36.1','35.2','36.2','33.9','36.4','35.1','34.9','32.9','35.0'],
'Lon': ['-78.5','-77.3','-79.0','-78.6','-79.3','-77.0','-78.5','-77.7','-79.9','-78.8','-79.1','-79.0']}
df = pd.DataFrame.from_dict(data)
This mapplot will need its colors to correspond with other charts, so I want to use category_order as the documentation reads...
By default, in Python 3.6+, the order of categorical values in axes, legends and facets depends on the order in which these values are first encountered in data_frame (and no order is guaranteed by default in Python below 3.6). This parameter is used to force a specific ordering of values per column. The keys of this dict should correspond to column names, and the values should be lists of strings corresponding to the specific display order desired."
band_categories = {'band':['10m', '6m', '2m', '1.25m', '70cm', '33cm', '23cm']}
is set to establish the order I wish the scattermapbox to use.
Lastly, color_discrete_sequence works directly with category_order by definition...
Strings should define valid CSS-colors. When color is set and the values in the corresponding column are not numeric, values in that column are assigned colors by cycling through color_discrete_sequence in the order described in category_orders, unless the value of color is a key in color_discrete_map.
so it receives my desired color order band_colors_list = ['green', 'red', 'blue', 'orange', 'purple', 'gray', 'yellow']
The final marker dict is compiled and is assigned to the scattermapbox dict and pushed to the Dash element.
import pandas as pd
import numpy as np
from pandas.api.types import CategoricalDtype
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
data = {'band':['10m','10m','6m','2m','2m','2m','1.25m','70cm','33cm','33cm','33cm','23cm'],
'Lat': ['35.5','34.2','35.9','36.1','35.2','36.2','33.9','36.4','35.1','34.9','32.9','35.0'],
'Lon': ['-78.5','-77.3','-79.0','-78.6','-79.3','-77.0','-78.5','-77.7','-79.9','-78.8','-79.1','-79.0']}
df = pd.DataFrame.from_dict(data)
df.head()
token = drop_mapbox_token_string_here
layers = []
band_categories = ['10m', '6m', '2m', '1.25m', '70cm', '33cm', '23cm']
band_colors_list = ['green', 'red', 'blue', 'orange', 'purple', 'gray', 'yellow']
app = dash.Dash(__name__)
template = {'layout': {'paper_bgcolor': "#f3f3f1", 'plot_bgcolor': "#f3f3f1"}}
def blank_fig(height):
"""
Build blank figure with the requested height
"""
return {
'data': [],
'layout': {
'height': height,
'template': template,
'xaxis': {'visible': False},
'yaxis': {'visible': False},
}
}
# Build Dash layout
app.layout = html.Div(children=[
html.Div(children=[
dcc.Graph(
id='map-graph',
figure=blank_fig(500),
config={'displayModeBar': False},
),],
id="map-div"
)
])
#app.callback(
Output('map-graph', 'figure'),
[Input('map-graph', 'relayoutData')])
def update_plots(relayout_data):
marker = {
'color': df.band,
'category_order': band_categories,
'color_discrete_sequence': band_colors_list,
'size': 5,
'opacity': 0.6
}
map_graph = {'data': [{
'type': 'scattermapbox',
'lat': df.Lat, 'lon': df.Lon,
'marker': marker
}],
'layout': {
'template': template,
'uirevision':True,
'mapbox':{
'style': "light",
'accesstoken': token,
'layers': layers,
},
'margin': {"r": 0, "t": 0, "l": 0, "b": 0},
'height': 500
},
}
map_graph['layout']['mapbox'].update()
return (map_graph)
app.scripts.config.serve_locally = True
app.css.config.serve_locally = True
app.run_server(debug=True, use_reloader=False)
Solved the issue.
Ultimately, I was getting mixed up with components in Plotly Express and various scatter map components in Plotly Graph Objects. While category_order and discrete_color_sequence are resident in Plotly Express scatters, they are not in Plotly Graph Object's Scattermapbox.
While one avenue would be to convert to using one of these other components that allow for discrete color definitions, I instead went with a less invasive approach. Simply define a new dataframe column that has preset colors as a function of the band column and push this column into the color parameter under marker.
band_categories = ['10m', '6m', '2m', '1.25m', '70cm', '33cm', '23cm']
band_colors_list = ['green', 'red', 'blue', 'orange', 'purple', 'gray', 'yellow']
band_dict = dict(zip(band_categories,band_colors_list)) #set up band to color substitution dict
df['color'] = df['band'].replace(to_replace=band_colorscale)
and later...
marker = {
'color': df.color,
'size': 5,
'opacity': 0.6
}
Arguably less elegant and memory intensive, since we're storing extra attributes for a large dataset, but it works.
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.)
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.