How to fully delete plots from subplot and properly resize? - python-2.7

I am trying to create a corner plot for an upcoming paper, but I'm running into difficulty. I am creating an N x N array of subplots (currently, N = 6) and then deleting a bit over half of them. The issue is that the figure doesn't seem to resize itself after I delete the extraneous subplots, so when I later add a legend using a dummy subplot, it exists in the area where a full row and column of deleted subplots were, thus enlarging the figure. I've been working on this for several hours now and haven't found a solution. Here is the MWE:
import matplotlib.pyplot as plt
%matplotlib notebook
n_char = 8
# Set up the main figure.
fig, ax = plt.subplots(n_char, n_char, figsize=(n_char, n_char))
# Get rid of the axis labels unless it's on the left-most column or bottom-most row.
for i in range(0, n_char):
# For each row, loop over each column.
for j in range(0, n_char):
# If the plot isn't in the bottom-most row, get rid of the x-axis tick labels.
if i != n_char - 1:
ax[i, j].set_xticklabels([])
# If the plot isn't in the left-most column, get rid of the y-axis tick labels.
if j != 0:
ax[i, j].set_yticklabels([])
# Remove the plots that are repetitive or boring (plotting against the same characteristic).
for i in range(0, n_char):
# For each row, loop over each column.
for j in range(0, n_char):
# Delete the offending axes.
if j >= i:
ax[i, j].remove()
# Set the spacing between the plots to a much smaller value.
fig.subplots_adjust(hspace=0.00, wspace=0.00)
# Create a big plot for the legend. Have the frame hidden.
fig.add_subplot(111, frameon=False, xticks=[], yticks=[], xticklabels=[], yticklabels=[])
# Create some dummy data to serve as the source of the legend.
plt.scatter([10], [10], color="k", s=5, zorder=2, label="Targets")
# Set the x-axis limits such that the dummy data point is invisible.
fig.gca().set_xlim(-1, 1)
# Add the legend to the plot. Have it located in the upper right.
plt.legend(scatterpoints=1, loc="upper right", fontsize=5)
# Save the final plot.
fig.savefig("./../Code Output/Other Plots/Corner_Plot_Test.png", bbox_inches="tight", dpi=500)
I have looked at many different questions here on Stack Overflow. The two most promising candidates was this one, but I found the solution wasn't quite workable due to the large number of plots (and, to be frank, I didn't fully understand the solution). I thought that the first answer in this one might also work, as I thought it was a sizing issue (i.e. the figure wasn't resizing, so creating a new subplot was creating one the size of the original figure), but all it did was resize the entire figure, so that didn't work either.
To help, I will also include an image. I took the output of the code above and edited it to show what I want:
I should add that if I don't add a subplot, the output is as I expected (i.e. it's the proper size), so the issue comes in when adding the subplot, i.e. the line fig.add_subplot(111, frameon=False, xticks=[], yticks=[], xticklabels=[], yticklabels=[]).

The use of GridSpec may help.
GridSpec is used to specify array of axes to plot. You can set widths for columns and heights for rows as ratios in the option. The unneeded row should have very small height ratio, while unneeded column very small width ratio.
Here is the runnable code and output plot:-
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
#import numpy as np
fig = plt.figure(figsize=(8, 8))
nn = 6
# will create gridspec of 6 rows, 6 columns
# 1st row will occupy v small heights
# last column will occupy v small widths
sm = 0.01 # the v small width/height
wh = (1.-sm)/(nn-1.) # useful width/height
gs = gridspec.GridSpec(nn, nn, width_ratios=[*[wh]*(nn-1), sm], \
height_ratios= [sm, *[wh]*(nn-1)])
cols, rows = nn, nn
ax = [[0 for i in range(cols)] for j in range(rows)]
for ea in range(nn):
for eb in range(nn):
ax[ea][eb] = fig.add_subplot(gs[ea, eb])
ax[ea][eb].set_xticklabels([])
ax[ea][eb].set_yticklabels([])
if eb>=ea:
ax[ea][eb].remove()
# plot data on some axes
# note that axes on the first row (index=0) are gone
ax[2][0].plot([2,5,3,7])
ax[4][2].plot([2,3,7])
# make legend in upper-right axes (GridSpec's first row, last column)
# first index: 0
# second index: nn-1
rx, cx = 0, nn-1
ax[rx][cx] = fig.add_subplot(gs[rx,cx])
hdl = ax[rx][cx].scatter([10], [10], color="k", s=5, zorder=2, label="Targets")
ax[rx][cx].set_axis_off()
#ax[rx][cx].set_visible(True) # already True
ax[rx][cx].set_xticklabels([])
ax[rx][cx].set_yticklabels([])
# plot legend
plt.legend(bbox_to_anchor=(1.0, 1.0), loc='upper right', borderaxespad=0.)
fig.subplots_adjust(hspace=0.00, wspace=0.00)
plt.show

