Setting the same scale for subplots but different limits using matplotlib - python

I want the scaling to be the same for my two subplots to make them comparable, but the limits should be set automatically.
Here a small working example:
import matplotlib.pyplot as plt
import numpy as np
time = range(20)
y1 = np.random.rand(20)*2
y2 = np.random.rand(20) + 10
fig, axes = plt.subplots(2, figsize=(10,4), sharex=True, sharey=True)
# OPTION 2: fig, axes = plt.subplots(2, figsize=(10,4))
axes[0].plot(time, y1)
axes[1].plot(time, y2)
plt.show()
The plot looks like this:
and with option 2 uncommented it looks like this:
In the second plot, it looks like y1 and y2 are equally noisy which is wrong, but in plot 1 the axis limits are too high/low.

I am not aware of an automatic scaling function that does this (that does not mean it does not exist - actually, I would be surprised it did not exist). But it is not difficult to write it:
import matplotlib.pyplot as plt
#data generation
import numpy as np
np.random.seed(123)
time = range(20)
y1 = np.random.rand(20)*2
y2 = np.random.rand(20) + 10
y3 = np.random.rand(20)*6-12
#plot data
fig, axes = plt.subplots(3, figsize=(10,8), sharex=True)
for ax, y in zip(axes, [y1, y2, y3]):
ax.plot(time, y)
#determine axes and their limits
ax_selec = [(ax, ax.get_ylim()) for ax in axes]
#find maximum y-limit spread
max_delta = max([lmax-lmin for _, (lmin, lmax) in ax_selec])
#expand limits of all subplots according to maximum spread
for ax, (lmin, lmax) in ax_selec:
ax.set_ylim(lmin-(max_delta-(lmax-lmin))/2, lmax+(max_delta-(lmax-lmin))/2)
plt.show()
Sample output:

Related

Giving a Y-Scale to matplotlib

I have two axes. I want to plot 4e-6 in ax1 and plot 4e-7 in ax2. How can I make y-axis of ax1 like y-axis of ax2? i.e, it should be scaled to 1e-6 and the scale should appear at the top of the y-axis.
Here is the code:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
x = np.linspace(0, 1000, 3)
y1 = np.full_like(x, 4e-6)
y2 = np.full_like(x, 4e-7)
ax1.plot(x, y1)
ax2.plot(x, y2)
plt.show()
Here is the figure:
This can be achieved as shown here: default_tick_formatter
Mentioning the y_axis value as 4*1e-7 instead of 4e-7, will result in the solution to your requirement.
The following was recreated and the solution is produced below:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
x = np.linspace(0, 1000, 3)
y1 = np.full_like(x, 4*1e-6)
y2 = np.full_like(x, 4*1e-7)
ax1.plot(x, y1)
ax2.plot(x, y2)
plt.show()
The results would look

How draw box across multiple axes on matplotlib using ax position as reference

I would like to draw a box across multiple axes, using one ax coordinates as reference. The simple code I have, that does not generate the box is
import matplotlib.pyplot as plt
import numpy as np
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=False, sharey=False, figsize=(15,9))
x = 2 * np.pi * np.arange(1000) / 1000
y1 = np.sin(x)
y2 = np.cos(x)
ax1.plot(x,y1)
ax2.plot(x,y2)
plt.show()
This generate the following figure:
What I would like to have is the following figure, using x cordinates from ax2 to specify the position:
The question is a bit what purpose the rectangle should serve. If it is simply a rectangle bound to ax2 but extending up to the upper edge of ax1 a rectangle can be created like
import matplotlib.pyplot as plt
import numpy as np
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=False, sharey=False, figsize=(15,9))
x = 2 * np.pi * np.arange(1000) / 1000
y1 = np.sin(x)
y2 = np.cos(x)
ax1.plot(x,y1)
ax2.plot(x,y2)
rect = plt.Rectangle((1,0), width=1, height=2+fig.subplotpars.wspace,
transform=ax2.get_xaxis_transform(), clip_on=False,
edgecolor="k", facecolor="none", linewidth=3)
ax2.add_patch(rect)
plt.show()
But that will of course stay where it is, even if the limits of ax1 change. Is that desired?
So maybe a more interesting solution is one where the rectangle follows the coordinates in both axes. The following would only work in matplotlib 3.1, which is as of today only available as prerelease
(pip install --pre --upgrade matplotlib)
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
import numpy as np
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=False, sharey=False, figsize=(15,9))
x = 2 * np.pi * np.arange(1000) / 1000
y1 = np.sin(x)
y2 = np.cos(x)
ax1.plot(x,y1)
ax2.plot(x,y2)
def rectspan(x1, x2, ax1, ax2, **kwargs):
line1, = ax1.plot([x1, x1, x2, x2],[0,1,1,0],
transform=ax1.get_xaxis_transform(), **kwargs)
line2, = ax2.plot([x1, x1, x2, x2],[1,0,0,1],
transform=ax2.get_xaxis_transform(), **kwargs)
for x in (x1, x2):
p = ConnectionPatch((x,1), (x,0),
coordsA=ax2.get_xaxis_transform(),
coordsB=ax1.get_xaxis_transform(), **kwargs)
ax1.add_artist(p)
rectspan(1, 2, ax1, ax2, color="k", linewidth=3)
plt.show()
There is definitely a simpler way to do it using a Rectangle patch but this is a workaround solution for the time being. The idea is to have 4 lines: 2 horizontal which are restricted to ax1 and ax2 respectively, and 2 vertical which span both ax1 and ax2. For the latter two, you use ConnectionPatch covering both the axes. To have the upper and lower y-value for the horizontal and vertical lines, you use get_ylim() function. The idea to plot vertical lines came from this official example and this answer by ImportanceOfBeingErnest
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import ConnectionPatch
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=False, sharey=False, figsize=(15,9))
x = 2 * np.pi * np.arange(1000) / 1000
y1 = np.sin(x)
y2 = np.cos(x)
ax1.plot(x,y1)
ax2.plot(x,y2)
y_up, y_down = ax1.get_ylim(), ax2.get_ylim()
ax1.hlines(max(y_up), 1, 2, linewidth=4)
ax2.hlines(min(y_down), 1, 2, linewidth=4)
line1 = ConnectionPatch(xyA=[1,min(y_down)], xyB=[1,max(y_up)], coordsA="data", coordsB="data",
axesA=ax2, axesB=ax1, color="k", lw=4)
line2 = ConnectionPatch(xyA=[2,min(y_down)], xyB=[2,max(y_up)], coordsA="data", coordsB="data",
axesA=ax2, axesB=ax1, color="k", lw=4)
ax2.add_artist(line1)
ax2.add_artist(line2)
plt.show()

is there a way to add 2 yticks and 2 bars in one matplotlib plot?

Hello i want to ask if there is a way to add another yticks with bars in the same plot?
This would be an example:
Here is a possible way, using ax.set_yticklabels:
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(0)
x = np.arange(8)
y1 = np.random.rand(4)
y2 = np.random.rand(4)
fig, ax = plt.subplots()
ax.barh(x[::2], y1, color="C3")
ax.barh(x[1::2], y2, color="C0")
t = range(len(x))
ax.set_yticks(t)
t[0::2] = ["another tick"]*(len(x)/2)
t[1::2] = ["tick {}".format(i+1) for i in range((len(x)/2))]
ax.set_yticklabels(t)
plt.tight_layout()
plt.show()

Making multiple figures in matplotlib with legend on each one

I am trying to make multiple figures in parallel, each with its own legend. My code produces multiple figures but I can only ever get the legend to appear on the last figure instance - is there a way of getting it to appear on all figures? I have a large number of datasets so I would like to be able to use a for loop (or similar) - making each figure separately is not really an option.
I have included a minimum working example below that reproduces the problem.
import numpy as np
import matplotlib.pyplot as plt
X1 = np.linspace(0,5,5)
X2 = np.linspace(1,6,5)
Y1 = np.power(X1,2)
Y2 = np.power(X2,2)
Z1 = np.power(X1,3)
Z2 = np.power(X2,3)
Xs = [X1,X2]
Ys = [Y1,Y2]
Zs = [Z1,Z2]
# Marker size
size = 100
for x,y,z, in zip(Xs,Ys,Zs):
plt.figure()
ax = plt.subplot(111)
ax.scatter(x,y,linewidth=1.5,s=size,facecolors='#0571b0',marker='o',alpha=0.5,label='A label')
ax.scatter(x,z,linewidth=1.5,s=size,facecolors='#92c5de',marker='o',alpha=0.5,label='Another label')
plt.legend(bbox_to_anchor=(1.45,1.), loc='top left',scatterpoints=1,fontsize=8)
plt.show()
It seems the legend is simply out of the figure. You place it at (1.45, 1) (in axes coordinates. Putting it at (1,1) and setting the location e.g. to loc="upper right" (note that "top left" does not exist), will produce the legend in the plot.
Here is the complete example:
import numpy as np
import matplotlib.pyplot as plt
X1 = np.linspace(0,5,5)
X2 = np.linspace(1,6,5)
Xs = [X1,X2]
Ys = [X1**2,X2**2]
Zs = [X1**3,X2**3]
# Marker size
size = 100
for x,y,z, in zip(Xs,Ys,Zs):
plt.figure()
ax = plt.subplot(111)
ax.scatter(x,y,linewidth=1.5,s=size,facecolors='#0571b0',marker='o',alpha=0.5,label='A label')
ax.scatter(x,z,linewidth=1.5,s=size,facecolors='#92c5de',marker='o',alpha=0.5,label='Another label')
plt.legend(bbox_to_anchor=(1,1), loc='upper right',scatterpoints=1,fontsize=8)
plt.show()

Axes Subplot y size

I have two subplots that share the x-axes. The first one has data and a fit function, in the second one is the difference between the data and the fit function. In the figure both subplots have the same y axis size (in pixels). Now i want the y axis of the data and the fit to be bigger than the axis of the errors. my code is the following:
import matplotlib.pyplot as plt
f, axarr = plt.subplots(2, sharex=True,figsize=(15, 12))
axarr[0].scatter(x, data , facecolors='none', edgecolors='crimson')
axarr[0].plot(x, fit, color='g',linewidth=1.5)
axarr[0].set_ylim([18,10])
axarr[1].plot(x,data-fit,color='k',linewidth=width)
axarr[1].set_ylim([-0.4,0.4])
yticks[-1].label1.set_visible(False)
plt.subplots_adjust(hspace=0.)
is there any code that sets the size of the second plot?
Take a look at this example, using gridspec. I believe it is exactly what you want. Below is the example adopted for your case. Edited to also share the x-axis
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
fig = plt.figure(figsize=(8, 6))
gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1])
ax0 = plt.subplot(gs[0])
ax1 = plt.subplot(gs[1], sharex=ax0) # <---- sharex=ax0 will share ax1 with ax2
ax0.plot(x, y)
ax1.plot(y, x)
plt.show()
Or even simpler by following Hagnes answer in the first link:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.2)
y = np.sin(x)
f, (a0, a1) = plt.subplots(2,1, gridspec_kw = {'height_ratios':[1, 3]}, sharex=True) # <---- sharex=True will share the xaxis between the two axes
a0.plot(x, y)
a1.plot(y, x)
plt.show()

Categories

Resources