Plotly: How to add text labels to a histogram? - python

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!

Related

How to plot a ternary chart with lines using Plotly python?

I need to create a kind of "ternary" chart but instead of showing only points I need to set the lines, similar to the chart below:
I have created a data sample but I'm not 100% sure that this it has the correct data structure to build the chart
import pandas as pd
dummy_data=[{"var1":0.74, "var2":.60, "var3":.78, "comp":"option1"},
{"var1":0.8, "var2":0.75, "var3":0.72, "comp":"option2"}]
table=pd.DataFrame.from_dict(dummy_data)
I did a lot of searches but the most similar alternative I found was scatter_ternary which only plots points;
Any help will be very welcome!
Thank you in advance;
Regards,
Leonardo
I am new to this chart. I created the graph by replacing the examples in the official reference with lines. First of all, I needed four pieces of data from the start point to the end point. A->B->C->A Then the sum of the ABC points of that data must be the same. In my example, the sum is 1. After that I added the graph with as much triangular data as I needed.
import pandas as pd
dummy_data=[
{"var1":0.7, "var2":0.15, "var3":0.15, "comp":"option1"},
{"var1":0.15, "var2":0.7, "var3":0.15, "comp":"option1"},
{"var1":0.15, "var2":0.15, "var3":0.7, "comp":"option1"},
{"var1":0.7, "var2":0.15, "var3":0.15, "comp":"option1"},
{"var1":0.6, "var2":0.2, "var3":0.2, "comp":"option2"},
{"var1":0.2, "var2":0.6, "var3":0.2, "comp":"option2"},
{"var1":0.2, "var2":0.2, "var3":0.6, "comp":"option2"},
{"var1":0.6, "var2":0.2, "var3":0.2, "comp":"option2"}
]
table=pd.DataFrame.from_dict(dummy_data)
import plotly.graph_objects as go
fig = go.Figure()
table1 = table[table['comp'] == 'option1']
fig.add_trace(go.Scatterternary(
text=table1['comp'],
a=table1['var1'],
b=table1['var2'],
c=table1['var3'],
mode='lines',
line_color='red',
name='option1'
))
table2 = table[table['comp'] == 'option2']
fig.add_trace(go.Scatterternary(
text=table2['comp'],
a=table2['var1'],
b=table2['var2'],
c=table2['var3'],
mode='lines',
line_color='black',
name='option2'
))
fig.update_layout({
'title': 'Ternary Line Plot',
'ternary':
{
'sum':1,
'aaxis':{'title': 'A', 'min': 0.01, 'linewidth':2, 'ticks':'outside' },
'baxis':{'title': 'B', 'min': 0.01, 'linewidth':2, 'ticks':'outside' },
'caxis':{'title': 'C', 'min': 0.01, 'linewidth':2, 'ticks':'outside' }
},
'showlegend': False
})
fig.update_layout(showlegend=True)
fig.show()

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

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)

Assigning points discrete colors in Plotly mapbox scatter

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.

Save the Path2D as a .PNG or .JPEG from .to_planar() function of Trimesh Module in Python

My main aim is to get the Drawing views of a STL file. I have tried the SolidWorks Method of converting a STL file to SLDPRT and then take the drawing views, but the drawing views in that case contains a lot of noise and is not accurate. SO, I am trying the Trimesh module. So far, with
slicex = meshx.section(plane_origin=meshx.centroid, plane_normal=[0,0,30])
slice_2D, to_3D = slicex.to_planar()
slice_2D.show()
and by changing the Plane_normal array values, I get the required cross section (which is somewhat similar to the three views), but I do not know how to save the image shown in the Console of Spyder as JPEG or PNG. I need the drawing views for further image analysis.
Any leads on this method or any other method to get the drawing views would be much appreciated! Thank you!
The best results I've seen for this is using matplotib and saving a scatter plot. You are able to adjust the resolution or save as a vector once it is in that format:
import matplotlib.pylab as plt
slicex = meshx.section(plane_origin=meshx.centroid, plane_normal=[0,0,30])
slice_2D, to_3D = slicex.to_planar()
fig, ax = plt.subplots(figsize=(16,8))
ax.set_aspect('equal')
_ = ax.scatter(slice_2D.vertices[:,0], slice_2D.vertices[:,1], color='lightgray')
ax.axis('off')
plt.savefig('meshx_slice.png')
More details on the file format and options are here. This also works well for saving a full 2D mesh or a planar view of a 3D mesh, using the Trimesh.vertices as points.
ALTERNATIVE
If you're wanting to replicate what slice_2D.show() does, you can just borrow from its code (which uses matplotlib):
import matplotlib.pyplot as plt
# keep plot axis scaled the same
plt.axes().set_aspect('equal', 'datalim')
# hardcode a format for each entity type
eformat = {'Line0': {'color': 'g', 'linewidth': 1},
'Line1': {'color': 'y', 'linewidth': 1},
'Arc0': {'color': 'r', 'linewidth': 1},
'Arc1': {'color': 'b', 'linewidth': 1},
'Bezier0': {'color': 'k', 'linewidth': 1},
'Bezier1': {'color': 'k', 'linewidth': 1},
'BSpline0': {'color': 'm', 'linewidth': 1},
'BSpline1': {'color': 'm', 'linewidth': 1}}
for entity in slice_2D.entities:
# if the entity has it's own plot method use it
if hasattr(entity, 'plot'):
entity.plot(slice_2D.vertices)
continue
# otherwise plot the discrete curve
discrete = entity.discrete(slice_2D.vertices)
# a unique key for entities
e_key = entity.__class__.__name__ + str(int(entity.closed))
fmt = eformat[e_key].copy()
if hasattr(entity, 'color'):
# if entity has specified color use it
fmt['color'] = entity.color
plt.plot(*discrete.T, **fmt)
plt.savefig('meshx_slice.png')

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.)

Categories