Related

How can I remove the negative sign from y tick labels in matplotlib.pyplot figure?

I am using the matplotlib.pylot module to generate thousands of figures that all deal with a value called "Total Vertical Depth(TVD)". The data that these values come from are all negative numbers but the industry standard is to display them as positive (I.E. distance from zero / absolute value). My y axis is used to display the numbers and of course uses the actual value (negative) to label the axis ticks. I do not want to change the values, but am wondering how to access the text elements and just remove the negative symbols from each value(shown in red circles on the image).
Several iterations of code after diving into the matplotlib documentation has gotten me to the following code, but I am still getting an error.
locs, labels = plt.yticks()
newLabels = []
for lbl in labels:
newLabels.append((lbl[0], lbl[1], str(float(str(lbl[2])) * -1)))
plt.yticks(locs, newLabels)
It appears that some of the strings in the "labels" list are empty and therefore the cast isn't working correctly, but I don't understand how it has any empty values if the yticks() method is retrieving the current tick configuration.
#SiHA points out that if we change the data then the order of labels on the y-axis will be reversed. So we can use a ticker formatter to just change the labels without changing the data as shown in the example below:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
#ticker.FuncFormatter
def major_formatter(x, pos):
label = str(-x) if x < 0 else str(x)
return label
y = np.linspace(-3000,-1000,2001)
fig, ax = plt.subplots()
ax.plot(y)
ax.yaxis.set_major_formatter(major_formatter)
plt.show()
This gives me the following plot, notice the order of y-axis labels.
Edit:
based on the Amit's great answer, here's the solution if you want to edit the data instead of the tick formatter:
import matplotlib.pyplot as plt
import numpy as np
y = np.linspace(-3000,-1000,2001)
fig, ax = plt.subplots()
ax.plot(-y) # invert y-values of the data
ax.invert_yaxis() # invert the axis so that larger values are displayed at the bottom
plt.show()

matplotlib legend bars and texts are not aligned

I use matplotlib in python2.7 to get a figure with multiple trajectories. I am getting this figure as the output of my code:
However, as you see the title of each trajectory is not aligned with the corresponding color line. I played with the options of legend, but did not figure out the solution.
Do you have any idea?
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
liste = range(1,13)
labels = ['3-3','3-4','3-5','3-6','4-3','4-4',
'4-5','4-6','5-3','5-4','5-5','5-6',
'6-3','6-4','6-5','6-6']
plt.figure(1)
plt.subplot(2,1,1)
for cnt, j in enumerate(labels):
a1 = range(100) # iter
a2 = np.random.randint(10,size=[100]) # dnn
a3 = np.ones((100)) # optimal
if cnt == 0:
plt.plot(a1,a3,label='BS', color='blue')
plt.plot(a1, np.divide(a3,a3), color='blue')
title_font = {'size':'11', 'color':'black', 'weight':'normal',
'verticalalignment':'bottom'}
plt.plot(a1,a2,label=j, color='red')
plt.xlabel('episode')
plt.ylabel('cost')
plt.grid(True)
lgd=plt.legend(bbox_to_anchor=(0., 1.0, 1.0, 0.102),
loc=3, ncol=len(liste), mode="expand", borderaxespad=0.)
plt.show()
The legend handles and labels do not fit into the legend box. You may see that when removing the mode="expand" label.
To get the legend entries take less space, use the handlelength, handletextpad and the columnspacing and play with the parameters until they fit. If that would still not be the case, using less columns is necessary. Set the ncols to a lower number.
E.g. with the following
lgd=plt.legend(bbox_to_anchor=(0., 1.02, 1.0, 0.102),
handlelength=0.9, handletextpad=0.3,columnspacing=0.7,
loc=3, ncol=9, mode="expand", borderaxespad=0.)
one would get an image with all legend entries fitting

