Margin between plot and scale in matplotlib [duplicate] - matplotlib

Look at the chart below. In this chart, I want to draw to circle with the centre coordinates at (45, 0). But, as you see, bottom limit of chart is zero and half of my circle is not shown. So, I need to extend this chart to the bottom a little bit. "Margins" method of axes (ax.margins()) doesn't work, since bottom line of this chart is zero and zero multiplied by any number is equal to zero.
Note: Please, do not post replies like ax.set_ylim(...) or ax.set_yticks(...). I am looking for a general solution for this like problems.
Code
import matplotlib.pyplot as plt
import numpy as np
values = np.array([0.00388632352941, 0.00375827941176, 0.00355033823529, 0.00328273529412, 0.00294677941176, 0.00272142647059, 0.00246463235294, 0.00227766176471, 0.00213151470588, 0.00202594117647, 0.00183544117647, 0.00162102941177, 0.00148372058824, 0.00128380882353, 0.00112252941176, 0.000931544117647, 0.000786573529412, 0.000658220588235, 0.000584485294118, 0.000524044117647, 0.000562485294118, 0.000716441176471, 0.000872617647059, 0.00109039705882, 0.00124138235294, 0.00136894117647, 0.00143985294118, 0.00134760294118, 0.00121794117647, 0.00112772058824, 0.00109435294118, 0.00102432352941, 0.00101069117647, 0.00102417647059, 0.00104895588235, 0.00101776470588, 0.00101494117647, 0.000885558823529, 0.00078075, 0.000752647058824, 0.000667691176471, 0.000593220588236, 0.000658647058823, 0.000742117647059, 0.000651470588235, 0.000604647058824, 0.000584573529412, 0.00049530882353, 0.000281235294118, 0.000355029411765])
fig, ax = plt.subplots()
ax.bar(np.arange(values.shape[0]), values)
plt.show()

In the question there is no circle, so my answer doesn't include any circle either.
In order to have some space around the data in the plot, you can use ax.margins(y=ymargin), where ymargin is the percentage of space to add on each side of the data. I.e. if data goes from 0 to 1 and you add ymargin = 0.1 of margin, the ylimits will be (-0.1, 1.1). (This is independent on whether or not one limit would be zero or not.)
Now, by default this does not work for a bar plot, as it would in the general case be undesireable to have the bars start somewhere in the air, as opposed to the bottom axis. This behaviour is steered using a flag called use_sticky_edges. We can set this flag to False to get back the behaviour of margins being applied to both ends of the axis. For taking effect, we need to call ax.autoscale_view afterwards.
import matplotlib.pyplot as plt
import numpy as np
values = np.array([3.89, 3.76, 3.55, 3.28, 2.95, 2.72, 2.46, 2.28, 2.13, 2.03, 1.84,
1.62, 1.48, 1.28, 1.12, 0.93, 0.79, 0.66, 0.58, 0.52, 0.56, 0.72,
0.87, 1.09, 1.24, 1.37, 1.44, 1.35, 1.22, 1.13, 1.09, 1.02, 1.01,
1.02, 1.05, 1.02, 1.01, 0.89, 0.78, 0.75, 0.67, 0.59, 0.66, 0.74,
0.65, 0.60, 0.58, 0.50, 0.28, 0.36])
fig, ax = plt.subplots()
ax.bar(np.arange(values.shape[0]), values)
ax.margins(y=0.3)
ax.use_sticky_edges = False
ax.autoscale_view(scaley=True)
plt.show()

Related

Add xticks within margins

