invert_xaxis gives an error when using matplotlib plt.barh - python

I am trying to plot 2 way bar charts. I want to invert the x-axis of x1 so that 0 is in the middle of both. I keep getting the error:
AttributeError: 'BarContainer' object has no attribute 'invert_xaxis'
Here is my code:
import matplotlib.pyplot as plt
y = ['F','M','H']
x1 = [8, 4, 3]
x2 = [2, 4, 7]
fig, axes = plt.subplots(ncols=2, sharey=True)
axes[0] = plt.barh(y, x1, align='center', color='b')
axes[1] = plt.barh(y, x2, align='center', color='r')
axes[0].invert_xaxis()
plt.show()

The problem is that you are assigning the plots to the two axis objects instead of using them to plot. The correct way is to directly use the axis objects to plot the barh. Then the things will work as expected.
import matplotlib.pyplot as plt
y = ['F','M','H']
x1 = [8, 4, 3]
x2 = [2, 4, 7]
fig, axes = plt.subplots(ncols=2, sharey=True)
axes[0].barh(y, x1, align='center', color='b') # <---- Changed here
axes[1].barh(y, x2, align='center', color='r') # <---- Changed here
axes[0].invert_xaxis()
plt.show()

Related

Plot different functions as different axis on same figure

I am trying to put different functions plots on the same figure. Is that possible?
Overall, this is what I have tried:
fig1 = plt.figure()
fig1, (ax1, ax2) = plt.subplots(2)
x = [1,2,3,4]
y = [5,6,7,8]
z = [1,2,5,6]
def plot1(xcoord,ycoord,ax=None):
ax=ax
ax.plot(xcoord,ycoord)
return plt.show()
def plot2(xcoord,ycoord,ax=None):
ax=ax
ax.plot(xcoord,ycoord)
ax.grid('on')
return plt.show()
doplot1(x,y,ax1)
doplot2(x,z,ax2)
I would like it to return with axis doplot1 on ax1 and doplot2 on ax2.
Thanks for helping!!
The standard way to do what you want, would be:
import matplotlib.pyplot as plt
def doplot1(xcoord, ycoord, ax=None):
ax.plot(xcoord, ycoord)
def doplot2(xcoord, ycoord, ax=None):
ax = ax or plt.gca()
ax.plot(xcoord, ycoord)
ax.grid('on')
x = [1, 2, 3, 4]
y = [5, 6, 7, 8]
z = [1, 2, 5, 6]
fig1, (ax1, ax2) = plt.subplots(2)
doplot1(x, y, ax1)
doplot2(x, z, ax2)
plt.show()
Some remarks:
When using plt.subplots() (not to be confounded by plt.subplot() without the 's'), there is no need to call plt.figure() as that is now done automatically.
plt.show() should only be called after all the subplots have been created.
ax = ax or plt.gca() can be used to check that when no ax is given (so being None) it gets the value of the current ax (gca means "get current axis").

How to plot data in background of multiple subplots in matplotlib

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()

matplotlib two y axes with mixed z order

I would like to plot data on two y axes such that some of the data on the second y axis is behind the first y axis graph and part of it is above. Essentially I would like to have use "global" zorder parameter. Is that possible?
Here is a minimal example:
import numpy as np
import matplotlib.pyplot as plt
# generate data
x = np.linspace(0,30,30)
y1 = np.random.random(30)+x
y2 = np.random.random(30)+x*2
# create figure
fig, ax = plt.subplots()
# y1 axis
ax.plot(x,y1,lw=5,c='#006000', zorder=2)
ax.set_ylim((0,30))
ax.set_xlim((0,30))
# y2 axis
ax2 = ax.twinx() # instantiate a second axes that shares the same x-axis
ax2.fill_between([0, 30], [10, 10], color='pink', lw=0, zorder=1)
ax2.fill_between([0, 30], [60, 60], y2=[10, 10], color='gray', lw=0, zorder=1)
ax2.plot(x, y2,'o',ms=3,c='black', zorder=3)
ax2.set_ylim((0,60))
ax2.set_xlim((0,30))
# move y1 axis to the front
ax.set_zorder(ax2.get_zorder()+1)
ax.patch.set_visible(False)
I would like the background fill color to be in the background but the black data points should be on top of the green line. I tried to achieve this by defining the zorder parameter for these curves but apparently the zorder is only defined within one axis and not across multiple axes.
Here is a solution that gets what you want, however sub-ideal it may be in implementation.
import numpy as np
import matplotlib.pyplot as plt
# generate data
x = np.linspace(0,30,30)
y1 = np.random.random(30)+x
y2 = np.random.random(30)+x*2
# create figure
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax3 = ax2.twiny()
ax1.get_shared_x_axes().join(ax2, ax3)
# line
ax1.plot(x,y1,lw=5,c='#006000')
ax1.set_ylim((0,30))
ax1.set_xlim((0,30))
# points
ax2.plot(x, y2,'o',ms=3,c='black')
ax2.set_ylim((0,60))
# fills
ax3.set_xticklabels([])
ax3.get_xaxis().set_visible(False)
ax3.fill_between([0, 30], [10, 10], color='pink', lw=0)
ax3.fill_between([0, 30], [60, 60], y2=[10, 10], color='gray', lw=0)
# order
ax3.zorder = 1 # fills in back
ax1.zorder = 2 # then the line
ax2.zorder = 3 # then the points
ax1.patch.set_visible(False)
plt.show()
It seems there is a clear relationship between the two axes (in this case a factor of 2). So one could plot everything in the same axes and just scale the necessary parts by the factor. (This requires matplotlib >= 3.1)
import numpy as np
import matplotlib.pyplot as plt
# generate data
x = np.linspace(0,30,30)
y1 = np.random.random(30)+x
y2 = np.random.random(30)+x*2
# create figure
fig, ax = plt.subplots()
f = lambda x: 2*x
g = lambda x: x/2
ax2 = ax.secondary_yaxis('right', functions=(f,g))
ax.plot(x, y1,lw=5,c='#006000', zorder=2)
ax.plot(x, g(y2),'o',ms=3,c='black', zorder=3)
ax.set_ylim((0,30))
ax.set_xlim((0,30))
ax.fill_between([0, 30], [5, 5], color='pink', lw=0, zorder=1)
ax.fill_between([0, 30], [30, 30], y2=[5, 5], color='gray', lw=0, zorder=0)
plt.show()

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()