Python Matplotlib creating a custom colour scale

I have created a map of precipitation levels in a region based on precipitation data from NetCDF files. I would like to add a custom scale such that if precipitation is less than 800mm it would be one colour, 800-1000mm another, etc. Similar to the map found here: http://www.metmalawi.com/climate/climate.php
At the moment I am using a gradient scale but it isn't showing the detail I need. This is the code for the plot at the moment (where 'Average' is my data that I have already formatted).
#load color palette
colourA = mpl_cm.get_cmap('BuPu')
#plot map with physical features
ax = plt.axes(projection=cartopy.crs.PlateCarree())
ax.add_feature(cartopy.feature.COASTLINE)
ax.add_feature(cartopy.feature.BORDERS)
ax.add_feature(cartopy.feature.LAKES, alpha=0.5)
ax.add_feature(cartopy.feature.RIVERS)
#set map boundary
ax.set_extent([32.5, 36., -9, -17])
#set axis tick marks
ax.set_xticks([33, 34, 35])
ax.set_yticks([-10, -12, -14, -16])
lon_formatter = LongitudeFormatter(zero_direction_label=True)
lat_formatter = LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
#plot data and set colour range
plot = iplt.contourf(Average, cmap=colourA, levels=np.arange(0,15500,500), extend='both')
#add colour bar index and a label
plt.colorbar(plot, label='mm per year')
#give map a title
plt.title('Pr 1990-2008 - Average_ERAINT ', fontsize=10)
#save the image of the graph and include full legend
plt.savefig('ERAINT_Average_Pr_MAP_Annual', bbox_inches='tight')
plt.show()
Anyone know how I can do this?
Thank you!
This is a matplotlib question disguised as an Iris question as the issue has appeared via Iris plotting routines, but to answer this we need only a couple of matplotlib commands. As such, I'm basing this answer on this matplotlib gallery example. These are levels (containing values for the upper bound of each contour) and colors (specifying the colours to shade each contour). It's best if there are the same number of levels and colours.
To demonstrate this, I put the following example together. Given that there's no sample data provided, I made my own trigonometric data. The levels are based on the trigonometric data values, so do not reflect the levels required in the question, but could be changed to the original levels. The colours used are the hex values of the levels specified by image in the link in the question.
The code:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-25, 25)
y = np.arange(-20, 20)
x2d, y2d = np.meshgrid(x, y)
vals = (3 * np.cos(x2d)) + (2 * np.sin(y2d))
colours = ['#bf8046', '#df9f24', '#e0de30', '#c1de2d', '#1ebf82',
'#23de27', '#1dbe20', '#11807f', '#24607f', '#22427e']
levels = range(-5, 6)
plt.contourf(vals, levels=levels, colors=colours)
plt.colorbar()
plt.show()
The produced image:
Colours could also be selected from a colormap (one way of doing this is shown in this StackOverflow answer). There are also other ways, including in the matplotlib gallery example linked above. Given, though, that the sample map linked in the question had specific colours I chose to use those colours directly.

Updating several text objects in matplotlib simultaneously

I'm trying to animate several text objects in matplotlib at the same time. I have a 3-D numpy array (arrow_data) of data which stores the index of a unicode arrow which I need to plot (text for all unicode arrows are stored in a list arrows). The first and second dimension of this np.array indicate the location on a grid where this arrow needs to be plotted and the 3rd dimension is the 'time' dimension over which I need to update the plot (ie the arrows change through time over the 3d dimension of the array).
Below is the code I have for animating these arrows through 'time' using a loop, but I don't see how I can make a collection of text objects, as I can with other matplotlib functions like scatter, and then collectively (and efficiently) update the set_text property of each of them. The loop over the 'time' variable is fine but I'd prefer to not use a loop over the grid of text objects if possible.
Any thoughts on how to do this without the double nested loop?
Thanks.
from matplotlib import pyplot as plt
import numpy as np
import time
import matplotlib
matplotlib.interactive(True)
arrows = [u'\u2190', u'\u2196', u'\u2191', u'\u2197', u'\u2192', u'\u2198', u'\u2193', u'\u2199']
rest_time = 0.25
steps = 20
fig, ax = plt.subplots(1, 1)
ax.plot([0,4],[0,4], c = 'white')
objs = [[[] for i in range(4)] for j in range(4)]
for i in range(4):
for j in range(4):
objs[i][j] = ax.text(i+0.5,j+0.5, arrows[i], ha = 'center', va = 'center')
fig.canvas.draw()
arrow_data = np.random.randint(0,len(arrows), (4,4,steps))
for t in range(arrow_data.shape[2]):
for i in range(4):
for j in range(4):
objs[i][j].set_text(arrows[arrow_data[i,j,t]])
fig.canvas.draw()
time.sleep(rest_time)