I am trying create two plots that should have the same width when displayed in a row-wise fashion. I have noticed that adding xticks followed by tight_layout makes the plot (pcolormesh) decrease in width from increasing the x-margins. I would like to move the ticks in such a way that the x-margins are eliminated and both pcolormesh have the same width.
I have the following example:
import numpy as np, matplotlib.pyplot as plt
def plot(ticks=True):
fig, ax = plt.subplots(figsize=(6,1))
np.random.seed(42)
a = np.random.randn(1,6)
ax.pcolormesh(a)
plt.gca().invert_yaxis()
ax.xaxis.tick_top()
ax.set(yticklabels=[])
ax.tick_params(left=False, length=5)
if ticks:
ax.set_xticks([0, 3, 6])
else:
plt.axis('off')
plt.tight_layout()
plt.savefig(f'plot-{ticks}.png', dpi=300, bbox_inches='tight', pad_inches=0.0)
I get the following plots when running with and without the ticks:
The x-margins are not the same, which is more noticeable when increasing the font-size. How do I move the 3 label to right and the 6 label to the left to make both images have the same x-margins (0 margin)?
EDIT
Using the suggestion from Align specific x labels differently to each other? we have
import numpy as np, matplotlib.pyplot as plt
plt.rcParams.update({'font.size': 17})
fig, ax = plt.subplots(figsize=(6,1))
np.random.seed(42)
a = np.random.randn(1,6)
ax.pcolormesh(a)
plt.gca().invert_yaxis()
ax.xaxis.tick_top()
ax.set(yticklabels=[])
ax.tick_params(left=False, length=5)
# get list of x tick objects
xtick_objects = ax.xaxis.get_major_ticks()
xtick_objects[0].label1.set_horizontalalignment('left') # left align first tick
xtick_objects[-1].label1.set_horizontalalignment('right') # right align last tick
ax.set_xticks([0, 3, 6])
plt.tight_layout()
# plt.savefig(f'plot.png', dpi=300, bbox_inches='tight', pad_inches=0.0
plt.show()
which does not seem to change the alignment.

Positioning of log-polar plot axis labels in matplotlib

I have trouble positioning the axis labels in this plot. I like to position the top label so that the pipe aligns with the grid, and the right and left labels so that they do not touch the plot.
I tried
ax.tick_params(axis='both', which='both', pad=15)
but it has no effect. Also,
rcParams
seems to conflict with the log-scale of the plot. As a hack, I tried whitespaces but these are stripped of LaTeX at the beginning and end of each word. Finally, I tried invisible Unicode signs but only got matplotlib to crash.
Your help is greatly appreciated!
log-polar plot
import numpy
from matplotlib import pyplot as plt
ax = plt.subplot(111, polar=True)
plt.rc('text', usetex=True)
plt.rc('font', family='serif')
ax.set_xticks(numpy.linspace(0, 2 * 3.14, 4, endpoint=False))
ax.set_theta_direction(-1)
ax.set_theta_offset(3.14 / 2.0)
plt.ylim(0, 2.5)
ax.set_xticklabels([
r'$100,000\,{\rm yr}|10\,{\rm yr}$',
r'$100\,{\rm yr}$',
r'$1000\,{\rm yr}$',
r'$10,000\,{\rm yr}$'],
fontsize=16)
plt.show()
The trick can be to align the textlabels differently according to their position. That is, the left label should be aligned right, the right label left.
For the top label, it makes sense to split it up, such that the first label is left aligned and the last label is right aligned.
import numpy as np
from matplotlib import pyplot as plt
ax = plt.subplot(111, polar=True)
ticks = np.linspace(0, 2 * np.pi, 5, endpoint=True)
ax.set_xticks(ticks)
ax.set_theta_direction(-1)
ax.set_theta_offset(3.14 / 2.0)
plt.ylim(0, 2.5)
ax.set_xticklabels([
r'$10\,{\rm yr}$',
r'$100\,{\rm yr}$',
r'$1000\,{\rm yr}$',
r'$10,000\,{\rm yr}$', r"$100,000\,{\rm yr}|$"],
fontsize=16)
aligns = ["left", "left", "center", "right", "right"]
for tick, align in zip(ax.get_xticklabels(), aligns):
tick.set_ha(align)
plt.show()

Get desired wspace and subplots appropriately sized?

I'm trying to make a plot with one panel up top (colspan = 2) and two plots below, with a controlled amount of space between them. I'd like the bounds of the plots to be in alignment. Here's what I'm starting with:
import cartopy
from matplotlib import pyplot
from matplotlib.gridspec import GridSpec
gs = GridSpec(2, 2, height_ratios=[2, 1], hspace=0, wspace=0)
ax0 = pyplot.subplot(gs[0, :], projection=cartopy.crs.LambertConformal())
ax0.add_feature(cartopy.feature.COASTLINE)
ax0.set_extent([-120, -75, 20, 52], cartopy.crs.Geodetic())
ax1 = pyplot.subplot(gs[1, 0], projection=cartopy.crs.LambertConformal())
ax1.add_feature(cartopy.feature.COASTLINE)
ax1.set_extent([-90, -75, 20, 30], cartopy.crs.Geodetic())
ax2 = pyplot.subplot(gs[1, 1], projection=cartopy.crs.LambertConformal())
ax2.add_feature(cartopy.feature.COASTLINE)
ax2.set_extent([-90, -75, 20, 30], cartopy.crs.Geodetic())
pyplot.show()
First problem is that the wspace=0 parameter doesn't take.
Second problem is (at least this is my guess on how to proceed) calculating a height ratio that will make the width of the upper subplot equal the combined width of the lower subplots (plus any wspace).

