Two subplots coming out too long (length) - python

I'm attempting to plot two bar charts using matplotlib.pyplot.subplots. I've created subplots within a function, but when I output the subplots they are too long in height and not long enough in width.
Here's the function that I wrote:
def corr_bar(data1, data2, method='pearson'):
# Basic configuration.
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 7))
ax1, ax2 = axes
corr_matrix1 = data1.corr(method=method)
corr_matrix2 = data2.corr(method=method)
cmap = cm.get_cmap('coolwarm')
major_ticks = np.arange(0, 1.1, 0.1)
minor_ticks = np.arange(0, 1.1, 0.05)
# Values for plotting.
x1 = corr_matrix1['price'].sort_values(ascending=False).index
x2 = corr_matrix2['price'].sort_values(ascending=False).index
values1 = corr_matrix1['price'].sort_values(ascending=False).values
values2 = corr_matrix2['price'].sort_values(ascending=False).values
im1 = ax1.bar(x1, values1, color=cmap(values1))
im2 = ax2.bar(x2, values2, color=cmap(values2))
# Formatting for plot 1.
ax1.set_yticks(major_ticks)
ax1.set_yticks(minor_ticks, minor=True)
plt.setp(ax1.get_xticklabels(), rotation=45, ha='right', rotation_mode='anchor')
ax1.grid(which='both')
ax1.grid(which='minor', alpha=0.4)
ax1.grid(which='major', alpha=0.7)
ax1.xaxis.grid(False)
# Formatting for plot 2.
ax2.set_yticks(major_ticks)
ax2.set_yticks(minor_ticks, minor=True)
plt.setp(ax2.get_xticklabels(), rotation=45, ha='right', rotation_mode='anchor')
ax2.grid(which='both')
ax2.grid(which='minor', alpha=0.4)
ax2.grid(which='major', alpha=0.7)
ax2.xaxis.grid(False)
fig.tight_layout()
plt.show()
This function (when run with two Pandas DataFrames) outputs an image like the following:
I purposely captured the blank right side of the image as well in an attempt to better depict my predicament. What I want is for the bar charts to be appropriately sized in height and width as to take up the entire space, rather than be elongated and pushed to the left.
I've tried to use the ax.set(aspect='equal') method but it "scrunches up" the bar chart. Would anybody happen to know what I could do to solve this issue?
Thank you.

When you define figsize=(7,7) you are setting the size of the entire figure and not the subplots. So your entire figure must be a square in this case. You should change it to figsize=(14,7) or use a number larger than 14 to get a little bit of extra space.

Related

Add a tick to top x axis

I do not really understand why this code is not working
fig, ax = plt.subplots()
ax.plot(Vg_vec, sigma_i)
ax.set_xlabel('$V_\mathrm{g}$ ($\mathrm{V}}$)')
ax.set_ylabel('$\sigma_\mathrm{i}$ ($\mathrm{C/m^2}$)')
peaks, _ = find_peaks(sigma_i, height=0.006415)
plt.axvline(Vg_vec[peaks], color='red')
ax2 = ax.twiny()
ax2.set_xticks(Vg_vec[peaks])
ax2.tick_params(axis='x', colors='red')
My result
There should be a red tick in the top x axis.
Thanks
the issue with your code is twofold:
By using twiny instead of secondary_axis, the upper x-axis will be different to the bottom one, and I assume you want them to be the same. That's extra work to fix, so I used secondary_axis in my example.
This is something I don't know why it happens, but it has happened to me before. When supplying the tick values, the first one is always "ignored", so you have to supply two or more values. I used 0, but you can use anything.
Here's my code:
fig, ax = plt.subplots()
ax.plot(Vg_vec, sigma_i)
ax.set_xlabel('$V_\mathrm{g}$ ($\mathrm{V}}$)')
ax.set_ylabel('$\sigma_\mathrm{i}$ ($\mathrm{C/m^2}$)')
peaks, _ = find_peaks(sigma_i, height=None)
plt.axvline(Vg_vec[peaks], color='red')
ax2 = ax.secondary_xaxis('top')
ax2.tick_params(axis='x', color='red')
ax2.set_xticks([0, *Vg_vec[peaks]], minor=False)
And the resulting plot:

How do I fix plt.subplots to bring the plots closer together?

