I create tightly spaced subplots with shared axes using AxesGrid. This leads to overlapping tick labels where the axes meet (Figure, A). To avoid this overlap I want to remove the first tick of the lower right axes. However, the axes are shared, so the first tick label is removed on the other axes too (Figure, B).
Is there a way to show different tick labels on shared axes?
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid
fig = plt.figure()
grid = AxesGrid(fig, 111, nrows_ncols=(2, 2), share_all=True)
#grid[-1].set_xticks([0.2, 0.4, 0.6, 0.8, 1.0]) # This applies to *all* axes
plt.show()
You can get the axis handle from grid which is just a list with ax=grid[3] and then use xticks = ax.xaxis.get_major_ticks() and xticks[1].label1.set_visible(False). As a minimal example,
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid
import numpy as np
from matplotlib.cbook import get_sample_data
#Setup figure/grid
fig = plt.figure()
grid = AxesGrid(fig, 111, nrows_ncols = (2, 2), share_all=True)
#Plot some data
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
Z = np.load(f)
for i in range(4):
im = grid[i].imshow(Z)
#Set tick one of axis 3 in grid to off
ax = grid[3]
xticks = ax.xaxis.get_major_ticks()
xticks[1].label1.set_visible(False)
plt.draw()
plt.show()
Related
The code below produces gaps between the subplots. How do I remove the gaps between the subplots and make the image a tight grid?
import matplotlib.pyplot as plt
for i in range(16):
i = i + 1
ax1 = plt.subplot(4, 4, i)
plt.axis('on')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_aspect('equal')
plt.subplots_adjust(wspace=None, hspace=None)
plt.show()
The problem is the use of aspect='equal', which prevents the subplots from stretching to an arbitrary aspect ratio and filling up all the empty space.
Normally, this would work:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
plt.subplots_adjust(wspace=0, hspace=0)
The result is this:
However, with aspect='equal', as in the following code:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
a.set_aspect('equal')
plt.subplots_adjust(wspace=0, hspace=0)
This is what we get:
The difference in this second case is that you've forced the x- and y-axes to have the same number of units/pixel. Since the axes go from 0 to 1 by default (i.e., before you plot anything), using aspect='equal' forces each axis to be a square. Since the figure is not a square, pyplot adds in extra spacing between the axes horizontally.
To get around this problem, you can set your figure to have the correct aspect ratio. We're going to use the object-oriented pyplot interface here, which I consider to be superior in general:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,8)) # Notice the equal aspect ratio
ax = [fig.add_subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
a.set_aspect('equal')
fig.subplots_adjust(wspace=0, hspace=0)
Here's the result:
You can use gridspec to control the spacing between axes. There's more information here.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.figure(figsize = (4,4))
gs1 = gridspec.GridSpec(4, 4)
gs1.update(wspace=0.025, hspace=0.05) # set the spacing between axes.
for i in range(16):
# i = i + 1 # grid spec indexes from 0
ax1 = plt.subplot(gs1[i])
plt.axis('on')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_aspect('equal')
plt.show()
Without resorting gridspec entirely, the following might also be used to remove the gaps by setting wspace and hspace to zero:
import matplotlib.pyplot as plt
plt.clf()
f, axarr = plt.subplots(4, 4, gridspec_kw = {'wspace':0, 'hspace':0})
for i, ax in enumerate(f.axes):
ax.grid('on', linestyle='--')
ax.set_xticklabels([])
ax.set_yticklabels([])
plt.show()
plt.close()
Resulting in:
With recent matplotlib versions you might want to try Constrained Layout. This does (or at least did) not work with plt.subplot() however, so you need to use plt.subplots() instead:
fig, axs = plt.subplots(4, 4, constrained_layout=True)
Have you tried plt.tight_layout()?
with plt.tight_layout()
without it:
Or: something like this (use add_axes)
left=[0.1,0.3,0.5,0.7]
width=[0.2,0.2, 0.2, 0.2]
rectLS=[]
for x in left:
for y in left:
rectLS.append([x, y, 0.2, 0.2])
axLS=[]
fig=plt.figure()
axLS.append(fig.add_axes(rectLS[0]))
for i in [1,2,3]:
axLS.append(fig.add_axes(rectLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[4]))
for i in [1,2,3]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[8]))
for i in [5,6,7]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
axLS.append(fig.add_axes(rectLS[12]))
for i in [9,10,11]:
axLS.append(fig.add_axes(rectLS[i+4],sharex=axLS[i],sharey=axLS[-1]))
If you don't need to share axes, then simply axLS=map(fig.add_axes, rectLS)
Another method is to use the pad keyword from plt.subplots_adjust(), which also accepts negative values:
import matplotlib.pyplot as plt
ax = [plt.subplot(2,2,i+1) for i in range(4)]
for a in ax:
a.set_xticklabels([])
a.set_yticklabels([])
plt.subplots_adjust(pad=-5.0)
Additionally, to remove the white at the outer fringe of all subplots (i.e. the canvas), always save with plt.savefig(fname, bbox_inches="tight").
I have a list of values which I want to plot the distribution for. I'm using a box-plot but it would be nice to add some dotted lines going from the boxplot quartiles to the axis. Also I want just the quartile values displayed on the x ticks.
Here's a rough idea but with values at the end instead of names.
import numpy as np
import pandas as pd
import matplotlib.pylab as plt
vel_arr = np.random.rand(1000,1)
fig = plt.figure(1, figsize=(9, 6))
ax = fig.add_subplot(111)
# Create the boxplot
ax.boxplot(vel_arr,vert=False, manage_ticks=True)
ax.set_xlabel('value')
plt.yticks([1], ['category'])
plt.show()
np.quantile calculates the desired quantiles.
ax.vlines draws vertical lines, for example from the center of the boxplot to y=0. zorder=0 makes sure these lines go behind the boxplot.
ax.set_ylim(0.5, 1.5) resets the ylims. Default, the vlines force the ylims with some extra padding.
ax.set_xticks(quantiles) sets xticks at the position of every quantile.
import numpy as np
import matplotlib.pylab as plt
vel_arr = np.random.rand(50, 1)
fig = plt.figure(1, figsize=(9, 6))
ax = fig.add_subplot(111)
ax.boxplot(vel_arr, vert=False, manage_ticks=True)
ax.set_xlabel('value')
ax.set_yticks([1])
ax.set_yticklabels(['category'])
quantiles = np.quantile(vel_arr, np.array([0.00, 0.25, 0.50, 0.75, 1.00]))
ax.vlines(quantiles, [0] * quantiles.size, [1] * quantiles.size,
color='b', ls=':', lw=0.5, zorder=0)
ax.set_ylim(0.5, 1.5)
ax.set_xticks(quantiles)
plt.show()
I'm plotting scatter points onto a map and seeing unwanted rectangles in my legend, despite the insertion of label='_nolegend_':
# import functions
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt
# Create a Stamen terrain background instance
stamen_terrain = cimgt.Stamen('terrain-background')
fig = plt.figure(figsize = (10,10))
ax = fig.add_subplot(1, 1, 1, projection=stamen_terrain.crs, label='_nolegend_')
# Set range of map, stipulate zoom level
ax.set_extent([-122.7, -121.5, 37.15, 38.15], crs=ccrs.Geodetic())
ax.add_image(stamen_terrain, 12, label='_nolegend_')
# Add scatter point
ax.scatter(-122.4194, 37.7749, s=55, c='k', transform=ccrs.PlateCarree())
ax.legend(('','','San Francisco'), loc = 3)
plt.show()
How to remove the rectangles, and just show the scatter point in the legend?
The problem is that you set labels for each of the elements in the axes via ('','','San Francisco'). Instead just set the label to the scatter itself
ax.scatter(..., label="Some City")
ax.legend(loc=3)
Alternatively, if you don't want to give the scatter a label, you can pass the handle and label to the legend:
sc = ax.scatter(...)
ax.legend(handles=[sc], labels=['Some City'], loc = 3)
Pandas offers kind='kde' when plotting. In my setting, I would prefer a kde density. The alternative kind='histogram' offers the orientation option: orientation='horizontal', which is strictly necessary for what I am doing. Unfortunately, orientation is not available for kde.
At least this is what I think that happens because I get a
in set_lineprops
raise TypeError('There is no line property "%s"' % key)
TypeError: There is no line property "orientation"
Is there any straight forward alternative for plotting kde horizontally as easily as it can be done for histogram?
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
plt.ion()
ser = pd.Series(np.random.random(1000))
ax1 = plt.subplot(2,2,1)
ser.plot(ax = ax1, kind = 'hist')
ax2 = plt.subplot(2,2,2)
ser.plot(ax = ax2, kind = 'kde')
ax3 = plt.subplot(2,2,3)
ser.plot(ax = ax3, kind = 'hist', orientation = 'horizontal')
# not working lines below
ax4 = plt.subplot(2,2,4)
ser.plot(ax = ax4, kind = 'kde', orientation = 'horizontal')
Adding previously deleted answer as a community wiki because it's a helpful answer.
pandas.Series.plot.kde does not have an option to change the orientation of the plot.
Use scipy.stats.gaussian_kde to calculate the values, and plot them on a line with matplotlib.axes.Axes.plot.
Alternatively, seaborn.kdeplot is an option.
gaussian_kde is used under the hood by both .plot.kde and sns.kdeplot
import pandas as pd
import numpy as np
import seaborn as sns
from scipy.stats import gaussian_kde
# crate subplots and don't share x and y axis ranges
fig, axes = plt.subplots(2, 2, figsize=(10, 10), sharex=False, sharey=False)
# flatten the axes for easy selection from a 1d array
axes = axes.flat
# create sample data
np.random.seed(2022)
ser = pd.Series(np.random.random(1000)).sort_values()
# plot example plots
ser.plot(ax=axes[0], kind='hist', ec='k')
ser.plot(ax=axes[1], kind='kde')
ser.plot(ax=axes[2], kind='hist', orientation='horizontal', ec='k')
# 1. create kde model
gkde = gaussian_kde(ser)
# 2. create a linspace to match the range over which the kde model is plotted
xmin, xmax = ax2.get_xlim()
x = np.linspace(xmin, xmax, 1000)
# 3. plot the values
axes[3].plot(gkde(x), x)
# Alternatively, use seaborn.kdeplot and skip 1., 2., and 3.
# sns.kdeplot(y=ser, ax=axes[3])
It seems that I can't have both setting equal axes scales AND setting the size of the plot. What I'm doing is:
fig = pl.figure(figsize=(20,20))
ax = fig.add_subplot(111)
ax.set_aspect('equal')
If I remove the figsize the plot seems to have equal scales, but with figsize I have a bigger plot but the scales aren't equal anymore.
Edit: The graph does not necessarily have to be square, just bigger.. please assume that I don't now the exact ratio of the axes
Any solutions?
Thanks
If you want to change the data limits to make the axes square, add datalim to your call to set_aspect:
ax.set_aspect('equal', 'datalim')
If you want the scaling to change so that the limits are different but the axes look square, you can calculate the axis size ratio and set it explicitly:
ax.set_aspect(1./ax.get_data_ratio())
e.g.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111)
x = np.linspace(0,np.pi,1000)
y = np.sin(3*x)**2
ax.plot(x,y)
ax.set_aspect('equal', 'datalim')
plt.show()
or
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111)
x = np.linspace(0,np.pi,1000)
y = np.sin(3*x)**2
ax.plot(x,y)
ax.set_aspect(1./ax.get_data_ratio())
plt.show()