Logarithmic multi-sequenz plot with equal bar widths

I have something like
import matplotlib.pyplot as plt
import numpy as np
a=[0.05, 0.1, 0.2, 1, 2, 3]
plt.hist((a*2, a*3), bins=[0, 0.1, 1, 10])
plt.gca().set_xscale("symlog", linthreshx=0.1)
plt.show()
which gives me the following plot:
As one can see, the bar width is not equal. In the linear part (from 0 to 0.1), everything is find, but after this, the bar width is still in linear scale, while the axis is in logarithmic scale, giving me uneven widths for bars and spaces in between (the tick is not in the middle of the bars).
Is there any way to correct this?
Inspired by https://stackoverflow.com/a/30555229/635387 I came up with the following solution:
import matplotlib.pyplot as plt
import numpy as np
d=[0.05, 0.1, 0.2, 1, 2, 3]
def LogHistPlot(data, bins):
totalWidth=0.8
colors=("b", "r", "g")
for i, d in enumerate(data):
heights = np.histogram(d, bins)[0]
width=1/len(data)*totalWidth
left=np.array(range(len(heights))) + i*width
plt.bar(left, heights, width, color=colors[i], label=i)
plt.xticks(range(len(bins)), bins)
plt.legend(loc='best')
LogHistPlot((d*2, d*3, d*4), [0, 0.1, 1, 10])
plt.show()
Which produces this plot:
The basic idea is to drop the plt.hist function, compute the histogram by numpy and plot it with plt.bar. Than, you can easily use a linear x-axis, which makes the bar width calculation trivial. Lastly, the ticks are replaced by the bin edges, resulting in the logarithmic scale. And you don't even have to deal with the symlog linear/logarithmic botchery anymore.
You could use histtype='stepfilled' if you are okay with a plot where the data sets are plotted one behind the other. Of course, you'll need to carefully choose colors with alpha values, so that all your data can still be seen...
a = [0.05, 0.1, 0.2, 1, 2, 3] * 2
b = [0.05, 0.05, 0.05, 0.15, 0.15, 2]
colors = [(0.2, 0.2, 0.9, 0.5), (0.9, 0.2, 0.2, 0.5)] # RGBA tuples
plt.hist((a, b), bins=[0, 0.1, 1, 10], histtype='stepfilled', color=colors)
plt.gca().set_xscale("symlog", linthreshx=0.1)
plt.show()
I've changed your data slightly for a better illustration. This gives me:
For some reason the overlap color seems to be going wrong (matplotlib 1.3.1 with Python 3.4.0; Is this a bug?), but it's one possible solution/alternative to your problem.
Okay, I found out the real problem: when you create the histogram with those bin-edge settings, the histogram creates bars which have equal size, and equal outside-spacing on the non-log scale.
To demonstrate, here's a zoomed-in version of the plot in the question, but in non-log scale:
Notice how the first two bars are centered around (0 + 0.1) / 2 = 0.05, with a gap of 0.1 / 10 = 0.01 at the edges, while the next two bars are centered around (0.1 + 1.0) / 2 = 0.55, with a gap of 1.1 / 10 = 0.11 at either edge.
When converting things to log scale, bar widths and edge widths all go for a huge toss. This is compounded further by the fact that you have a linear scale from 0 to 0.1, after which things become log-scale.
I know no way of fixing this, other than to do everything manually. I've used the geometric means of the bin-edges in order to compute what the bar edges and bar widths should be. Note that this piece of code will work only for two datasets. If you have more datasets, you'll need to have some function that fills in the bin-edges with a geometric series appropriately.
import numpy as np
import matplotlib.pyplot as plt
def geometric_means(a):
"""Return pairwise geometric means of adjacent elements."""
return np.sqrt(a[1:] * a[:-1])
a = [0.05, 0.1, 0.2, 1, 2, 3] * 2
b = [0.05, 0.1, 0.2, 1, 2, 3] * 3
# Find frequencies
bins = np.array([0, 0.1, 1, 10])
a_hist = np.histogram(a, bins=bins)[0]
b_hist = np.histogram(b, bins=bins)[0]
# Find log-scale mid-points for bar-edges
mid_vals = np.hstack((np.array([0.05,]), geometric_means(bins[1:])))
# Compute bar left-edges, and bar widths
a_x = np.empty(mid_vals.size * 2)
a_x = bins[:-1]
a_widths = mid_vals - bins[:-1]
b_x = np.empty(mid_vals.size * 2)
b_x = mid_vals
b_widths = bins[1:] - mid_vals
plt.bar(a_x, a_hist, width=a_widths, color='b')
plt.bar(b_x, b_hist, width=b_widths, color='g')
plt.gca().set_xscale("symlog", linthreshx=0.1)
plt.show()
And the final result:
Sorry, but the neat gaps between the bars get killed. Again, this can be fixed by doing the appropriate geometric interpolation, so that everything is linear on log-scale.
Just in case someone stumbles upon this problem:
This solution looks much more like the way it should be
plotting a histogram on a Log scale with Matplotlib

