Configure a second x-axis with matplotlib in logscale - python

I am trying to do a plot with a second x-axis on matplotlib but with a logarithmic scale.
I managed to add a second x-axis on a plot with a linear scale using the example below:
import numpy as np
import matplotlib.pyplot as plt
x_axis = np.linspace(0,100, 100)
yvalues = x_axis**3
new_tick_locations = np.array([20,40,60,80,100])
second_x_axis = new_tick_locations**2
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(x_axis, yvalues)
ax1.axvline(x = 60, color = 'black', linestyle = 'dotted')
ax1.set_xlabel("first x axis")
ax1.set_ylabel("yvalues")
SecondAxis = True
if(SecondAxis):
ax2 = ax1.twiny()
ax2.set_xlim(ax1.get_xlim())
ax2.set_xticks(new_tick_locations )
ax2.set_xticklabels(second_x_axis)
ax2.set_xlabel("second x axis")
Logscale = False
if(Logscale):
ax1.set_xscale("log")
ax1.set_yscale("log")
plt.show()
Here, my second x-axis is defined as the square of my x-axis and we retrieve on the plot that for x =60 we have second_x =3600.
However, if I now set the "Logscale" variable in my code to "True" I get the following result:
Where the values of my second x-axis do not match the ones of my first x-axis.
Does anyone know how to fix this problem?

Related

Set size of subplot in matplotlib

I wonder how to set the size of the subplot when figure contains multiple subplots (5 × 2 in my case). No matter how big I allow the whole figure to be, the subplots always seem to be small. I would like to have direct control of the size of the subplot in this figure. The simplified version of the code is pasted below.
import numpy as np
import matplotlib.pyplot as plt
x = np.random.randn(20)
y = np.random.randn(20)
fig = plt.figure(figsize=(20, 8))
for i in range(0,10):
ax = fig.add_subplot(5, 2, i+1)
plt.plot(x, y, 'o')
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
# x and y axis should be equal length
x0,x1 = ax.get_xlim()
y0,y1 = ax.get_ylim()
ax.set_aspect(abs(x1-x0)/abs(y1-y0))
plt.show()
fig.savefig('plot.pdf', bbox_inches='tight')
Just switch figure size width and height from:
fig = plt.figure(figsize=(20, 8))
to:
fig = plt.figure(figsize=(8, 20))
to use the whole page for your plots.
This will change your plot from:
to:

Matplotlib 3d plot: how to get rid of the excessive white space?

If I make a 3d plot in Matplotlib:
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection='3d')
x_labels = [10,20,30]
x = [1,2,3,4]
y = [3,1,5,1]
legend = False
for label in x_labels:
x_3d = label*np.ones_like(x)
ax.plot(x_3d, x, y, color='black', label='GMM')
if legend == False:
ax.legend()
legend = True
ax.set_zlabel('test')
It will produce:
The left side have excessive white space. I want to know if it is possible to get rid of it?
It's probably too late, but I came across similar problems and here is what I did to remove the white space: use fig.subplot_adjust() to put left/right outside the normal region. In your case I found fig.subplot_adjust(left=-0.11) gives a reasonable result.
Full code below:
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection='3d')
x_labels = [10,20,30]
x = [1,2,3,4]
y = [3,1,5,1]
legend = False
for label in x_labels:
x_3d = label*np.ones_like(x)
ax.plot(x_3d, x, y, color='black', label='GMM')
if legend == False:
ax.legend()
legend = True
ax.set_zlabel('test')
fig.tight_layout()
fig.subplots_adjust(left=-0.11) # plot outside the normal area

Changing axis without changing data (Python)

How could I plot some data, remove the axis created by that data, and replace them with axis of a different scale?
Say I have something like:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
plt.xlim([0,5])
plt.ylim([0,5])
plt.plot([0,1,2,3,4,5])
plt.show()
This plots a line in a 5x5 plot with ranges from 0 to 5 on both axis. I would like to remove the 0 to 5 axis and say replace it with a -25 to 25 axis. This would just change the axis, but I don't want to move any of the data, i.e., it looks identical to the original plot just with different axis. I realize this can be simply done by shifting the data, but I do not wish to alter the data.
You could use plt.xticks to find the location of the labels, and then set the labels to 5 times the location values. The underlying data does not change; only the labels.
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
plt.xlim([0,5])
plt.ylim([0,5])
plt.plot([0,1,2,3,4,5])
locs, labels = plt.xticks()
labels = [float(item)*5 for item in locs]
plt.xticks(locs, labels)
plt.show()
yields
Alternatively, you could change the ticker formatter:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
N = 128
fig = plt.figure()
ax = fig.add_subplot(111)
plt.plot(range(N+1))
plt.xlim([0,N])
plt.ylim([0,N])
ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: ('%g') % (x * 5.0)))
plt.show()