I am plotting 27 maps, or 9 rows and 3 columns. I am using plt.subplots to plot them, but I am struggling to bring the plots closer together? I tried both:
plt.tight_layout()
fig.tight_layout()
But I keep getting this error anytime I add that in:
ValueError: zero-size array to reduction operation minimum which has no identity
This is my code so far with the plt.subplot and mapping, it appears to be working but the map layout is not very readable:
fig, axes = plt.subplots(nrows=9, ncols=3, figsize=(60,44), subplot_kw=dict(projection=ccrs.PlateCarree()))
for i,t,ax in zip(range(27),time_years, axes.ravel()):
ax.set_extent([-90, 10, 5, 85], crs=ccrs.PlateCarree())
x = ax.contourf(longitude,latitude,yearly_means[i],10, extend='both')
ax.add_feature(cfeature.LAND, zorder=100, edgecolor='k')
ax.coastlines()
gridlines = ax.gridlines(draw_labels=True)
gridlines.xlabels_top = False
gridlines.ylabels_right = False
ax.text(.5,-.11, 'Longitude' , va='bottom' , ha='center', rotation='horizontal', rotation_mode= 'anchor',transform=ax.transAxes)
ax.text(-.15, .5, 'Latitude' , va='bottom' , ha='center', rotation='vertical', rotation_mode= 'anchor',transform=ax.transAxes)
ax.set_title('extremes for %d' %t)
cbar = fig.colorbar(x, orientation='horizontal', ax = axes,fraction=.046, pad=0.04)
cbar.set_label('psu', labelpad=15, y=.5, rotation=0)
#plt.tight_layout()
plt.subplots_adjust(wspace=None, hspace=None) # THIS DOES NOT WORK, no change
plt.show()
I tried adding: plt.subplots_adjust to make the width between plots smaller, but there is no difference when I add that line.
How do I bring these plots closer together and make the figures bigger? Also the colorbar overlaps on the image, why might be that happening?
plt.tight_layoutdoesn't remove the padding between the plots automatically but rather fixes overlapping issues.
you can try the pad options described in plt.tight_layout documentation
what will probably work better/best is to use fig, ax = plt.subplots(9,3, figsize=(9,6), layout="compressed")
with emphasis on layout="compressed" which should help in your case of maps/ images layout=compressed
The first thing to try is plt.tight_layout() - it will automatically adjust paddings around subplots. Another thing to play with is figsize and its aspect ratio to make it consistent with your subplots alignment. In your case, the canvas is too wide for the subplots.

Python: Draw a second y-axis on pyplot figure

I am trying to create a subplot that consists of two figures. Each of the figures shows some data plotted vs a time axis. And for each figure I want to have two y axes corresponding to two different graphs shown within the same figure.
Let's start with the data corresponding to one of the y-axes. This data is the same for each of the two figures and is generated as follows (it is fairly ugly code and if you have any suggestions as on how to improve it, please let me know!):
pwm_len = len(Time)/6
pwm_max = 255
pwm_min = 150
pwm_mid = 200
pwm_zero = 0
pwm1 = np.repeat(pwm_max, pwm_len)
pwm2 = np.repeat(pwm_min, pwm_len)
pwm3 = np.repeat(pwm_max, pwm_len)
pwm4 = np.repeat(pwm_mid, pwm_len)
pwm5 = np.repeat(pwm_max, pwm_len)
pwm6 = np.repeat(pwm_zero, pwm_len)
pwm = pwm1 + pwm2 + pwm3 + pwm4 + pwm5 + pwm6
To create the figure, I am using the following code (please note that it is not working right now, due to some wrong usage of twinx() ):
fig, axs = plt.subplots(2, sharex=True, sharey=True)
plt.subplots_adjust(hspace=0.5)
axs_pwm = axs.twinx()
axs[0].plot(Time, velocity, 'b-')
axs_pwm[0].plot(Time, pwm, 'r-')
axs[0].set_ylabel('[mm/s]')
axs_pwm[0].set_ylabel('PWM')
axs[0].grid(True)
axs[1].plot(Time, velocity_filtered, 'b-')
axs_pwm[1].plot(Time, pwm, 'r-')
axs[1].set_ylabel('[mm/s]')
axs_pwm[1]-set_ylabel('PWM')
axs[1].grid(True)
plt.show()
apparently I am using the twinx() function in a wrong way. But what is a different way to draw the second y axis?
Extending upon ImportanceOfBeingErnest's's suggestion, you need the following:
Create the twin axis for each subplot using the index 0 and 1 while using twinx()
Use the respective twin axis' object to plot data and set y-axis labels
fig, axs = plt.subplots(2, sharex=True, sharey=True)
plt.subplots_adjust(hspace=0.5)
axs_pwm1 = axs[0].twinx() # Create twin axis for the first subplot
axs[0].plot(Time, velocity, 'b-')
axs_pwm1.plot(Time, pwm, 'r-')
axs[0].set_ylabel('[mm/s]')
axs_pwm1.set_ylabel('PWM')
axs[0].grid(True)
axs_pwm2 = axs[1].twinx() # Create twin axis for the second subplot
axs[1].plot(Time, velocity_filtered, 'b-')
axs_pwm2.plot(Time, pwm, 'r-')
axs[1].set_ylabel('[mm/s]')
axs_pwm2.set_ylabel('PWM')
axs[1].grid(True)
plt.show()
Or as suggested by #SpghttCd in the comments, you can predefine all the twin axis and then use index as
ax2 = [ax.twinx() for ax in axs]
ax2[0].plot(...)
ax2[1].plot(...)