How do I extend the margin at the bottom of a figure in Matplotlib?

The following screenshot shows my x-axis.
I added some labels and rotated them by 90 degrees in order to better read them. However, pyplot truncates the bottom such that I'm not able to completely read the labels.
How do I extend the bottom margin in order to see the complete labels?
Two retroactive ways:
fig, ax = plt.subplots()
# ...
fig.tight_layout()
Or
fig.subplots_adjust(bottom=0.2) # or whatever
Here's a subplots_adjust example: http://matplotlib.org/examples/pylab_examples/subplots_adjust.html
(but I prefer tight_layout)
A quick one-line solution that has worked for me is to use pyplot's auto tight_layout method directly, available in Matplotlib v1.1 onwards:
plt.tight_layout()
This can be invoked immediately before you show the plot (plt.show()), but after your manipulations on the axes (e.g. ticklabel rotations, etc).
This convenience method avoids manipulating individual figures of subplots.
Where plt is the standard pyplot from:
import matplotlib.pyplot as plt
fig.savefig('name.png', bbox_inches='tight')
works best for me, since it doesn't reduce the plot size compared to
fig.tight_layout()
Subplot-adjust did not work for me, since the whole figure would just resize with the labels still out of bounds.
A workaround I found was to keep the y-axis always a certain margin over the highest or minimum y-values:
x1,x2,y1,y2 = plt.axis()
plt.axis((x1,x2,y1 - 100 ,y2 + 100))
fig, ax = plt.subplots(tight_layout=True)
This is rather complicated, but it gives a general and neat solution.
import numpy as np
value1 = 3
xvalues = [0, 1, 2, 3, 4]
line1 = [2.0, 3.0, 2.0, 5.0, 4.0]
stdev1 = [0.1, 0.2, 0.1, 0.4, 0.3]
line2 = [1.7, 3.1, 2.5, 4.8, 4.2]
stdev2 = [0.12, 0.18, 0.12, 0.3, 0.35]
max_times = [max(line1+stdev1),max(line2+stdev2)]
min_times = [min(line1+stdev1),min(line2+stdev2)]
font_size = 25
max_total = max(max_times)
min_total = min(min_times)
max_minus_min = max_total - min_total
step_size = max_minus_min/10
head_space = (step_size*3)
plt.figure(figsize=(15, 15))
plt.errorbar(xvalues, line1, yerr=stdev1, fmt='', color='b')
plt.errorbar(xvalues, line2, yerr=stdev2, fmt='', color='r')
plt.xlabel("xvalues", fontsize=font_size)
plt.ylabel("lines 1 and 2 Test "+str(value1), fontsize=font_size)
plt.title("Let's leave space for the legend Experiment"+ str(value1), fontsize=font_size)
plt.legend(("Line1", "Line2"), loc="upper left", fontsize=font_size)
plt.tick_params(labelsize=font_size)
plt.yticks(np.arange(min_total, max_total+head_space, step=step_size) )
plt.grid()
plt.tight_layout()
Result: