I want to create a plot with two x axis and also two y axis. I am using twiny and twinx to do it. The secondary axis are just a rescaling of the original ones, so I'm applying a transformations to get the ticks. The problem is that I am in log scale, so the separation between the ticks does not match between the original and the twin ax. Moreover, the second x axis has other values that I don't want.
Let's follow an example to explain better:
#define the transformations I need
h=0.67
def trasf_log(y):
y_ = 10**y
return y_/h
def trasf_sigma(x):
return 1.68/x
#plot in log scale and with ticks that I choose
fig,ax = plt.subplots(1)
ax.plot(x,y0)
ax.set_ylim(1.0,2.4)
ax.set_xlim(0.6,5)
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xticks([0.6,0.8,1,2,3,4])
ax.set_yticks([1.0,1.2,1.4,1.6,1.8,2.0,2.2,2.4])
ax.xaxis.set_major_formatter(ScalarFormatter())
ax.yaxis.set_major_formatter(ScalarFormatter())
ax.ticklabel_format(axis='both', style='plain')
ax.set_xlabel(r'$\nu$', fontsize=20)
ax.set_ylabel(r'$\log_{10}Q$', fontsize=20)
ax.tick_params(labelsize=15)
#create twin axes
ax1 = ax.twinx()
ax1.set_yscale('log')
ymin,ymax=ax.get_ylim()
ax1.set_ylim((trasf_log(ymin),trasf_log(ymax)))
ax1.set_yticks(trasf_log(ax.get_yticks()))
ax1.yaxis.set_major_formatter(ScalarFormatter())
ax1.ticklabel_format(axis='y', style='plain')
ax1.tick_params(labelsize=15,labelleft=False,labelbottom=False,labeltop=False)
ax1.set_ylabel(r'$Q$', fontsize=20)
ax2 = ax.twiny()
ax2.set_xscale('log')
xmin,xmax=ax.get_xlim()
ax2.set_xlim((trasf_sigma(xmin),trasf_sigma(xmax)))
ax2.set_xticks(trasf_sigma(ax.get_xticks()))
ax2.xaxis.set_major_formatter(ScalarFormatter())
ax2.ticklabel_format(axis='x', style='plain')
ax2.tick_params(labelsize=15,labelleft=False,labelbottom=False,labelright=False)
ax2.set_xlabel(r'$\sigma $', fontsize=20)
ax.grid(True)
fig.tight_layout()
plt.show()
This is what I get:
The values of the new x and y axis are not aligned with the original ones. For example, on the two x axis the values 1 and 1.68 should be aligned. Same thing for the y axis: 1.2 and 23.7 should be aligned.
Moreover, I don't understand where the other numbers on the second x axis are coming from.
I tried already applying Scalar Formatter to each axis with 'plain' style, but nothing changes.
I also tried using secondary_axis, but I could not find a solution as well.
Anyone knows a solution?
Related
I have a plot with multiple curves, and they have the same x axis but somehow they are overlapping so the x axis is unreadable. I would like to only keep the x axis label for df.boxplot while hiding axis labels coming from df_median.plot(). Plots are done through pandas.
I tried something like this with the intention of hiding the x axis labels for a only
df.boxplot(column=['vals'], by='date', ax=axes[0], rot=45, showfliers=False, showmeans=True, whis=0)
axes[0].axhline(y=df.vals.mean(), color='r', linestyle='-')
a = df_median.plot(y='vals', ax=axes[0], label='7 day')
a.xaxis.set_visible(False)
However this just removed x axis labels all together.
I have an axis on which I plot some data and I have another twin axis which I use to draw grid lines at specific tick positions (other than the ticks of the original axis):
import matplotlib.pyplot as plt
import numpy as np
f, ax = plt.subplots()
ax.set_xlim([0, 1])
ax2 = ax.twiny()
ax2.set_xlim([0, 1])
ax2.set_xticks(np.linspace(0, 1, 11))
ax2.xaxis.grid()
x = np.linspace(0, 1, 100)
ax.plot(x, np.sin(x), label='sin(x)')
ax.legend()
plt.show()
Now this has the undesirable effect that the grid lines of the twin axes are drawn on top of the legend and line plot of the original axis. As far as I understand this is because matplotlib draws the axes in the order they were created and for that reason zorder won't help (because zorder only specifies the order among the artists of a single axis).
I know I could plot the data on the twin axis ax2 instead (followed by ax2.legend()) but I'd prefer to have the setup as is. Instead changing the order in which the two axes are drawn should solve the problem, but I couldn't figure out how to do that. There is f.get_axes() which seems to return the axes in the order they were created but no option to revert it.
Or maybe there exists even another solution?
You can change the zorder of the axes themselves.
ax.set_zorder(2)
ax2.set_zorder(1)
ax.patch.set_visible(False)
I'm trying to create a plot with two Y axes (left and right) for the same data, that is, one is a scaled version of the other. I would like also to preserve the tick positions and grid positions, so the grid will match the ticks at both sides.
I'm trying to do this by plotting twice the same data, one as-is and the other scaled, but they are not coincident.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(17, 27, 0.1)
y1 = 0.05 * x + 100
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(x, y1, 'g-')
ax2.plot(x, y1/max(y1), 'g-')
ax1.set_xlabel('X data')
ax1.set_ylabel('Y data', color='g')
ax2.set_ylabel('Y data normalized', color='b')
plt.grid()
plt.show()
Any help will be appreciated.
Not sure if you can achieve this without getting ugly-looking numbers on your normalized axis. But if that doesn't bother you, try adding this to your code:
ax2.set_ylim([ax1.get_ylim()[0]/max(y1),ax1.get_ylim()[1]/max(y1)])
ax2.set_yticks(ax1.get_yticks()/max(y1))
Probably not the most elegant solution, but it scales your axis limits and tick positions similarly to what you do with the data itself so the grid matches both axes.
In my plot, a secondary x axis is used to display the value of another variable for some data. Now, the original axis is log scaled. Unfortunaltely, the twinned axis puts the ticks (and the labels) referring to the linear scale of the original axis and not as intended to the log scale. How can this be overcome?
Here the code example that should put the ticks of the twinned axis in the same (absolute axes) position as the ones for the original axis:
def conv(x):
"""some conversion function"""
# ...
return x2
ax = plt.subplot(1,1,1)
ax.set_xscale('log')
# get the location of the ticks of ax
axlocs,axlabels = plt.xticks()
# twin axis and set limits as in ax
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
#Set the ticks, should be set referring to the log scale of ax, but are set referring to the linear scale
ax2.set_xticks(axlocs)
# put the converted labels
ax2.set_xticklabels(map(conv,axlocs))
An alternative way would be (the ticks are then not set in the same position, but that doesn't matter):
from matplotlib.ticker import FuncFormatter
ax = plt.subplot(1,1,1)
ax.set_xscale('log')
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
ax2.xaxis.set_major_formatter(FuncFormatter(lambda x,pos:conv(x)))
Both approaches work well as long as no log scale is used.
Perhaps there exists an easy fix. Is there something I missed in the documentation?
As a workaround, I tried to obtain the ax.transAxes coordinates of the ticks of ax and put the ticks at the very same position in ax2. But there does not exist something like
ax2.set_xticks(axlocs,transform=ax2.transAxes)
TypeError: set_xticks() got an unexpected keyword argument 'transform'
This has been asked a while ago, but I stumbled over it with the same question.
I eventually managed to solve the problem by introducing a logscaled (semilogx) transparent (alpha=0) dummy plot.
Example:
import numpy as np
import matplotlib.pyplot as plt
def conversion_func(x): # some arbitrary transformation function
return 2 * x**0.5 # from x to z
x = np.logspace(0, 5, 100)
y = np.sin(np.log(x))
fig = plt.figure()
ax = plt.gca()
ax.semilogx(x, y, 'k')
ax.set_xlim(x[0], x[-1]) # this is important in order that limits of both axes match
ax.set_ylabel("$y$")
ax.set_xlabel("$x$", color='C0')
ax.tick_params(axis='x', which='both', colors='C0')
ax.axvline(100, c='C0', lw=3)
ticks_x = np.logspace(0, 5, 5 + 1) # must span limits of first axis with clever spacing
ticks_z = conversion_func(ticks_x)
ax2 = ax.twiny() # get the twin axis
ax2.semilogx(ticks_z, np.ones_like(ticks_z), alpha=0) # transparent dummy plot
ax2.set_xlim(ticks_z[0], ticks_z[-1])
ax2.set_xlabel("$z \equiv f(x)$", color='C1')
ax2.xaxis.label.set_color('C1')
ax2.tick_params(axis='x', which='both', colors='C1')
ax2.axvline(20, ls='--', c='C1', lw=3) # z=20 indeed matches x=100 as desired
fig.show()
In the above example the vertical lines demonstrate that first and second axis are indeed shifted to one another as wanted. x = 100 gets shifted to z = 2*x**0.5 = 20. The colours are just to clarify which vertical line goes with which axis.
Don't need to cover them, just Eliminate the ticks!
d= [7,9,14,17,35,70];
j= [100,80,50,40,20,10];
plt.figure()
plt.xscale('log')
plt.plot(freq, freq*spec) #plot some spectrum
ax1 = plt.gca() #define my first axis
ax1.yaxis.set_ticks_position('both')
ax1.tick_params(axis='y',which='both',direction='in');
ax1.tick_params(axis='x',which='both',direction='in');
ax2 = ax1.twiny() #generates second axis (top)
ax2.set_xlim(ax1.get_xlim()); #same limits
plt.xscale('log') #make it log
ax2.set_xticks(freq[d]); #my own 'major' ticks OVERLAPS!!!
ax2.set_xticklabels(j); #change labels
ax2.tick_params(axis='x',which='major',direction='in');
ax2.tick_params(axis='x',which='minor',top=False); #REMOVE 'MINOR' TICKS
ax2.grid()
I think you can fix your issue by calling ax2.set_xscale('log').
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.semilogx(np.logspace(1.0, 5.0, 20), np.random.random([20]))
new_tick_locations = np.array([10., 100., 1000., 1.0e4])
def tick_function(X):
V = X / 1000.
return ["%.3f" % z for z in V]
ax2 = ax.twiny()
ax2.set_xscale('log')
ax2.set_xlim(ax.get_xlim())
ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.set_xlabel(r"Modified x-axis: $X/1000$")
I want to draw a plot with matplotlib with axis on both sides of the plot, similar to this plot (the color is irrelevant to this question):
How can I do this with matplotlib?
Note: contrary to what is shown in the example graph, I want the two axis to be exactly the same, and want to show only one graph. Adding the two axis is only to make reading the graph easier.
You can use tick_params() (this I did in Jupyter notebook):
import matplotlib.pyplot as plt
bar(range(10), range(10))
tick_params(labeltop=True, labelright=True)
Generates this image:
UPD: added a simple example for subplots. You should use tick_params() with axis object.
This code sets to display only top labels for the top subplot and bottom labels for the bottom subplot (with corresponding ticks):
import matplotlib.pyplot as plt
f, axarr = plt.subplots(2)
axarr[0].bar(range(10), range(10))
axarr[0].tick_params(labelbottom=False, labeltop=True, labelleft=False, labelright=False,
bottom=False, top=True, left=False, right=False)
axarr[1].bar(range(10), range(10, 0, -1))
axarr[1].tick_params(labelbottom=True, labeltop=False, labelleft=False, labelright=False,
bottom=True, top=False, left=False, right=False)
Looks like this:
There are a couple of relevant examples in the online documentation:
Two Scales (seems to do exactly what you're asking for)
Dual Fahrenheit and Celsius
I've done this previously using the following:
# Create figure and initial axis
fig, ax0 = plt.subplots()
# Create a duplicate of the original xaxis, giving you an additional axis object
ax1 = ax.twinx()
# Set the limits of the new axis from the original axis limits
ax1.set_ylim(ax0.get_ylim())
This will exactly duplicate the original y-axis.
Eg:
ax = plt.gca()
plt.bar(range(3), range(1, 4))
plt.axhline(1.75, color="gray", ls=":")
twin_ax = ax.twinx()
twin_ax.set_yticks([1.75])
twin_ax.set_ylim(ax.get_ylim())