I want to add a second y-axis into a holoviews figure with bokeh backend.
In bokeh, the parameter "extra-y-axis" achieves that.
After searching the holoviews API I did not find any direct command/parameter for that, so - with some hv github research - I tried it with hooks.
But unfortunately I am still struggling to define a finalize/initialize hook to do that.
What I tried (code from holoviews' github):
def twinx(plot, element):
# Setting the second y axis range name and range
start, end = (element.range(1))
label = element.dimensions()[1].pprint_label
plot.state.extra_y_ranges = {"foo": Range1d(start=start, end=end)}
# Adding the second axis to the plot.
linaxis = LinearAxis(axis_label=label, y_range_name='foo')
plot.state.add_layout(linaxis, 'left')
curve_1 = hv.Scatter(data1)
curve_2 = hv.Scatter(data2).opts(plot=dict(finalize_hooks=[twinx]), style=dict(color='red'))
curve_1*curve_2
The result does create a second y-axis, but curve_2 is still plotted against the first y-axis.
How can I solve this? Thank you!
Related
I have the following plot done in Plotly
As you can see the X,Y axis are in the traditional way.
How can I rotate the axis so that X is plot vertically and Y horizontally (to the left)?
(also I would like to modify the reach and separations of each axis)
I suppose it has to do something with the layout element.
My code if so far
layout= go.Layout(title=go.layout.Title(text=title,x=0.5),
xaxis={'title':'x[m]'},
yaxis={'title':'y[m]'})
point_plot=[
go.Scatter(x=[series[listoflabels[0]]],y=[series[listoflabels[1]]],name="V0"),
go.Scatter(x=[series[listoflabels[2]]],y=[series[listoflabels[3]]],name="GT"),
go.Scatter(x=[0],y=[0],name="egoCar")
]
return go.Figure(data=point_plot, layout=layout)
EDIT:
In order to make it reproducible I modified the code to
layout1= go.Layout(title=go.layout.Title(text="A graph",x=0.5),
xaxis={'title':'x[m]'},
yaxis={'title':'y[m]'})
point_plot=[
go.Scatter(x=[3],y=[1],name="V0"),
go.Scatter(x=[5],y=[2],name="GT"),
go.Scatter(x=[0],y=[0],name="egoCar")
]
go.Figure(data=point_plot, layout=layout1).show()
and the plot is
I am not aware that Plotly has any built-in method to switch x- and y-axes. However, you can achieve this yourself but switching the titles of the x- and y-axes, then switching the parameters x and y that correspond to your points.
Then you can place the y-axis (containing your x-coordinates) on the right side in your layout, and reverse the range of the x-axis (containing your y-coordinates) so that the origin is in the lower-right side.
import plotly.graph_objects as go
# switch the axes titles
layout1= go.Layout(title=go.layout.Title(text="A graph",x=0.5),
xaxis={'title':'y[m]'},
yaxis={'title':'x[m]', 'side':'right'})
# switch the x- and y-coordinates
point_plot=[
go.Scatter(y=[3],x=[1],name="V0"),
go.Scatter(y=[5],x=[2],name="GT"),
go.Scatter(y=[0],x=[0],name="egoCar")
]
fig = go.Figure(data=point_plot, layout=layout1)
# reverse the range of the xaxis (which contains the y values)
fig.update_xaxes(autorange="reversed")
fig.show()
Derek O hit on what is the easiest to implement. It's the graph analogy of 1km is 0.6 miles or a, b = b, a
A second option is to abstract the data and your axes. That's essentially saying the same thing but can be easier to read as Plotly has a unique ability to turn clear, well-structured Python into ugly JS without parenthesis pretty quickly.
Option three is to further abstract this and have it set your axes for you.
Edited:
I am plotting stacked bars from my pandas DataFrame. Code generally works, but when I put the same code in a user defined function and try to plot the bars between 2 specific dates, it returns an error. Here are the codes:
import pandas as pd
import matplotlib.pyplot as plt
time_table = pd.DataFrame(data =[['2019-09-03',-1.089987,5.085],
['2019-09-04',-5.982087,-2.7],
['2019-09-05',-2.887029,57.46659545],
['2019-09-06',-5.634726,-47.45]] , columns=['Exec_date','Trade_cost', 'Closed_PnL'])
time_table['Exec_date'] = pd.to_datetime(time_table['Exec_date'] )
def between_dates(df_time , start = pd.to_datetime('09/3/2019'), finish = pd.to_datetime('09/6/2019')):
mask = (df_time['Exec_date'] >= start ) &(df_time['Exec_date'] <= finish)
df_time = df_time.loc[mask].copy()
plot_it(df_time)
return df_time
def plot_it(time_table):
fig1=plt.figure()
axes1 = fig1.add_axes([0.1,0.1,1,1])
# Plots
axes1.bar(time_table['Exec_date'], time_table['Closed_PnL'], color='0.7')
axes1.bar(time_table['Exec_date'], time_table['Trade_cost'],bottom = time_table['Closed_PnL'],color='r')
plt.show();
between_dates(time_table)
the above code works. but if I change the dates in between_dates function to anything which doesn't cover all of my data, say change the start to 09/04/2019 it returns this error: only size-1 arrays can be converted to Python scalars,which, I guess, has probably something to do with functions not being able to send multiple values for bottom argument. If I take out the bottom or assign a single value to it, my function works without any errors, but the plot is not a stacked bar plot anymore. To Solve the problem, I am using a plotting loop inside my function:
def plot_it(time_table):
fig1=plt.figure()
axes1 = fig1.add_axes([0.1,0.1,1,1])
# Plots
axes1.bar(time_table['Exec_date'], time_table['Closed_PnL'], color='0.7',edgecolor = 'k')
for i in time_table.index:
axes1.bar(time_table.loc[i,'Exec_date'], time_table.loc[i,'Trade_cost'],bottom = time_table.loc[i,'Closed_PnL'],
color='r')
plt.show();
It works! But doesn't look that great. I am wondering if there is a more elegant way of writing this code. Something that doesn't require going through a for loop and plotting bars one by one. I read a little about itertools.starmap, but can't get it to work in this case yet.
Here is a link to the code on Github for testing.
So, is there any better way of plotting these stacked bars? Appreciate your help and comments!
Using matplotlib 2.2.2 with gridspec in Python 3.6.5, I created a huge plot for a research paper with several subplots. The axes objects are stored in a dictionary called axes. This dictionary is passed to the function adjust_xticklabels(), which is supposed to align the first xticklabel slightly to the right and the last xticklabel slightly to the left in each subplot, such that the xticklabels of neighbouring plots dont get in the way of each other. The function is defined as:
def adjust_xticklabels(axes, rate = 0.1):
for ax in axes.values():
left, right = ax.get_xlim() # get boundaries
dist = right-left # get distance
xtl = ax.get_xticklabels()
if len(xtl) > 1:
xtl[0].set_position((left + rate*dist, 0.)) # (x, y), shift right
xtl[-1].set_position((right - rate*dist, 0.)) # shift left
Calling it has no effect. Of course I also tried it with ridiculously high values. However, is has an effect in y-direction, for instance in case of setting xtl[0].set_position((0.3, 0.3)).
A simple reproduction:
ax = plt.subplot(111)
ax.plot(np.arange(10))
xtl = ax.get_xticklabels()
xtl[4].set_position((0.3, 0.3)) # wlog, 4 corresponds to 6
I spent quite a while on trying to figure out if this is a feature or a bug. Did I miss something or is this a bug? Is there any other way to do the same thing?
This is a feature, no bug. The ticklabels are positionned at drawtime to sit at the correct locations according to the ticker in use. This ensures that the label always sits where the corresponding tick is located. If you change the limits, move or zoom the plot, the label always follows those changes.
You are usually not meant to change this location, but you may, by adding a custom transform to it. This is described in
Moving matplotlib xticklabels by pixel value. The general idea is to set a translating transformation on the label. E.g. to translate the second label by 20 pixels to the right,
import matplotlib.transforms as mtrans
# ...
trans = mtrans.Affine2D().translate(20, 0)
label = ax.get_xticklabels()[1]
label.set_transform(label.get_transform()+trans)
Let's look at a swarmplot, made with Python 3.5 and Seaborn on some data (which is stored in a pandas dataframe df with column lables stored in another class. This does not matter for now, just look at the plot):
ax = sns.swarmplot(x=self.dte.label_temperature, y=self.dte.label_current, hue=self.dte.label_voltage, data = df)
Now the data is more readable if plotted in log scale on the y-axis because it goes over some decades.
So let's change the scaling to logarithmic:
ax.set_yscale("log")
ax.set_ylim(bottom = 5*10**-10)
Well I have a problem with the gaps in the swarms. I guess they are there because they have been there when the plot is created with a linear axis in mind and the dots should not overlap there. But now they look kind of strange and there is enough space to from 4 equal looking swarms.
My question is: How can I force seaborn to recalculate the position of the dots to create better looking swarms?
mwaskom hinted to me in the comments how to solve this.
It is even stated in the swamplot doku:
Note that arranging the points properly requires an accurate transformation between data and point coordinates. This means that non-default axis limits should be set before drawing the swarm plot.
Setting an existing axis to log-scale and use this for the plot:
fig = plt.figure() # create figure
rect = 0,0,1,1 # create an rectangle for the new axis
log_ax = fig.add_axes(rect) # create a new axis (or use an existing one)
log_ax.set_yscale("log") # log first
sns.swarmplot(x=self.dte.label_temperature, y=self.dte.label_current, hue=self.dte.label_voltage, data = df, ax = log_ax)
This yields in the correct and desired plotting behaviour:
I'm running the code from the top answer to this question in an IPython/Jupyter notebook. The first time I run it, it displays properly:
If I change any parameter, no matter how inconsequential (e.g. a single quote to a double quote), when I run the cell again, or when I run the same code in another cell, the following appears:
It looks like it's recursively putting the desired 2x1 grid of subplots inside a new 2x1 grid of subplots. I tried adding bk_plotting.reset_output(), but it had no effect.
Here's the cell code:
import numpy as np
import bokeh.plotting as bk_plotting
import bokeh.models as bk_models
# for the ipython notebook
bk_plotting.output_notebook()
# a random dataset
data = bk_models.ColumnDataSource(data=dict(x=np.arange(10),
y1=np.random.randn(10),
y2=np.random.randn(10)))
# defining the range (I tried with start and end instead of sources and couldn't make it work)
x_range = bk_models.DataRange1d(sources=[data.columns('x')])
y_range = bk_models.DataRange1d(sources=[data.columns('y1', 'y2')])
# create the first plot, and add a the line plot of the column y1
p1 = bk_models.Plot(x_range=x_range,
y_range=y_range,
title="",
min_border=2,
plot_width=250,
plot_height=250)
p1.add_glyph(data,
bk_models.glyphs.Line(x='x',
y='y1',
line_color='black',
line_width=2))
# add the axes
xaxis = bk_models.LinearAxis()
p1.add_layout(xaxis, 'below')
yaxis = bk_models.LinearAxis()
p1.add_layout(yaxis, 'left')
# add the grid
p1.add_layout(bk_models.Grid(dimension=1, ticker=xaxis.ticker))
p1.add_layout(bk_models.Grid(dimension=0, ticker=yaxis.ticker))
# add the tools
p1.add_tools(bk_models.PreviewSaveTool())
# create the second plot, and add a the line plot of the column y2
p2 = bk_models.Plot(x_range=x_range,
y_range=y_range,
title="",
min_border=2,
plot_width=250,
plot_height=250)
p2.add_glyph(data,
bk_models.glyphs.Line(x='x',
y='y2',
line_color='black',
line_width=2))
# add the x axis
xaxis = bk_models.LinearAxis()
p2.add_layout(xaxis, 'below')
# add the grid
p2.add_layout(bk_models.Grid(dimension=1, ticker=xaxis.ticker))
p2.add_layout(bk_models.Grid(dimension=0, ticker=yaxis.ticker))
# add the tools again (it's only displayed if added to each chart)
p2.add_tools(bk_models.PreviewSaveTool())
# display both
gp = bk_plotting.GridPlot(children=[[p1, p2]])
bk_plotting.show(gp)
It is important to specify versions in reports such as this. This bug was noted in this GitHub issue (detailed comment), with a fix recorded as being in put in place in this Pull Request before the 0.8.2 release. I cannot reproduce the problem in a clean Bokeh 0.8.2 conda environent (python3, jupiter/ipython 3.10, OSX+safari) with the code above. If you can still reproduce this problem with Bokeh >= 0.8.2, please file a bug report on Bokeh issue tracker with as much information above versions, platform, etc. as possible.