Matplotlib: how to set ticks of twinned axis in log plot

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$")

How to add a second x-axis in matplotlib

I have a very simple question. I need to have a second x-axis on my plot and I want that this axis has a certain number of tics that correspond to certain position of the first axis.
Let's try with an example. Here I am plotting the dark matter mass as a function of the expansion factor, defined as 1/(1+z), that ranges from 0 to 1.
semilogy(1/(1+z),mass_acc_massive,'-',label='DM')
xlim(0,1)
ylim(1e8,5e12)
I would like to have another x-axis, on the top of my plot, showing the corresponding z for some values of the expansion factor. Is that possible? If yes, how can I have xtics ax
I'm taking a cue from the comments in #Dhara's answer, it sounds like you want to set a list of new_tick_locations by a function from the old x-axis to the new x-axis. The tick_function below takes in a numpy array of points, maps them to a new value and formats them:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()
X = np.linspace(0,1,1000)
Y = np.cos(X*20)
ax1.plot(X,Y)
ax1.set_xlabel(r"Original x-axis: $X$")
new_tick_locations = np.array([.2, .5, .9])
def tick_function(X):
V = 1/(1+X)
return ["%.3f" % z for z in V]
ax2.set_xlim(ax1.get_xlim())
ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.set_xlabel(r"Modified x-axis: $1/(1+X)$")
plt.show()
You can use twiny to create 2 x-axis scales. For Example:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()
a = np.cos(2*np.pi*np.linspace(0, 1, 60.))
ax1.plot(range(60), a)
ax2.plot(range(100), np.ones(100)) # Create a dummy plot
ax2.cla()
plt.show()
Ref: http://matplotlib.sourceforge.net/faq/howto_faq.html#multiple-y-axis-scales
Output:
From matplotlib 3.1 onwards you may use ax.secondary_xaxis
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1,13, num=301)
y = (np.sin(x)+1.01)*3000
# Define function and its inverse
f = lambda x: 1/(1+x)
g = lambda x: 1/x-1
fig, ax = plt.subplots()
ax.semilogy(x, y, label='DM')
ax2 = ax.secondary_xaxis("top", functions=(f,g))
ax2.set_xlabel("1/(x+1)")
ax.set_xlabel("x")
plt.show()
If You want your upper axis to be a function of the lower axis tick-values you can do as below. Please note: sometimes get_xticks() will have a ticks outside of the visible range, which you have to allow for when converting.
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
ax1 = fig.add_subplot(111)
ax1.plot(range(5), range(5))
ax1.grid(True)
ax2 = ax1.twiny()
ax2.set_xticks( ax1.get_xticks() )
ax2.set_xbound(ax1.get_xbound())
ax2.set_xticklabels([x * 2 for x in ax1.get_xticks()])
title = ax1.set_title("Upper x-axis ticks are lower x-axis ticks doubled!")
title.set_y(1.1)
fig.subplots_adjust(top=0.85)
fig.savefig("1.png")
Gives:
Answering your question in Dhara's answer comments: "I would like on the second x-axis these tics: (7,8,99) corresponding to the x-axis position 10, 30, 40. Is that possible in some way?"
Yes, it is.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
a = np.cos(2*np.pi*np.linspace(0, 1, 60.))
ax1.plot(range(60), a)
ax1.set_xlim(0, 60)
ax1.set_xlabel("x")
ax1.set_ylabel("y")
ax2 = ax1.twiny()
ax2.set_xlabel("x-transformed")
ax2.set_xlim(0, 60)
ax2.set_xticks([10, 30, 40])
ax2.set_xticklabels(['7','8','99'])
plt.show()
You'll get:
I'm forced to post this as an answer instead of a comment due to low reputation.
I had a similar problem to Matteo. The difference being that I had no map from my first x-axis to my second x-axis, only the x-values themselves. So I wanted to set the data on my second x-axis directly, not the ticks, however, there is no axes.set_xdata. I was able to use Dhara's answer to do this with a modification:
ax2.lines = []
instead of using:
ax2.cla()
When in use also cleared my plot from ax1.

Categories