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
Related
Here is what I have. Is it possible to align bars in ax1 with the others?
image
import numpy as np
import matplotlib.pyplot as plt
x= np.arange(10)+.5
y = np.sin(x)
fig=plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(223)
ax3 = plt.subplot(224)
ax1.bar(x,y)
ax2.bar(x[:5],y[:5])
ax3.bar(x[5:],y[5:])
plt.show()
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:
I want to save an instance of a plot into an object so that I can display it later by just calling that object.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
fig1 = plt.gcf()
plt.plot(x, y2, linewidth=1, color = 'red')
fig2 = plt.gcf()
In this example, I first draw a blue line (y1=x) and use plt.gcf() to save an instance of this plot in fig1. Then I add a red curve (y2=sin(x)) to the plot and use plt.gcf() again to save this plot in fig2. Now, I expect that when I call fig1 I only get the blue line, and when I call fig2 I get both lines. Like this (I'm in Jupyter):
fig1 # or fig1.show() if not in Jupyter
Only blue curve
fig2
Both curves
But, in reality, when I call fig1 and fig2, both of them show both curves (like the second picture). Can someone please help how I can correctly get an instance of each plot so that I can display each of them later whenever I want?
You need to force matplotlib to actually draw the figure by setting a plot.show() in your code:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
plt.show()
fig1 = plt.gcf()
plt.plot(x, y2, linewidth=1, color = 'red')
plt.show()
fig2 = plt.gcf()
Using the function plt.plot() always plots to the current axis (if no axis is present, a new one is created).
You can also tell matplotlib explicitly to open a new figure:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
# open first figure
fig1 = plt.figure()
# plot
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
# open second figure
fig2 = plt.figure()
#plot
plt.plot(x, y2, linewidth=1, color = 'red')
Although this already fixes your problem, it is considered good practice to use an object-oriented version of this like this:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
# open first figure, axis
fig1, ax1 = plt.subplots()
# plot
ax1.plot(x, y1, linewidth=1, color = 'deepskyblue')
# open second figure, axis
fig2, ax2 = plt.subplots()
#plot
ax2.plot(x, y2, linewidth=1, color = 'red')
In all cases, you will get:
Now, why don't you need plt.show() in the other approaches? Well, matplotlib by explicitly opening a new figure/axis, it is obvious that the previous axis is finished and can be drawn.
The last approach is the clearest as you tell exactly which figure and which axis you are considering.
I am trying to create a figure consisting of multiple subplots stacked on top of each other. However, I also want a single plot that runs through all the stacked subplots and shows up "behind" them. I'm not concerned about the actual y-values so it's fine that the y-axis is unreadable in this case. Below is what I have so far:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x
y2 = x**2
y3 = x**3
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.4])
ax2 = fig.add_axes([0.1, 0.5, 0.8, 0.4])
ax3 = ax1.twinx()
ax4 = ax2.twinx()
ax1.plot(x, y)
ax2.plot(x, y3)
ax3.plot(x, y2)
ax4.plot(x, y2)
Essentially, I want ax3 and ax4 to combine into one large plot that shows a single quadratic function while having a cubic function stacked on top of a linear function in the same figure. Ideally, I'll have three actually separate axes since I'll want to be customizing and performing actions to one subplot without affecting the other two in the future.
Thanks!
I guess the idea would be to first create two subplots one below the other and reduce the spacing in between to 0. Then create a new subplot covering the complete area and make the background transparent. Also put the ticks and labels of the third axes to the right. Then plot to each of the three axes.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x
y2 = x**2
y3 = x**3
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
fig.subplots_adjust(hspace=0.0)
ax3 = fig.add_subplot(111, sharex=ax1, label="right axes")
l1, = ax1.plot(x, y, color="C0")
l2, = ax2.plot(x, y3, color="C2")
l3, = ax3.plot(x, y2, color="C3")
ax1.tick_params(axis="y", colors=l1.get_color())
ax2.tick_params(axis="y", colors=l2.get_color())
ax3.set_facecolor("none")
ax3.tick_params(labelbottom=False, bottom=False, labelleft=False, left=False,
right=True, labelright=True, colors=l3.get_color())
plt.show()
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()