I'm trying to plot a polar chart with python and plotly to show the orientation of fibers in an 2D image (see the code below).
It would be great if you could show me how to change the angular axis, currently ranging from 0° to 360°, to match my data ranging from -90° to 90°.
Below are example images of a half polar chart, the format of my data, and my code.
import plotly.graph_objs as go
from plotly import plotly
import plotly.offline as offline
import numpy as np
# data
x= np.genfromtxt(see data image)
y= np.genfromtxt(see data image)
trace = [
go.Scatterpolar(
r = [y], #radial coordinates
theta = [x], #angular coordinates
mode = 'markers',
marker = dict(
color = 'peru'
)
)
]
layout = go.Layout(
showlegend = True,
polar = dict(
domain = dict( # set chart size and position
x = [0, 0.8],
y = [0.3, 0.8]),
sector = [0, 180], # set chart shape (half or full)
angularaxis = dict(
thetaunit = "degrees",
dtick = 10),
radialaxis = dict(
range = [1, 8000])
))
fig = go.Figure(data=trace, layout=layout)
offline.plot(fig)
x-y data
half polar chart for fiber orientation
Thank you very much DatHydroGuy! I completely rewrote the code with your changes and it is working now. Below is the code and an image of the output. NOICE! Thanks again, HydroGuy, for you help!
If somebody knows how to rotate it (by 90° counterclockwise) and mirror it (-90 left, 0 top, 90 right) please let me know. I tried rotation= and direction= commands, but they did not work and collide with the sector= command for setting the angular axis range from -90 to 90.
Cheers, Ron
import plotly.graph_objs as go
from plotly import plotly
import plotly.offline as offline
import numpy as np
import plotly
# data
x= np.genfromtxt("data.csv", delimiter=",", usecols=(0), skip_header=1, encoding = 'unicode_escape')
y= np.genfromtxt("data.csv", delimiter=",", usecols=(1), skip_header=1, encoding = 'unicode_escape')
trace = [
go.Scatterpolar(
r = [float(a) for a in y], #radial coordinates
theta = [float(b) for b in x], #angular coordinates
mode = 'lines',
marker = dict(
color = 'peru'
)
)
]
layout = go.Layout(
showlegend = True,
legend=dict(
x=1),
polar = dict(
domain = dict( # set chart size and position
x = [0, 1],
y = [0, 1]),
sector = [-90,90], # set chart shape (half or full)
angularaxis = dict(
thetaunit = "degrees",
dtick = 10,
#rotation = -90, #does not work
#direction = "clockwise" # does not work
),
radialaxis = dict(
range = [1, 6500])
))
fig = go.Figure(data=trace, layout=layout)
offline.plot(fig)
plotly half polar plot
OK, so I only got output by making the following changes:
Changed the go.Layout sector=[0, 180] to sector=[-90, 90]
Changed the go.Scatterplot r=[y] to r=[float(a) for a in y]
Changed the go.Scatterplot theta=[x] to theta=[float(b) for b in x]
The first change created the range that you require (-90 to 90)
The 2nd and 3rd changes converted the results from np.genfromtext() from strings to numeric (although these changes may not be necessary if your data is already numerical within x and y).
My code now looks like this:
import plotly.graph_objs as go
import plotly.offline as offline
import numpy as np
# data
x = np.genfromtxt([str(i) for i in range(-90, 91)])
y = np.genfromtxt([str(i * 25 + np.random.randint(1000, 2500)) for i in range(0, 181)])
trace = [
go.Scatterpolar(
r=[float(a) for a in y], # radial coordinates
theta=[float(b) for b in x], # angular coordinates
thetaunit="degrees",
mode='markers',
marker=dict(
color='peru'
)
)
]
layout = go.Layout(
showlegend=True,
polar=dict(
domain=dict( # set chart size and position
x=[0, 0.8],
y=[0.3, 0.8]),
sector=[0, 1800], # set chart shape (half or full)
angularaxis=dict(
thetaunit="degrees",
dtick=10,
rotation=90,
direction='clockwise'),
radialaxis=dict(
range=[1, 8000])
)
)
fig = go.Figure(data=trace, layout=layout)
offline.plot(fig)
These changes were sufficient to produce the following:
Is that what you are looking for?
Related
When you set scaleanchor=x when adjusting the aspect ratio, for example to make a perfectly square heatmap using ff.annotated_heatmaps, you'll end up with x-axis labels with a large offset from the x-axis itself like this:
How can you fix that?
Code:
import numpy as np
import plotly.graph_objs as go
import plotly.figure_factory as ff
# data
z = np.random.randint(0,6, size=(10, 10))
z_text = np.full(z.shape, '', dtype=str)
d = {0:'a', 1:'b', 2:'c', 3:'d', 4:'e', 5:'f'}
class_mat = np.vectorize(d.get)(z)
# plotly figure factory annotated heatmap
fig = ff.create_annotated_heatmap(z, annotation_text=z_text,
text=class_mat, hoverinfo='text', colorscale='Viridis',
x = list('ABCDEFGHIJ'),
y = list('ABCDEFGHIJ')
)
fig.layout.title = 'Semantic Segmentation'
fig.data[0]['hoverinfo'] = 'all'
# adjustment 1: scaleanchor => squared figure
fig['layout']['yaxis']['scaleanchor']='x'
# adjustment 2: remove redunant background background
fig.update_layout(plot_bgcolor='rgba(0,0,0,0)')
fig.show()
This solution is a bit cryptic, but just make sure to include constrain='domain':
fig.update_layout(xaxis=dict(scaleanchor='y',constrain='domain'))
Plot
Complete code
import numpy as np
import plotly.graph_objs as go
import plotly.figure_factory as ff
# data
z = np.random.randint(0,6, size=(10, 10))
z_text = np.full(z.shape, '', dtype=str)
d = {0:'a', 1:'b', 2:'c', 3:'d', 4:'e', 5:'f'}
class_mat = np.vectorize(d.get)(z)
# plotly figure factory annotated heatmap
fig = ff.create_annotated_heatmap(z, annotation_text=z_text,
text=class_mat, hoverinfo='text', colorscale='Viridis',
x = list('ABCDEFGHIJ'),
y = list('ABCDEFGHIJ')
)
fig.layout.title = 'Semantic Segmentation'
fig.data[0]['hoverinfo'] = 'all'
# adjustment 1: scaleanchor => squared figure
fig['layout']['yaxis']['scaleanchor']='x'
# adjustment 2: remove redunant background background
fig.update_layout(plot_bgcolor='rgba(0,0,0,0)')
# adjustment 3: x-axis label offsets
fig.update_layout(xaxis=dict(scaleanchor='y',constrain='domain'))
fig.show()
How can I use Plotly to produce a line plot with a shaded standard deviation? I am trying to achieve something similar to seaborn.tsplot. Any help is appreciated.
The following approach is fully flexible with regards to the number of columns in a pandas dataframe and uses the default color cycle of plotly. If the number of lines exceed the number of colors, the colors will be re-used from the start. As of now px.colors.qualitative.Plotly can be replaced with any hex color sequence that you can find using px.colors.qualitative:
Alphabet = ['#AA0DFE', '#3283FE', '#85660D', '#782AB6', '#565656', '#1...
Alphabet_r = ['#FA0087', '#FBE426', '#B00068', '#FC1CBF', '#C075A6', '...
[...]
Complete code:
# imports
import plotly.graph_objs as go
import plotly.express as px
import pandas as pd
import numpy as np
# sample data in a pandas dataframe
np.random.seed(1)
df=pd.DataFrame(dict(A=np.random.uniform(low=-1, high=2, size=25).tolist(),
B=np.random.uniform(low=-4, high=3, size=25).tolist(),
C=np.random.uniform(low=-1, high=3, size=25).tolist(),
))
df = df.cumsum()
# define colors as a list
colors = px.colors.qualitative.Plotly
# convert plotly hex colors to rgba to enable transparency adjustments
def hex_rgba(hex, transparency):
col_hex = hex.lstrip('#')
col_rgb = list(int(col_hex[i:i+2], 16) for i in (0, 2, 4))
col_rgb.extend([transparency])
areacol = tuple(col_rgb)
return areacol
rgba = [hex_rgba(c, transparency=0.2) for c in colors]
colCycle = ['rgba'+str(elem) for elem in rgba]
# Make sure the colors run in cycles if there are more lines than colors
def next_col(cols):
while True:
for col in cols:
yield col
line_color=next_col(cols=colCycle)
# plotly figure
fig = go.Figure()
# add line and shaded area for each series and standards deviation
for i, col in enumerate(df):
new_col = next(line_color)
x = list(df.index.values+1)
y1 = df[col]
y1_upper = [(y + np.std(df[col])) for y in df[col]]
y1_lower = [(y - np.std(df[col])) for y in df[col]]
y1_lower = y1_lower[::-1]
# standard deviation area
fig.add_traces(go.Scatter(x=x+x[::-1],
y=y1_upper+y1_lower,
fill='tozerox',
fillcolor=new_col,
line=dict(color='rgba(255,255,255,0)'),
showlegend=False,
name=col))
# line trace
fig.add_traces(go.Scatter(x=x,
y=y1,
line=dict(color=new_col, width=2.5),
mode='lines',
name=col)
)
# set x-axis
fig.update_layout(xaxis=dict(range=[1,len(df)]))
fig.show()
I was able to come up with something similar. I post the code here to be used by someone else or for any suggestions for improvements.
import matplotlib
import random
import plotly.graph_objects as go
import numpy as np
#random color generation in plotly
hex_colors_dic = {}
rgb_colors_dic = {}
hex_colors_only = []
for name, hex in matplotlib.colors.cnames.items():
hex_colors_only.append(hex)
hex_colors_dic[name] = hex
rgb_colors_dic[name] = matplotlib.colors.to_rgb(hex)
data = [[1, 3, 5, 4],
[2, 3, 5, 4],
[1, 1, 4, 5],
[2, 3, 5, 4]]
#calculating mean and standard deviation
mean=np.mean(data,axis=0)
std=np.std(data,axis=0)
#draw figure
fig = go.Figure()
c = random.choice(hex_colors_only)
fig.add_trace(go.Scatter(x=np.arange(4), y=mean+std,
mode='lines',
line=dict(color=c,width =0.1),
name='upper bound'))
fig.add_trace(go.Scatter(x=np.arange(4), y=mean,
mode='lines',
line=dict(color=c),
fill='tonexty',
name='mean'))
fig.add_trace(go.Scatter(x=np.arange(4), y=mean-std,
mode='lines',
line=dict(color=c, width =0.1),
fill='tonexty',
name='lower bound'))
fig.show()
Great custom responses posted by others. In case someone is interested in code from the official plotly website, see here: https://plotly.com/python/continuous-error-bars/
I wrote a function to extend plotly.express.line with the same high level interface of Plotly Express. The line function (source code below) is used in the same exact way as plotly.express.line but allows for continuous error bands with the flag argument error_y_mode which can be either 'band' or 'bar'. In the second case it produces the same result as the original plotly.express.line. Here is an usage example:
import plotly.express as px
df = px.data.gapminder().query('continent=="Americas"')
df = df[df['country'].isin({'Argentina','Brazil','Colombia'})]
df['lifeExp std'] = df['lifeExp']*.1 # Invent some error data...
for error_y_mode in {'band', 'bar'}:
fig = line(
data_frame = df,
x = 'year',
y = 'lifeExp',
error_y = 'lifeExp std',
error_y_mode = error_y_mode, # Here you say `band` or `bar`.
color = 'country',
title = f'Using error {error_y_mode}',
markers = '.',
)
fig.show()
which produces the following two plots:
The source code of the line function that extends plotly.express.line is this:
import plotly.express as px
import plotly.graph_objs as go
def line(error_y_mode=None, **kwargs):
"""Extension of `plotly.express.line` to use error bands."""
ERROR_MODES = {'bar','band','bars','bands',None}
if error_y_mode not in ERROR_MODES:
raise ValueError(f"'error_y_mode' must be one of {ERROR_MODES}, received {repr(error_y_mode)}.")
if error_y_mode in {'bar','bars',None}:
fig = px.line(**kwargs)
elif error_y_mode in {'band','bands'}:
if 'error_y' not in kwargs:
raise ValueError(f"If you provide argument 'error_y_mode' you must also provide 'error_y'.")
figure_with_error_bars = px.line(**kwargs)
fig = px.line(**{arg: val for arg,val in kwargs.items() if arg != 'error_y'})
for data in figure_with_error_bars.data:
x = list(data['x'])
y_upper = list(data['y'] + data['error_y']['array'])
y_lower = list(data['y'] - data['error_y']['array'] if data['error_y']['arrayminus'] is None else data['y'] - data['error_y']['arrayminus'])
color = f"rgba({tuple(int(data['line']['color'].lstrip('#')[i:i+2], 16) for i in (0, 2, 4))},.3)".replace('((','(').replace('),',',').replace(' ','')
fig.add_trace(
go.Scatter(
x = x+x[::-1],
y = y_upper+y_lower[::-1],
fill = 'toself',
fillcolor = color,
line = dict(
color = 'rgba(255,255,255,0)'
),
hoverinfo = "skip",
showlegend = False,
legendgroup = data['legendgroup'],
xaxis = data['xaxis'],
yaxis = data['yaxis'],
)
)
# Reorder data as said here: https://stackoverflow.com/a/66854398/8849755
reordered_data = []
for i in range(int(len(fig.data)/2)):
reordered_data.append(fig.data[i+int(len(fig.data)/2)])
reordered_data.append(fig.data[i])
fig.data = tuple(reordered_data)
return fig
I made a line graph with the code below and I'm trying to add a horizontal line at y=1. I tried following the instructions on the plotly site but it is still not showing. Does anyone know why?
date = can_tot_df.date
growth_factor = can_tot_df.growth_factor
trace0 = go.Scatter(
x=date,
y=growth_factor,
mode = 'lines',
name = 'growth_factor'
)
fig = go.Figure()
fig.add_shape(
type='line',
x0=date.min(),
y0=1,
x1=date.max(),
y1=1,
line=dict(
color='Red',
)
)
data = [trace0]
iplot(data)
Short answer, and a general solution:
fig.add_shape(type='line',
x0=0,
y0=40,
x1=8,
y1=40,
line=dict(color='Red',),
xref='x',
yref='y'
)
Details and specifics about OP's question
It's hard to tell exactly what's wrong without a sample of your data.
What I can tell for sure is that you're missing the arguments xref and yref to specify that the line is drawn as units of your y and x axis. Judging by your sample code, this is what you'd like to do since you're specifying your x-values in terms of dates.
Also, you don't need to worry about iplot for newer versions of plotly. You can display your chart just as easily by just running fig.show(). The figure and code sample below will show you how to use fig.show() and how to define your lines in terms of axis units.
Plot:
Code:
import plotly.graph_objects as go
import numpy as np
x = np.arange(10)
fig = go.Figure(data=go.Scatter(x=x, y=x**2))
fig.add_shape(type='line',
x0=0,
y0=40,
x1=8,
y1=40,
line=dict(color='Red',),
xref='x',
yref='y'
)
fig.show()
An alternative to xref='x' is xref='paper'. Now you can specify x0 as a float between 0 and 1 spanning from the start and end of the plot.
You could also use fig.add_hline(y=1) --> see https://plotly.com/python/horizontal-vertical-shapes/
import plotly.graph_objects as go
import numpy as np
x = np.arange(10)
fig = go.Figure(data=go.Scatter(x=x, y=x**2))
fig.add_hline(y=40, line_width=3, line_dash="dash", line_color="green")
fig.show()
If you use subplots, then this is the easiest way I found to add an other line to a subplot. this example draws a horizontal line at y=80 for all x values
from plotly.subplots import make_subplots
fig = make_subplots(rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.02)
[some graph]
fig.add_trace(go.Scatter(
name='Y=80',
x = [df['date'].min(), df['date'].max()],
y = [80, 80],
mode = "lines",
marker = dict(color = 'rgba(80, 26, 80, 0.8)')
),row=1, col=1)
i found the solution on github :
df = df
fig = px.scatter(df, x="date", y="growth_factor", mode = 'lines',
hover_name=df['growth_factor'] )
fig.update_layout(shapes=[
dict(
type= 'line',
yref= 'y', y0= 1, y1= 1, # adding a horizontal line at Y = 1
xref= 'paper', x0= 0, x1= 1
)
])
fig.show()
You’re adding the line to your fig object, but fig is not getting passed into the iplot() function, only your data. So only the trace is getting plotted.
If you're using a late version of plotly, the new syntax allows you to create this plot simply using the fig object, like:
from plotly import graph_objects as go
fig = go.Figure()
# Contrived dataset for example.
x = [1, 2, 3, 4]
y = [i**2 for i in x]
fig.add_trace(go.Scatter(
x=x,
y=y,
mode = 'lines',
name = 'growth_factor'))
fig.add_shape(type='line',
x0=min(x),
y0=5,
x1=max(x),
y1=5,
line=dict(color='Red'))
fig.update_shapes(dict(xref='x', yref='y'))
fig.show()
Here are the plotly docs for convenience.
I want to create a lollipop plot with several horizontal line segments like this - https://python-graph-gallery.com/184-lollipop-plot-with-2-group. I'd like to use plotly since I prefer the graphics (and easy interactivity) but can't find a succint way.
There's both line graphs (https://plot.ly/python/line-charts/) and you can add lines in the layout (https://plot.ly/python/shapes/#vertical-and-horizontal-lines-positioned-relative-to-the-axes), but both of these solutions require each line segment to be added separately, with about 4-8 lines of code each. While I could just for-loop this, would appreciate if anyone can point me to anything with inbuilt vectorization, like the matplotlib solution (first link)!
Edit: Also tried the following code, to first make the plot ala matplotlib, then convert to plotly. The line segments disappear in the process. Starting to think it's just impossible.
mpl_fig = plt.figure()
# make matplotlib plot - WITH HLINES
plt.rcParams['figure.figsize'] = [5,5]
ax = mpl_fig.add_subplot(111)
ax.hlines(y=my_range, xmin=ordered_df['value1'], xmax=ordered_df['value2'],
color='grey', alpha=0.4)
ax.scatter(ordered_df['value1'], my_range, color='skyblue', alpha=1,
label='value1')
ax.scatter(ordered_df['value2'], my_range, color='green', alpha=0.4 ,
label='value2')
ax.legend()
# convert to plotly
plotly_fig = tls.mpl_to_plotly(mpl_fig)
plotly_fig['layout']['xaxis1']['showgrid'] = True
plotly_fig['layout']['xaxis1']['autorange'] = True
plotly_fig['layout']['yaxis1']['showgrid'] = True
plotly_fig['layout']['yaxis1']['autorange'] = True
# plot: hlines disappear :/
iplot(plotly_fig)
You can use None in the data like this:
import plotly.offline as pyo
import plotly.graph_objs as go
fig = go.Figure()
x = [1, 4, None, 2, 3, None, 3, 4]
y = [0, 0, None, 1, 1, None, 2, 2]
fig.add_trace(
go.Scatter(x=x, y=y))
pyo.plot(fig)
Plotly doesn't provide a built in vectorization for such chart, because it can be done easily by yourself, see my example based on your provided links:
import pandas as pd
import numpy as np
import plotly.offline as pyo
import plotly.graph_objs as go
# Create a dataframe
value1 = np.random.uniform(size = 20)
value2 = value1 + np.random.uniform(size = 20) / 4
df = pd.DataFrame({'group':list(map(chr, range(65, 85))), 'value1':value1 , 'value2':value2 })
my_range=range(1,len(df.index)+1)
# Add title and axis names
data1 = go.Scatter(
x=df['value1'],
y=np.array(my_range),
mode='markers',
marker=dict(color='blue')
)
data2 = go.Scatter(
x=df['value2'],
y=np.array(my_range),
mode='markers',
marker=dict(color='green')
)
# Horizontal line shape
shapes=[dict(
type='line',
x0 = df['value1'].loc[i],
y0 = i + 1,
x1 = df['value2'].loc[i],
y1 = i + 1,
line = dict(
color = 'grey',
width = 2
)
) for i in range(len(df['value1']))]
layout = go.Layout(
shapes = shapes,
title='Lollipop Chart'
)
# Plot the chart
fig = go.Figure([data1, data2], layout)
pyo.plot(fig)
With the result I got:
I'm trying to visualize the data with some outliers using Plotly and Python3. Outliers cause the color scale legend to look badly: there are only few high data points, but the legend looks bad: space between 2k and 10k is too big.
So the question is, how to change the appearance of 'color legend' on the right (see image below), so it will show the difference between 0 to 2k mostly? Unfortunately, couldn't get an answer from this doc file
Sample code (jupyter notebook):
import numpy as np
from plotly.offline import download_plotlyjs, init_notebook_mode, iplot
from plotly.graph_objs import *
init_notebook_mode()
x = np.random.randn(100,1) + 3
y = np.random.randn(100,1) + 10
x = np.reshape(x, 100)
y = np.reshape(y, 100)
color = np.random.randint(0,1000, [100])
color[[1,3,5]] = color[[1,3,5]] + 10000 # create outliers in color var
trace = Scatter(
x = x,
y = y,
mode = 'markers',
marker=dict(
color = color,
showscale=True,
colorscale = [[0, 'rgb(166,206,227, 0.5)'],
[0.05, 'rgb(31,120,180,0.5)'],
[0.1, 'rgb(178,223,138,0.5)'],
[0.15, 'rgb(51,160,44,0.5)'],
[0.2, 'rgb(251,154,153,0.5)'],
[1, 'rgb(227,26,28,0.5)']
]
)
)
fig = Figure(data=[trace])
iplot(fig)
What i'm looking for:
You can accomplish what I think you're after by customizing the colorscale, cmin, and cmax properties to have a discrete color change at exactly 2000. Then you can customize colorbar.tickvals to label the boundary as 2000. See https://plot.ly/python/reference/#scatter-marker-colorbar.
import numpy as np
from plotly.offline import download_plotlyjs, init_notebook_mode, iplot
from plotly.graph_objs import *
init_notebook_mode()
x = np.random.randn(100,1) + 3
y = np.random.randn(100,1) + 10
x = np.reshape(x, 100)
y = np.reshape(y, 100)
color = np.random.randint(0,1000, [100])
color[[1,3,5]] = color[[1,3,5]] + 10000 # create outliers in color var
bar_max = 2000
factor = 0.9 # Normalized location where continuous colorscale should end
trace = Scatter(
x = x,
y = y,
mode = 'markers',
marker=dict(
color = color,
showscale=True,
cmin=0,
cmax= bar_max/factor,
colorscale = [[0, 'rgb(166,206,227, 0.5)'],
[0.05, 'rgb(31,120,180,0.5)'],
[0.2, 'rgb(178,223,138,0.5)'],
[0.5, 'rgb(51,160,44,0.5)'],
[factor, 'rgb(251,154,153,0.5)'],
[factor, 'rgb(227,26,28,0.5)'],
[1, 'rgb(227,26,28,0.5)']
],
colorbar=dict(
tickvals = [0, 500, 1000, 1500, 2000],
ticks='outside'
)
)
)
fig = Figure(data=[trace])
iplot(fig)
New figure result
Since you asked with a precise question, I try to reply with a precise answer, even if I don't think this could not be the best in data visualization. Later I show you why.
Anyway, you can normalize the values of the colors and "squeeze" your data in a much smaller interval. It mathematically represents the power to which the number e must be raised to produce the original value. You can use log10 if you're more comfortable with.
The code is very very simple, I attach only the trace definition as the rest is unchanged. I placed a standard cmap for convenience as the interval of the values is continuous.
trace = Scatter(
x = x,
y = y,
mode = 'markers',
marker=dict(
color = np.log(color),
showscale=True,
colorscale = 'RdBu'
)
)
As I said, transforming the values with log isn't always the best. It actually forces the observer to a rough reading of the graph. As example, nevertheless in my example the orange markers range between 410 and 950, can you tell the difference?