Matplotlib subplot y-axis scale overlaps with plot above - python

I am trying to plot 3 subplots without any white space between them. The default y axis ticklabels use a scale displayed to the top right of the y axis (1e-8 in the example below), which would be fine except for the lower two plots this overlaps with the plot above. Anyone know how to fix this? A small example is below.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.ticker import MaxNLocator
x = np.arange(0,200)
y = np.random.rand(200) * 10e-8
fig = plt.figure(figsize=(10,15))
gs1 = gridspec.GridSpec(3, 3)
gs1.update(left=0.1, right=0.9, bottom=0.5, hspace=0.0)
ax0a = plt.subplot(gs1[0, :])
ax0b = plt.subplot(gs1[1, :])
ax0c = plt.subplot(gs1[2, :])
ax0a.set_xticklabels([])
ax0b.set_xticklabels([])
ax0a.plot(x,y)
nbins = len(ax0a.get_xticklabels())
ax0a.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
ax0b.plot(x,y)
ax0b.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
ax0c.plot(x,y)
ax0c.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
so one solution is to use mtick,
import matplotlib.ticker as mtick
ax0a.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.1e'))
ax0b.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.1e'))
ax0c.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.1e'))
but I would prefer to be able to shift the scale to the left so it is outside the axis if possible.

I have two options you might want to look at.
First, set the axis location and size yourself as such:
# your imports and data above
fig = plt.figure()
ax0a = fig.add_axes([0.1, 0.1, 0.8, 0.25])
ax0b = fig.add_axes([0.1, 0.39, 0.8, 0.25], sharex=ax0a)
ax0c = fig.add_axes([0.1, 0.68, 0.8, 0.25], sharex=ax0a)
ax0a.set_xticklabels([])
ax0b.set_xticklabels([])
ax0a.plot(x,y)
nbins = len(ax0a.get_xticklabels())
ax0a.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
ax0b.plot(x,y)
ax0b.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
ax0c.plot(x,y)
ax0c.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
plt.show()
The second option is to manually adjust the location and maybe font size of the offset text:
# your original code minus data and imports
fig = plt.figure()
gs1 = gridspec.GridSpec(3, 3)
gs1.update(left=0.1, right=0.9, bottom=0.5, hspace=0.0)
ax0a = plt.subplot(gs1[0, :])
ax0b = plt.subplot(gs1[1, :])
ax0c = plt.subplot(gs1[2, :])
ax0a.set_xticklabels([])
ax0b.set_xticklabels([])
ax0a.plot(x,y)
nbins = len(ax0a.get_xticklabels())
ax0a.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
ax0b.plot(x,y)
ax0b.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
ax0c.plot(x,y)
ax0c.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='upper'))
# play around with location and font of offset text here
ax0a.get_yaxis().get_offset_text().set_x(-0.075)
ax0a.get_yaxis().get_offset_text().set_size(10)
ax0b.get_yaxis().get_offset_text().set_x(-0.075)
ax0b.get_yaxis().get_offset_text().set_size(10)
ax0c.get_yaxis().get_offset_text().set_x(-0.075)
ax0c.get_yaxis().get_offset_text().set_size(10)
plt.show()

Ok, this is probably an ugly solution but you could just simply upscale
your data in the lower plots with the range of the power i.e. ax0b.plot(x, y*1e8).
This works for your example at least.

Related

Changing axis ticks in Matplotlib with multiple connected Boxplots