Why minor ticks disappear on pylab subplots

Begin Edit
After initial post I continued playing with my code. In my subplots I am making four plots of the same data set, with each subplot having a different time range. However, if I give each subplot the SAME time range then the minor ticks do not disappear. This may be why Deditos could not reproduce my issue.
That being said, if I manually create each subplot (with each having a different x-axis range), set the minor tick locations, THEN set each subplot's xrange I do not see the minor ticks disappear until I set ax3's (i.e. the last subplot) range.
It seems the issue is in having different x-axis ranges. Bizarre, I would think that by setting each axis' properties individually they would not all be tied together.
End Edit
I am creating one figure that has four sub-plots, all of which are time series. I have the xaxis major ticks spaced every four hours, and want minor ticks every hour. When I set the minor ticks for the first subplot (called ax1) the minor ticks appear, as they should. However, when I set the minor ticks in ax2 they show up in ax2, but the minor ticks in ax1 disappear. This repeats for ax3, and ax4. So, in the end I have only minor xaxis ticks in the fourth subplot. I had the same problem with the yaxis, but resolved this issue using yaxis.set_minor_locator(MultipleLocator(5)) for each axis (see below). However, MultipleLocator does not seem to work for time series data. Does anyone know how I can keep my minor xaxis ticks?
from pylab import *
from matplotlib.ticker import AutoMinorLocator, MultipleLocator
minor = AutoMinorLocator()
# Start plotting
fig = figure( figsize=(22,11) )
ax1 = fig.add_subplot(221) # 8-August 2011
ax2 = fig.add_subplot(222) # 9-August 2011
ax3 = fig.add_subplot(223) # 23-August 2011
ax4 = fig.add_subplot(224) # 24-August 2011
# This is repeated for ax2, ax3, and ax4, yielding a 2x2 grid of subplots.
# Plot 8-August 2011 data
ax1.plot(tpan.index,tpan.no2,'.-',markersize=10)
ax1.errorbar(tacam.index,tacam.no2,yerr=0.15,fmt='r.',markersize=12)
# Format plots
suptitle('Pandora/ACAM NO$_2$ Comparison', fontsize=22)
# Define xtick locations/string labels
xtickloc = [dt.datetime.combine(dates[0],dt.time())+dt.timedelta(hours=h) for h in range(0,25,4)]
xticklab = [dt.datetime.strftime(h,'%H:%M') for h in xtickloc]
ax1.set_xlabel('Hour of Day (UTC, EST+5)',fontsize=14)
ax1.set_ylabel('NO$_2$ Column Density (molec*cm$^{-2}$ E16)',fontsize=14)
ax1.xaxis.set_ticks(xtickloc)
ax1.yaxis.set_ticks(linspace(0,1.5,7))
ax1.xaxis.set_minor_locator(minor)
ax1.yaxis.set_minor_locator(MultipleLocator(5))
ax1.set_xticklabels(xticklab,fontsize=12,fontweight='bold')
ax1.set_yticklabels(linspace(0,1.5,7),fontsize=12,fontweight='bold')
ax1.axis( (dates[0],dates[0]+dt.timedelta(days=1),-0.05,1.5),fontsize=6,fontweight='bold')
ax1.tick_params(which='both',width=2,top='on')
ax1.tick_params(which='major',length=7)
ax1.tick_params(which='minor',length=4)
ax1.grid(linestyle='-',which='major',linewidth=1)
ax1.set_title('08-August 2011',fontsize=16)
ax1.legend( ('Pandora VCD','ACAM dSCD'),loc=2,ncol=2)
I was facing the same problem. I think what you need to do is:
ax1.xaxis.set_minor_locator(AutoMinorLocator())
instead of
ax1.xaxis.set_minor_locator(minor)
You are passing the same object to each of your axes. This object's contents are modified when you plot on ax4 based on the range on that subplot. Hope it helps.