Plotly seems to place the x and y ticks on the periphery of a graph by default however I have a graph which its y zero-line is in the middle. I'd like for the tick to be right beside the y axis instead but I fail to find a command to make that happen. Is it at all possible?
To my knowledge there is no direct way to do this at the moment. But if you use the correct combination of xaxis zeroline and annotations for the y-values, you can get this:
The code snippet below takes the max and min for the y-values, builds a list of that interval with a defined number of steps, and inserts annotations for those steps. It's not very elegant, it fails a bit when you try to zoom, but it works. At least now you don't have to try a similar approach yourself.
Complete code:
import plotly.graph_objects as go
import numpy as np
x = np.arange(-4,5)
y=x**3
yticks=list(range(y.min(), y.max(), 14))
#yticks.append(y.max())9
fig = go.Figure(data=go.Scatter(x=x, y=y))
fig.update_layout(xaxis = dict(zeroline=True,
zerolinecolor='firebrick',
title_text = "x zeroline",
title_standoff = 4))
fig.update_layout(yaxis = dict(zeroline=True,
showgrid=False,
title_font = {"size": 20},
tickfont= dict(color='rgba(0,0,0,0)')))
for i, m in enumerate(yticks):
fig.add_annotation(dict(font=dict(color='firebrick',size=12),
x=0.066,
y=yticks[i],
showarrow=False,
text=str(yticks[i])+' -',
textangle=0,
xanchor='right',
xref="x",
yref="y"))
fig.show()
Related
Here is CDF visualization I have:
fig_cdf = px.ecdf(df['Timespan'], color_discrete_sequence=['blue'],ecdfnorm='probability', orientation='h')
fig_cdf.add_hline(y=90, line_width=2, line_color="red", name='90%', visible=True)
fig_cdf.add_hline(y=30, line_width=2, line_color="red", name='75%', visible=True)
fig_cdf.update_layout(width=500, height=500)
The problem here is that i want horizontal lines' names to be visible and appear as 2nd and 3rd legends. For this, I tried to add visible=True. However, it seems not to work. What's wrong?
This is one way of doing it...
Add the two lines to the dataframe as new columns
Use color_discrete_sequence to identify the colors you want
I am using some random dummy data, which you can replace with your data
import plotly.express as px
df = pd.DataFrame({'firstline': random.sample(range(1, 500), 20),'myX' : range(20)}) #My dummy data
#Add the two lines to dataframe
df['90%'] = [90] * 20
df['75%'] = [75] * 20
fig = px.line(df,
y = ['firstline', '90%', '75%'], x= 'myX', color_discrete_sequence=["blue", "red", "red"])
fig.update_layout(legend_title_text='Legend Heading') #Update Legend header if you dont like 'variable'
fig.show()
Output graph
This is my first experience with this graph, but to add it to the legend, you can use the line mode of the scatter plot. So I took the maximum x-axis value used in the first graph and set the legend name Average using the appropriate y-axis value. This example is taken from the official reference.
import plotly.express as px
import plotly.graph_objects as go
df = px.data.tips()
fig = px.ecdf(df, x=["total_bill", "tip"])
xmax = max(fig.data[0]['x'])
#print(xmax)
fig.add_trace(go.Scatter(
x=[0,xmax],
y=[0.6,0.6],
mode='lines',
line_color='red',
name='mean',
showlegend=True
))
fig.show()
I have a vertical line plot of Lithology data (x=Lithology, y=Depth) and a dictionary with colors and patterns. I need to fill my line plot using plotly and patterns:colors from my dictionary, like one in this picture on the right side. In matplotlib this achieved with ax.fill_betweenx().
ax.fill_betweenx(well['DEPTH_MD'], 0, well['LITHOLOGY'],
where=(well['LITHOLOGY']==key),
facecolor=color, hatch=hatch)
How can it be done in plotly?
Well Logs
A workaround:
import numpy as np
import plotly.graph_objects as go
depth = list(range(2900, 3100, 5))
density1 = np.random.random_sample((len(depth), )) * 2
density2 = np.random.random_sample((len(depth), )) * 2 + 1
fig = go.Figure()
fig.add_trace(go.Scatter(
x=density1,
y=depth,
mode='lines',
name='left'
))
fig.add_trace(go.Scatter(
x=density2,
y=depth,
mode='lines',
name='right'
))
fig.for_each_trace(
lambda trace: trace.update(fill='tonextx') if trace.name == "right" else (),
)
fig.show()
My solution requires you to give a name to each trace (ie. lines). Only after the two traces are updated, do fig.for_each_trace() to update either one of the traces with argument fill='tonextx'.
I am drawing a correlation matrix of the Titanic dataset.
df_corr = df.corr()
Originally, the matrix looks like this:
fig = ff.create_annotated_heatmap(
z=df_corr.to_numpy(),
x=df_corr.columns.tolist(),
y=df_corr.index.tolist(),
zmax=1, zmin=-1,
showscale=True,
hoverongaps=True
)
# add title
fig.update_layout(title_text='<i><b>Correlation not round</b></i>')
I want to round the float number, so they display less digits after the . dot.
The current workaround is actually round the pandas dataframe before input.
df_corr_round = df_corr.round(3)
fig = ff.create_annotated_heatmap(
z=df_corr_round.to_numpy(),
x=df_corr.columns.tolist(),
y=df_corr.index.tolist(),
zmax=1, zmin=-1,
showscale=True,
hoverongaps=True
)
# add title
fig.update_layout(title_text='<i><b>Correlation round</b></i>')
But the workaround also rounds the text when I hover mouse over. I want hover text in full detail while display text are round.
Can I display less digits on each cell without changing the input dataframe ?
I can only assume that you're building your ff.create_annotated_heatmap() from a list of lists as they do in the docs under Annotated Heatmaps in Python. And don't worry if you're using a pandas dataframe instead. The complete snippet below will show you how you construct a correlation matrix from a pandas dataframe with multiple timeseries of stocks px.data.stocks, and then make a list of lists using df.values.tolist() to build an annotated heatmap. If you're doing something similar, then one way of building the annotations would be to define a text like this:
z_text = [[str(y) for y in x] for x in z]
And then all you'll need to get the number of digits you want is use round():
z_text = [[str(round(y, 1)) for y in x] for x in z]
As you can see below, this approach (1) does not alter the source dataframe like df_corr.round() would have, (2) shows only 1 digit in the figure, and (3) shows a longer number format on hover. In the image I'm hovering on MSFT / FB = 0.5
Complete code:
import plotly.express as px
import plotly.figure_factory as ff
import pandas as pd
df = px.data.stocks()#.tail(50)
df = df.drop(['date'], axis = 1)
dfc = df.corr()
z = dfc.values.tolist()
# change each element of z to type string for annotations
# z_text = [[str(y) for y in x] for x in z]
z_text = [[str(round(y, 1)) for y in x] for x in z]
# set up figure
fig = ff.create_annotated_heatmap(z, x=list(df.columns),
y=list(df.columns),
annotation_text=z_text, colorscale='agsunset')
# add title
fig.update_layout(title_text='<i><b>Confusion matrix</b></i>',
#xaxis = dict(title='x'),
#yaxis = dict(title='x')
)
# add custom xaxis title
fig.add_annotation(dict(font=dict(color="black",size=14),
x=0.5,
y=-0.15,
showarrow=False,
text="",
xref="paper",
yref="paper"))
# add custom yaxis title
fig.add_annotation(dict(font=dict(color="black",size=14),
x=-0.35,
y=0.5,
showarrow=False,
text="",
textangle=-90,
xref="paper",
yref="paper"))
# adjust margins to make room for yaxis title
fig.update_layout(margin=dict(t=50, l=200))
# add colorbar
fig['data'][0]['showscale'] = True
fig.show()
I don't have the data at hand, so I haven't been able to check the execution, but I think the following code will work. Please refer to the official reference.
df_corr_round = df_corr.round(3)
fig = ff.create_annotated_heatmap(
z=df_corr,
x=df_corr.columns.tolist(),
y=df_corr.index.tolist(),
zmax=1, zmin=-1,
showscale=True,
hoverongaps=True,
annotation_text=df_corr_round.to_numpy(),
)
# add title
fig.update_layout(title_text='<i><b>Correlation round</b></i>')
I use plotly package to show dynamic finance chart at python. However I didn't manage to put my all key points lines on one chart with for loop. Here is my code:
fig.update_layout(
for i in range(0,len(data)):
shapes=[
go.layout.Shape(
type="rect",
x0=data['Date'][i],
y0=data['Max_alt'][i],
x1='2019-12-31',
y1=data['Max_ust'][i],
fillcolor="LightSkyBlue",
opacity=0.5,
layer="below",
line_width=0)])
fig.show()
I have a data like below one. It is time series based EURUSD parity financial dataset. I calculated two constraits for both Local Min and Max. I wanted to draw rectangule shape to based on for each Min_alt / Min_ust and Max_alt / Max_range. I can draw for just one date like below image however I didn't manage to show all ranges in same plotly graph.
Here is the sample data set.
Here is the solution for added lines:
import datetime
colors = ["LightSkyBlue", "RoyalBlue", "forestgreen", "lightseagreen"]
ply_shapes = {}
for i in range(0, len(data1)):
ply_shapes['shape_' + str(i)]=go.layout.Shape(type="rect",
x0=data1['Date'][i].strftime('%Y-%m-%d'),
y0=data1['Max_alt'][i],
x1='2019-12-31',
y1=data1['Max_ust'][i],
fillcolor="LightSkyBlue",
opacity=0.5,
layer="below"
)
lst_shapes=list(ply_shapes.values())
fig1.update_layout(shapes=lst_shapes)
fig1.show()
However I have still problems to add traces to those lines. I mean text attribute.
Here is my code:
add_trace = {}
for i in range(0, len(data1)):
add_trace['scatter_' + str(i)] = go.Scatter(
x=['2019-12-31'],
y=[data1['Max_ust'][i]],
text=[str(data['Max_Label'][i])],
mode="text")
lst_trace = list(add_trace.values())
fig2=go.Figure(lst_trace)
fig2.show()
The answer:
For full control of each and every shape you insert, you could follow this logic:
fig = go.Figure()
#[...] data, traces and such
ply_shapes = {}
for i in range(1, len(df)):
ply_shapes['shape_' + str(i)]=go.layout.Shape()
lst_shapes=list(ply_shapes.values())
fig.update_layout(shapes=lst_shapes)
fig.show()
The details:
I'm not 100% sure what you're aimin to do here, but the following suggestion will answer your question quite literally regarding:
How to add more than one shape with loop in plotly?
Then you'll have to figure out the details regarding:
manage to put my all key points lines on one chart
Plot:
The plot itself is most likely not what you're looking for, but since you for some reason are adding a plot by the length of your data for i in range(0,len(data), I've made this:
Code:
This snippet will show how to handle all desired traces and shapes with for loops:
# Imports
import pandas as pd
#import matplotlib.pyplot as plt
import numpy as np
import plotly.graph_objects as go
#from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
# data, random sample to illustrate stocks
np.random.seed(12345)
rows = 20
x = pd.Series(np.random.randn(rows),index=pd.date_range('1/1/2020', periods=rows)).cumsum()
y = pd.Series(x-np.random.randn(rows)*5,index=pd.date_range('1/1/2020', periods=rows))
df = pd.concat([y,x], axis = 1)
df.columns = ['StockA', 'StockB']
# lines
df['keyPoints1']=np.random.randint(-5,5,len(df))
df['keyPoints2']=df['keyPoints1']*-1
# plotly traces
fig = go.Figure()
stocks = ['StockA', 'StockB']
df[stocks].tail()
traces = {}
for i in range(0, len(stocks)):
traces['trace_' + str(i)]=go.Scatter(x=df.index,
y=df[stocks[i]].values,
name=stocks[i])
data=list(traces.values())
fig=go.Figure(data)
# shapes update
colors = ["LightSkyBlue", "RoyalBlue", "forestgreen", "lightseagreen"]
ply_shapes = {}
for i in range(1, len(df)):
ply_shapes['shape_' + str(i)]=go.layout.Shape(type="line",
x0=df.index[i-1],
y0=df['keyPoints1'].iloc[i-1],
x1=df.index[i],
y1=df['keyPoints2'].iloc[i-1],
line=dict(
color=np.random.choice(colors,1)[0],
width=30),
opacity=0.5,
layer="below"
)
lst_shapes=list(ply_shapes.values())
fig.update_layout(shapes=lst_shapes)
fig.show()
Also you can use fig.add_{shape}:
fig = go.Figure()
fig.add_trace(
go.Scatter( ...)
for i in range( 1, len( vrect)):
fig.add_vrect(
x0=vrect.start.iloc[ i-1],
x1=vrect.finish.iloc[ i-1],
fillcolor=vrect.color.iloc[ i-1]],
opacity=0.25,
line_width=0)
fig.show()
Question : Using the python library 'plotnine', can we draw an interactive 3D surface plot?
Backup Explanations
What I'd like to do is, under python environment, creating an interactive 3D plot with R plot grammars like we do with ggplot2 library in R. It's because I have hard time remembering grammars of matplotlib and other libraries like seaborn.
An interactive 3D plot means a 3D plot that you can zoom in, zoom out, and scroll up and down, etc.
It seems like only Java supported plotting libraries scuh as bokeh or plotly can create interactive 3D plots. But I want to create it with the library 'plotnine' because the library supports ggplot-like grammar, which is easy to remember.
For example, can I draw a 3D surface plot like the one below with the library 'plotnine'?
import plotly.plotly as py
import plotly.graph_objs as go
import pandas as pd
# Read data from a csv
z_data =
pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/
master/api_docs/mt_bruno_elevation.csv')
data = [
go.Surface(
z=z_data.as_matrix()
)]
layout = go.Layout(
title='Mt Bruno Elevation',
autosize=False,
width=500,
height=500,
margin=dict(
l=65,
r=50,
b=65,
t=90
)
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig, filename='elevations-3d-surface')
The codes above make a figure like below.
You can check out the complete interactive 3D surface plot in this link
p.s. If i can draw an interactive 3D plot with ggplot-like grammar, it does not have to be the 'plotnine' library that we should use.
Thank you for your time for reading this question!
It is possible, if you are willing to expand plotnine a bit, and caveats apply. The final code is as simple as:
(
ggplot_3d(mt_bruno_long)
+ aes(x='x', y='y', z='height')
+ geom_polygon_3d(size=0.01)
+ theme_minimal()
)
And the result:
First, you need to transform your data into long format:
z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv', index_col=0)
z = z_data.values
nrows, ncols = z.shape
x, y = np.linspace(0, 1, nrows), np.linspace(0, 1, ncols)
x, y = np.meshgrid(x, y)
mt_bruno_long = pd.DataFrame({'x': x.flatten(), 'y': y.flatten(), 'height': z.flatten()})
Then, we need to create equivalents for ggplot and geom_polygon with awareness of the third dimension.
Since writing this answer the code is is now available in plotnine3d package, so you could just:
from plotnine3d import ggplot_3d, geom_polygon_3d
But for completeness, this is how (relatively) simple it is:
from plotnine import ggplot, geom_polygon
from plotnine.utils import to_rgba, SIZE_FACTOR
class ggplot_3d(ggplot):
def _create_figure(self):
figure = plt.figure()
axs = [plt.axes(projection='3d')]
figure._themeable = {}
self.figure = figure
self.axs = axs
return figure, axs
def _draw_labels(self):
ax = self.axs[0]
ax.set_xlabel(self.layout.xlabel(self.labels))
ax.set_ylabel(self.layout.ylabel(self.labels))
ax.set_zlabel(self.labels['z'])
class geom_polygon_3d(geom_polygon):
REQUIRED_AES = {'x', 'y', 'z'}
#staticmethod
def draw_group(data, panel_params, coord, ax, **params):
data = coord.transform(data, panel_params, munch=True)
data['size'] *= SIZE_FACTOR
grouper = data.groupby('group', sort=False)
for i, (group, df) in enumerate(grouper):
fill = to_rgba(df['fill'], df['alpha'])
polyc = ax.plot_trisurf(
df['x'].values,
df['y'].values,
df['z'].values,
facecolors=fill if any(fill) else 'none',
edgecolors=df['color'] if any(df['color']) else 'none',
linestyles=df['linetype'],
linewidths=df['size'],
zorder=params['zorder'],
rasterized=params['raster'],
)
# workaround for https://github.com/matplotlib/matplotlib/issues/9535
if len(set(fill)) == 1:
polyc.set_facecolors(fill[0])
For interactivity you can use any matplotlib backend of your liking, I went with ipympl (pip install ipympl and then %matplotlib widget in a jupyter notebook cell).
The caveats are:
while shading works nice, plot_trisurf does not handle facecolors well (there is a PR to fix it here)
you may want to add a parameter allowing to disable shading, see matplotlib 3D shading examples
faceting, flipping axes etc will not work without further fiddling - this could however be addressed in the future as discussed in this plotnine issue about bringing 3D plots to plotnine.
Edit: In case if the dataset becomes unavailable, here is a self-contained example based on matplotlib's documentation:
import numpy as np
n_radii = 8
n_angles = 36
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)[..., np.newaxis]
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
z = np.sin(-x*y)
df = pd.DataFrame(dict(x=x,y=y,z=z))
(
ggplot_3d(df)
+ aes(x='x', y='y', z='z')
+ geom_polygon_3d(size=0.01)
+ theme_minimal()
)