Matplotlib different size subplots

I need to add two subplots to a figure. One subplot needs to be about three times as wide as the second (same height). I accomplished this using GridSpec and the colspan argument but I would like to do this using figure so I can save to PDF. I can adjust the first figure using the figsize argument in the constructor, but how do I change the size of the second plot?
As of matplotlib 3.6.0, width_ratios and height_ratios can now be passed directly as keyword arguments to plt.subplots and subplot_mosaic, as per What's new in Matplotlib 3.6.0 (Sep 15, 2022).
f, (a0, a1) = plt.subplots(1, 2, width_ratios=[3, 1])
f, (a0, a1, a2) = plt.subplots(3, 1, height_ratios=[1, 1, 3])
Another way is to use the subplots function and pass the width ratio with gridspec_kw
matplotlib Tutorial: Customizing Figure Layouts Using GridSpec and Other Functions
matplotlib.gridspec.GridSpec has available gridspect_kw options
import numpy as np
import matplotlib.pyplot as plt
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
f, (a0, a1) = plt.subplots(1, 2, gridspec_kw={'width_ratios': [3, 1]})
a0.plot(x, y)
a1.plot(y, x)
f.tight_layout()
f.savefig('grid_figure.pdf')
Because the question is canonical, here is an example with vertical subplots.
# plot it
f, (a0, a1, a2) = plt.subplots(3, 1, gridspec_kw={'height_ratios': [1, 1, 3]})
a0.plot(x, y)
a1.plot(x, y)
a2.plot(x, y)
f.tight_layout()
You can use gridspec and figure:
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(1, 2, width_ratios=[3, 1])
ax0 = plt.subplot(gs[0])
ax0.plot(x, y)
ax1 = plt.subplot(gs[1])
ax1.plot(y, x)
plt.tight_layout()
plt.savefig('grid_figure.pdf')
I used pyplot's axes object to manually adjust the sizes without using GridSpec:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# definitions for the axes
left, width = 0.07, 0.65
bottom, height = 0.1, .8
bottom_h = left_h = left+width+0.02
rect_cones = [left, bottom, width, height]
rect_box = [left_h, bottom, 0.17, height]
fig = plt.figure()
cones = plt.axes(rect_cones)
box = plt.axes(rect_box)
cones.plot(x, y)
box.plot(y, x)
plt.show()
Probably the simplest way is using subplot2grid, described in Customizing Location of Subplot Using GridSpec.
ax = plt.subplot2grid((2, 2), (0, 0))
is equal to
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 2)
ax = plt.subplot(gs[0, 0])
so bmu's example becomes:
import numpy as np
import matplotlib.pyplot as plt
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
fig = plt.figure(figsize=(8, 6))
ax0 = plt.subplot2grid((1, 3), (0, 0), colspan=2)
ax0.plot(x, y)
ax1 = plt.subplot2grid((1, 3), (0, 2))
ax1.plot(y, x)
plt.tight_layout()
plt.savefig('grid_figure.pdf')
In a simple way, different size sub plotting can also be done without gridspec:
plt.figure(figsize=(12, 6))
ax1 = plt.subplot(2,3,1)
ax2 = plt.subplot(2,3,2)
ax3 = plt.subplot(2,3,3)
ax4 = plt.subplot(2,1,2)
axes = [ax1, ax2, ax3, ax4]
A nice way of doing this was added in matplotlib 3.3.0, subplot_mosaic.
You can make a nice layout using an "ASCII art" style.
For example
fig, axes = plt.subplot_mosaic("ABC;DDD")
will give you three axes on the top row and one spanning the full width on the bottom row like below
A nice thing about this method is that the axes returned from the function is a dictionary with the names you define, making it easier to keep track of what is what e.g.
axes["A"].plot([1, 2, 3], [1, 2, 3])
You can also pass a list of lists to subplot_mosaic if you want to use longer names
fig, axes = plt.subplot_mosaic(
[["top left", "top centre", "top right"],
["bottom row", "bottom row", "bottom row"]]
)
axes["top left"].plot([1, 2, 3], [1, 2, 3])
will produce the same figure

Categories

Resources