I am plotting a convergence graph and to show deviations from the mean I am using connected boxplots:
For some reason Matplotlib forces ticks for each boxplot and I cannot seem to get them removed. My code for the current plot looks something like this:
label = ["" for i in range(160)]
no_labels = int(np.floor(len(label)/20))
for i in range(no_labels):
label[i*20] = str(i*no_samples/no_labels)
# Weird behaviour for the last label so adding it manually
label[-1] = no_samples
fig = plt.figure(figsize=(10,5))
ax = fig.add_axes([0,0,1,1])
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.set_ylim(0, 0.11)
ax.boxplot(data, flierprops=flyprops, showcaps=False,
boxprops=colorprops, whiskerprops={'color' : 'tab:blue'},
labels=label, patch_artist=True)
I have tried multiple ways of manipulating axis ticks which are available in MPL.
1) Trying to let MPL do the work:
ax.xaxis.set_major_locator(MultipleLocator(20))
2) Trying to set ticks manually: ax.set_xticks([list_of_ticks])
3) Tried a workaround
ax.xaxis.set_minor_locator(MultipleLocator(20))
# Removing major ticks, setting minor ticks
ax.xaxis.set_tick_params(which='major', size=0, width=2, direction='in')
ax.yaxis.set_tick_params(which='major', size=5, width=2, direction='in')
None of these seemed to work and I am unsure why. I think it may have something to do with my label variable but if I don't include it in this way MPL with include an axis lable for every entry which is a mess.
How can I set axis ticks once every 1000 entries in a connected boxplots figure?`
Edit: The input data is a numpy array of shape (15, 160) s.t. there are 160 boxplots plotted of 15 samples each. Example data for 5 boxplots of 3 samples each would look like:
np.random.rand(3,5)
>>> array([[0.05942481, 0.03408175, 0.84021109, 0.27531937, 0.62428798],
[0.24658313, 0.77910387, 0.2161348 , 0.39101172, 0.14038211],
[0.40694432, 0.22979738, 0.87056873, 0.788295 , 0.29337562]])
The main issue seems to be that the ticks need to be updated after drawing the main plot, never before.
(Having ax = fig.add_axes([0, 0, 1, 1]) is also quite unusual to work with. The standard way is fig, ax = plt.subplots(figsize=(10, 5)) which lets matplotlib a bit of flexibility for the whitespace around the plot.)
The code of the question has some missing variables and data, but the following toy example should create something similar:
import numpy as np
import matplotlib.pyplot as plt
no_samples = 8000
x = np.linspace(0, no_samples, 160)
no_labels = int(np.floor(len(x) / 20))
label = [f'{i * no_samples / no_labels:.0f}' for i in range(no_labels+1)]
fig = plt.figure(figsize=(10, 5))
ax = fig.add_axes([0.1, 0.1, 0.85, 0.85])
N = 100
data = np.random.normal(np.tile(100 / (x+1000), N), 0.001).reshape(N, -1)
flyprops = {'markersize':0.01}
colorprops = None
ax.boxplot(data, flierprops=flyprops, showcaps=False,
boxprops=colorprops, whiskerprops={'color': 'tab:blue'},
patch_artist=True)
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.set_ylim(0, 0.11)
ax.set_xticks(range(0, len(x)+1, 20))
ax.set_xticklabels(label)
plt.show()
Here is an example of setting the tick marks:
import matplotlib.pyplot as plt
import numpy as np
data=np.random.rand(3,50)
fig = plt.figure(figsize=(10,5))
ax = fig.add_axes([0,0,1,1])
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.boxplot(data,
showcaps=False,
whiskerprops={'color' : 'tab:blue'},
patch_artist=True
)
plt.xticks([10, 20, 30, 40, 50],
["10", "20", "30", "40", "50"])
EDIT:
You can also avoid messing with strings and set the marks like this:
N=50
plt.xticks(np.linspace(0, N, num=6), np.linspace(0, N, num=6))
See here and this example.
Simple ticks can be acheived in a similar mannar as here (note data as transposed numpy array) using
import numpy as np
import matplotlib.pyplot as plt
data = np.array([ np.random.rand(100) for i in range(3) ]).T
plt.boxplot(data)
plt.xticks([1, 2, 3], ['mon', 'tue', 'wed'])

Rotating Matplotlib tick labels causes weird spacing issues

I have the following plot:
I would like to make the x-axis ticks more readable by rotating the ticks by ~40 degrees. So from:
plt.xticks(list(range(0, width)), list(df_100.columns), rotation='90', fontsize=16)
To:
plt.xticks(list(range(0, width)), list(df_100.columns), rotation='40', fontsize=16)
When I do this, though, I get some crazy spacing issues:
(ignore the change in color...)
What's causing this problem? How can I fix it? Here's a minimum working example:
import matplotlib.pyplot as plt
import numpy as np
# Z is your data set
N = 100
height = df_100.shape[0]
width = df_100.shape[1]
# Z = np.random.random((100, 29))
# G is a NxNx3 matrix
G = np.zeros((height,width,3))
# Where we set the RGB for each pixel
G[Z>0.5] = [1, 1, 1]
G[Z<0.5] = [0.25, 0.25, 0.25]
fig, ax = plt.subplots(figsize=(20, 10))
ax.imshow(G, interpolation='none')
ax.set_aspect('auto')
ax.grid(None)
ax.xaxis.tick_top()
plt.xticks(list(range(0, width)), list(df_100.columns), rotation='45', fontsize=16)
plt.yticks([0, df_100.shape[0] - 1], [1, df_100.shape[0]], fontsize=20)
plt.tight_layout()
plt.show()
If xticklabels are of the same length, you won't have this kind of problem. But given different length of labels, you can encounter this kind of problem. Because the default rotation is from the center of the xlabel string. So you can try to set the rotation anchor properly from
['right', 'center', 'left'].
ha = 'left' # or 'right'. Experiment with it.
ax.set_xticks(x) # set tick location
ax.set_xticklabels(xlabels, rotation=40, ha=ha) # rotate the labels with proper anchoring.

Adjust width of box in boxplot in python matplotlib

I would like to reduce the width of the boxes in the boxplot below. Here's my code, but it is not working:
bp = plt.boxplot(boxes, widths = 0.6, patch_artist = True)
From the documentation there is a widths option:
widths : array-like, default = 0.5
Either a scalar or a vector and sets the width of each box. The default is 0.5, or 0.15*(distance between extreme positions) if that is smaller.
Here is an example:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(937)
data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75)
labels = list('ABCD')
fs = 10 # fontsize
plt.boxplot(data, labels=labels, showfliers=False, widths=(1, 0.5, 1.2, 0.1))
plt.show()
Try working via the axes and see if it works:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.boxplot(boxes, widths = 0.6, patch_artist = True)

Change colour scale increments in Python

I have created a frequency time spectrogram plot seen below.
I want to edit the colour scale so that the higher frequencies shown from 20 seconds are more prominent. I think having smaller increments at the lower end of the colour scale (blues) would achieve this but am not sure how to do it. Any help would be great!
Here is what I have so far:
import numpy as np
import matplotlib.pyplot as plt
from obspy.core import read
from obspy.signal.tf_misfit import cwt
import pylab
tr = read("whole.sac")[0]
npts = tr.stats.npts
dt = tr.stats.delta
t = np.linspace(0, dt * npts, npts)
f_min = 1
f_max = 10
scalogram = cwt(tr.data, dt, 8, f_min, f_max)
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.7, 0.60])
ax2 = fig.add_axes([0.1, 0.75, 0.75, 0.2])
ax3 = fig.add_axes([0.83, 0.1, 0.03, 0.6])
img = ax1.imshow(np.abs(scalogram)[-1::-1], extent=[t[0], t[-1], f_min, f_max],
aspect='auto', interpolation="nearest")
ax1.set_xlabel("Time after %s [s]" % tr.stats.starttime)
ax1.set_ylabel("Frequency [Hz]")
ax1.set_yscale('linear')
ax2.plot(t, tr.data, 'k')
pylab.xlim([30,72])
fig.colorbar(img, cax=ax3)
plt.show()
You could try other colormaps or make you own according to this recipe.
Or you may want to filter the data to set all values above a given threshold (e.g. 60) to the threshold value. This would use the entire range of the colormap on the range of interest. You can easily use np.clip() to do this.
So...
np.abs(scalogram)[-1::-1]
becomes
np.clip(np.abs(scalogram)[-1::-1], 0, 100)
to clip between 0 and 100.

matplotlib scatter_hist with stepfilled histtype in histogram

I modified scatter_hist.py example found here to have two data sets to be plotted.
I'd like to have histograms with "stepfilled" type, but somehow if I set the type "stepfilled" the Y-axis histogram (orientation = "horizontal") is not working.
Is there any other way to do the histogram to look like "stepfilled"-style or am I doing something wrong?
Here is my code with histtype = "bar" to show the idea what I try to do. Change it to
histtype="stepfilled"
to get strange histogram:
import numpy as np
import matplotlib.pyplot as plt
# the random data
x = np.random.randn(1000)
y = np.random.randn(1000)
x_vals = [x]
y_vals = [y]
x_vals.append( np.random.randn( 300 ) )
y_vals.append( np.random.randn( 300 ) )
fig = plt.figure(1, figsize=(5.5,5.5))
from mpl_toolkits.axes_grid1 import make_axes_locatable
colour_LUT = ['#0000FF',
'#00FF00']
# the scatter plot:
xymax = np.max(np.fabs(x))
colors = []
axScatter = plt.subplot(111)
for i in range( len(x_vals ) ):
colour = colour_LUT[i]
xymax = np.max( [np.max(np.fabs(x)), np.max(np.fabs(y)), xymax ] )
axScatter.scatter( x_vals[i], y_vals[i], color = colour )
colors.append(colour)
axScatter.set_aspect(1.)
# create new axes on the right and on the top of the current axes
# The first argument of the new_vertical(new_horizontal) method is
# the height (width) of the axes to be created in inches.
divider = make_axes_locatable(axScatter)
axHistx = divider.append_axes("top", 1.2, pad=0.1, sharex=axScatter)
axHisty = divider.append_axes("right", 1.2, pad=0.1, sharey=axScatter)
# make some labels invisible
plt.setp(axHistx.get_xticklabels() + axHisty.get_yticklabels(),
visible=False)
# now determine nice limits by hand:
binwidth = 0.25
lim = ( int(xymax/binwidth) + 1) * binwidth
bins = np.arange(-lim, lim + binwidth, binwidth)
histtype = "bar"
axHistx.hist(x_vals, bins=bins, histtype= histtype, color=colors)
axHisty.hist(y_vals, bins=bins, orientation='horizontal',histtype= histtype, color=colors)
# the xaxis of axHistx and yaxis of axHisty are shared with axScatter,
# thus there is no need to manually adjust the xlim and ylim of these
# axis.
#axHistx.axis["bottom"].major_ticklabels.set_visible(False)
for tl in axHistx.get_xticklabels():
tl.set_visible(False)
axHistx.set_yticks([0, 50, 100])
#axHisty.axis["left"].major_ticklabels.set_visible(False)
for tl in axHisty.get_yticklabels():
tl.set_visible(False)
axHisty.set_xticks([0, 50, 100])
plt.draw()
plt.show()
Thank You for help!
Edit:
Here is the images which I receive in windows environment with matplotlib 1.0.0.
With histtype="bar" I have this:
and with histtype="stepfilled" I have this:
The documentation only mentions special cases for multiple data when using 'bar' and 'barstacked', which I would assume means that this isn't properly implemented for the other two types. Changing your code to add multiple histograms instead of just one worked for me:
histtype = "stepfilled"
for i in xrange(len(x_vals)):
axHistx.hist(x_vals[i], bins=bins, histtype= histtype, color=colors[i])
axHisty.hist(y_vals[i], bins=bins, orientation='horizontal',histtype= histtype, color=colors[i])

Categories