Add second axis to polar plot

I try to plot two polar plots in one figure. See code below:
fig = super(PlotWindPowerDensity, self).get_figure()
rect = [0.1, 0.1, 0.8, 0.8]
ax = WindSpeedDirectionAxes(fig, rect)
self.values_dict = collections.OrderedDict(sorted(self.values_dict.items()))
values = self.values_dict.items()
di, wpd = zip(*values)
wpd = np.array(wpd).astype(np.double)
wpdmask = np.isfinite(wpd)
theta = self.radar_factory(int(len(wpd)))
# spider plot
ax.plot(theta[wpdmask], wpd[wpdmask], color = 'b', alpha = 0.5)
ax.fill(theta[wpdmask], wpd[wpdmask], facecolor = 'b', alpha = 0.5)
# bar plot
ax.plot_bar(table=self.table, sectors=self.sectors, speedbins=self.wpdbins, option='wind_power_density', colorfn=get_sequential_colors)
fig.add_axes(ax)
return fig
The length of the bar is the data base (how many sampling points for this sector). The colors of the bars show the frequency of certain value bins (eg. 2.5-5 m/s) in the correspondent sector (blue: low, red: high). The blue spider plot shows the mean value for each sector.
In the shown figure, the values of each plot are similar, but this is rare. I need to assign the second plot to another axis and show this axis in another direction.
EDIT:
After the nice answer of Joe, i get the result of the figure.
That's almost everything i wanted to achieve. But there are some points i wasn't able to figure out.
The plot is made for dynamicly changing data bases. Therefore i need a dynamic way to get the same location of the circles. Till now I solve it with:
start, end = ax2.get_ylim()
ax2.yaxis.set_ticks(np.arange(0, end, end / len(ax.yaxis.get_ticklocs())))
means: for second axis i alter the ticks in order to fit the ticklocs to the one's of first axis.
In most cases i get some decimal places, but i don't want that, because it corrupts the clearness of the plot. Is there a way to solve this problem more smartly?
The ytics (the radial one's) range from 0 to the next-to-last circle. How can i achieve that the values range from the first circle to the very last (the border)? The same like for the first axis.
So, as I understand it, you want to display data with very different magnitudes on the same polar plot. Basically you're asking how to do something similar to twinx for polar axes.
As an example to illustrate the problem, it would be nice to display the green series on the plot below at a different scale than the blue series, while keeping them on the same polar axes for easy comparison.:
import numpy as np
import matplotlib.pyplot as plt
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax.fill_between(theta, r1, color='green', alpha=0.5)
plt.show()
However, ax.twinx() doesn't work for polar plots.
It is possible to work around this, but it's not very straight-forward. Here's an example:
import numpy as np
import matplotlib.pyplot as plt
def main():
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax2 = polar_twin(ax)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax2.fill_between(theta, r1, color='green', alpha=0.5)
plt.show()
def polar_twin(ax):
ax2 = ax.figure.add_axes(ax.get_position(), projection='polar',
label='twin', frameon=False,
theta_direction=ax.get_theta_direction(),
theta_offset=ax.get_theta_offset())
ax2.xaxis.set_visible(False)
# There should be a method for this, but there isn't... Pull request?
ax2._r_label_position._t = (22.5 + 180, 0.0)
ax2._r_label_position.invalidate()
# Ensure that original axes tick labels are on top of plots in twinned axes
for label in ax.get_yticklabels():
ax.figure.texts.append(label)
return ax2
main()
That does what we want, but it looks fairly bad at first. One improvement would be to the tick labels to correspond to what we're plotting:
plt.setp(ax2.get_yticklabels(), color='darkgreen')
plt.setp(ax.get_yticklabels(), color='darkblue')
However, we still have the double-grids, which are rather confusing. One easy way around this is to manually set the r-limits (and/or r-ticks) such that the grids will fall on top of each other. Alternately, you could write a custom locator to do this automatically. Let's stick with the simple approach here:
ax.set_rlim([0, 5])
ax2.set_rlim([0, 1])
Caveat: Because shared axes don't work for polar plots, the implmentation I have above will have problems with anything that changes the position of the original axes. For example, adding a colorbar to the figure will cause all sorts of problems. It's possible to work around this, but I've left that part out. If you need it, let me know, and I'll add an example.
At any rate, here's the full, stand-alone code to generate the final figure:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1977)
def main():
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax2 = polar_twin(ax)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax2.fill_between(theta, r1, color='green', alpha=0.5)
plt.setp(ax2.get_yticklabels(), color='darkgreen')
plt.setp(ax.get_yticklabels(), color='darkblue')
ax.set_ylim([0, 5])
ax2.set_ylim([0, 1])
plt.show()
def polar_twin(ax):
ax2 = ax.figure.add_axes(ax.get_position(), projection='polar',
label='twin', frameon=False,
theta_direction=ax.get_theta_direction(),
theta_offset=ax.get_theta_offset())
ax2.xaxis.set_visible(False)
# There should be a method for this, but there isn't... Pull request?
ax2._r_label_position._t = (22.5 + 180, 0.0)
ax2._r_label_position.invalidate()
# Bit of a hack to ensure that the original axes tick labels are on top of
# whatever is plotted in the twinned axes. Tick labels will be drawn twice.
for label in ax.get_yticklabels():
ax.figure.texts.append(label)
return ax2
if __name__ == '__main__':
main()
Just to add onto #JoeKington 's (great) answer, I found that the "hack to ensure that the original axes tick labels are on top of whatever is plotted in the twinned axes" didn't work for me so as an alternative I've used:
from matplotlib.ticker import MaxNLocator
#Match the tick point locations by setting the same number of ticks in the
# 2nd axis as the first
ax2.yaxis.set_major_locator(MaxNLocator(nbins=len(ax1.get_yticks())))
#Set the last tick as the plot limit
ax2.set_ylim(0, ax2.get_yticks()[-1])
#Remove the tick label at zero
ax2.yaxis.get_major_ticks()[0].label1.set_visible(False)

Matplotlib: multiple y axes, grid lines applied to both?

I've got a Matplotlib graph with two y axes, created like:
ax1 = fig.add_subplot(111)
ax1.grid(True, color='gray')
ax1.plot(xdata, ydata1, 'b', linewidth=0.5)
ax2 = ax1.twinx()
ax2.plot(xdata, ydata2, 'g', linewidth=0.5)
I need grid lines but I want them to apply to both y axes not just the left one. The scales of each axes will differ. What I get is grid lines that only match the values on the left hand axes.
Can Matplotlib figure this out for me or do I have to do it myself?
Edit: Don't think I was completely clear, I want the major ticks on both y axes to be aligned but the scales and ranges are potentially quite different making it tricky to setup the mins and maxes manually to achieve this. I am hoping that matplotlib will be able to do this "tricky" bit for me. Thanks
EDIT
Consider this simple example:
from pylab import *
# some random values
xdata = arange(0.0, 2.0, 0.01)
ydata1 = sin(2*pi*xdata)
ydata2 = 5*cos(2*pi*xdata) + randn(len(xdata))
# number of ticks on the y-axis
numSteps = 9;
# plot
figure()
subplot(121)
plot(xdata, ydata1, 'b')
yticks( linspace(ylim()[0],ylim()[1],numSteps) )
grid()
subplot(122)
plot(xdata, ydata2, 'g')
yticks( linspace(ylim()[0],ylim()[1],numSteps) )
grid()
show()

Categories