Why minor ticks disappear on pylab subplots - python-2.7

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.

Related

How to fully delete plots from subplot and properly resize?

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

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.

Set location of xticks in a matplotlib scatter plot

I am trying to create a scatter plot of measurements where the x labels are WIFI channels. By default matplotlib is spacing the labels in proportion to their numerical value. However, I would like them to be spaced uniformly over the scatter plot. Is that possible?
This is basically what my plot code currently looks like:
- where chanPoints is a list of frequencies and measurements is a list of measurements.
plt.scatter(chanPoints,measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.xticks(Tchan,rotation = 90)
plt.title('EVM for 5G Channels by Site')
plt.show()
Numpy
You may use numpy to create an array which maps the unique items within chanPoints to numbers 0,1,2.... You can then give each of those numbers the corresponding label.
import matplotlib.pyplot as plt
import numpy as np
chanPoints = [4980, 4920,4920,5500,4980,5500,4980, 5500, 4920]
measurements = [5,6,4,3,5,8,4,6,3]
unique, index = np.unique(chanPoints, return_inverse=True)
plt.scatter(index, measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.xticks(range(len(unique)), unique)
plt.title('EVM for 5G Channels by Site')
plt.show()
Seaborn
If you're happy to use seaborn, this can save a lot of manual work. Seaborn is specialized for plotting categorical data. The chanPoints would be interpreted as categories on the x axis, and have the same spacing between them, if you were e.g. using a swarmplot. If several points would then overlap, they are plotted next to each other, which may be an advantage as it allows to see the number of measurement for that channel.
import matplotlib.pyplot as plt
import seaborn.apionly as sns
chanPoints = [4980, 4920,4920,5500,4980,5500,4980, 5500, 4920]
measurements = [5,6,4,3,5,8,4,6,3]
sns.swarmplot(chanPoints, measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.title('EVM for 5G Channels by Site')
plt.show()
Replace chanPoints with an index.
index = numpy.searchsorted(Tchan, chanPoints)
plt.scatter(index, measurements)
Then build your xticks with the corresponding lables.
ticks = range(len(Tchan))
plt.xticks(ticks, labels=Tchan, rotation = 90)

Remove weekends in finance plots with volume overlay [duplicate]

I've been having some difficulty with Matplotlib's finance charting. It seems like their candlestick charts work best with daily data, and I am having a hard time making them work with intraday (every 5 minutes, between 9:30 and 4 pm) data.
I have pasted sample data in pastebin. The top is what I get from the database, and the bottom is tupled with the date formatted into an ordinal float for use in Matplotlib.
Link to sample data
When I draw my charts there are huge gaps in it, the axes suck, and the zoom is equally horrible. http://imgur.com/y7O8A
How do I make a nice readable graph out of this data? My ultimate goal is to get a chart that looks remotely like this:
http://i.imgur.com/EnrTW.jpg
The data points can be in various increments from 5 minutes to 30 minutes.
I have also made a Pandas dataframe of the data, but I am not sure if pandas has candlestick functionality.
If I understand well, one of your major concern is the gaps between the daily data.
To get rid of them, one method is to artificially 'evenly space' your data (but of course you will loose any temporal indication intra-day).
Anyways, doing this way, you will be able to obtain a chart that looks like the one you have proposed as an example.
The commented code and the resulting graph are below.
import numpy as np
import matplotlib.pyplot as plt
import datetime
from matplotlib.finance import candlestick
from matplotlib.dates import num2date
# data in a text file, 5 columns: time, opening, close, high, low
# note that I'm using the time you formated into an ordinal float
data = np.loadtxt('finance-data.txt', delimiter=',')
# determine number of days and create a list of those days
ndays = np.unique(np.trunc(data[:,0]), return_index=True)
xdays = []
for n in np.arange(len(ndays[0])):
xdays.append(datetime.date.isoformat(num2date(data[ndays[1],0][n])))
# creation of new data by replacing the time array with equally spaced values.
# this will allow to remove the gap between the days, when plotting the data
data2 = np.hstack([np.arange(data[:,0].size)[:, np.newaxis], data[:,1:]])
# plot the data
fig = plt.figure(figsize=(10, 5))
ax = fig.add_axes([0.1, 0.2, 0.85, 0.7])
# customization of the axis
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.tick_params(axis='both', direction='out', width=2, length=8,
labelsize=12, pad=8)
ax.spines['left'].set_linewidth(2)
ax.spines['bottom'].set_linewidth(2)
# set the ticks of the x axis only when starting a new day
ax.set_xticks(data2[ndays[1],0])
ax.set_xticklabels(xdays, rotation=45, horizontalalignment='right')
ax.set_ylabel('Quote ($)', size=20)
ax.set_ylim([177, 196])
candlestick(ax, data2, width=0.5, colorup='g', colordown='r')
plt.show()
I got tired of matplotlib's (and plotly's) bad performance and lack of such features you request, so implemented one of my own. Here's how that works:
import finplot as fplt
import yfinance
df = yfinance.download('AAPL')
fplt.candlestick_ochl(df[['Open', 'Close', 'High', 'Low']])
fplt.show()
Not only are days in which the exchange is closed left out automatically, but also has better performance and a nicer api. For something that more resembles what you're ultimately looking for:
import finplot as fplt
import yfinance
symbol = 'AAPL'
df = yfinance.download(symbol)
ax = fplt.create_plot(symbol)
fplt.candlestick_ochl(df[['Open', 'Close', 'High', 'Low']], ax=ax)
fplt.plot(df['Close'].rolling(200).mean(), ax=ax, legend='SMA 200')
fplt.plot(df['Close'].rolling(50).mean(), ax=ax, legend='SMA 50')
fplt.plot(df['Close'].rolling(20).mean(), ax=ax, legend='SMA 20')
fplt.volume_ocv(df[['Open', 'Close', 'Volume']], ax=ax.overlay())
fplt.show()

subplots only plotting 1 plot using pandas

I am trying to get two plots on one figure using matplotlib's subplots() command. I want the two plots to share an x-axis and have one legend for the whole plot. The code I have right now is:
observline = mlines.Line2D([], [], color=(1,0.502,0),\
markersize=15, label='Observed',linewidth=2)
wrfline=mlines.Line2D([], [], color='black',\
markersize=15, label='WRF',linewidth=2)
fig,axes=plt.subplots(2,1,sharex='col',figsize=(18,10))
df08.plot(ax=axes[0],linewidth=2, color=(1,0.502,0))\
.legend(handles=[observline,wrfline],loc='lower center', bbox_to_anchor=(0.9315, 0.9598),prop={'size':16})
axes[0].set_title('WRF Model Comparison Near %.2f,%.2f' %(lat,lon),fontsize=24)
axes[0].set_ylim(0,360)
axes[0].set_yticks(np.arange(0,361,60))
df18.plot(ax=axes[1],linewidth=2, color='black').legend_.remove()
plt.subplots_adjust(hspace=0)
axes[1].set_ylim(0,360)
axes[1].set_yticks(np.arange(0,361,60))
plt.ylabel('Wind Direction [Degrees]',fontsize=18,color='black')
axes[1].yaxis.set_label_coords(-0.05, 1)
plt.xlabel('Time',fontsize=18,color='black')
#plt.savefig(df8graphfile, dpi = 72)
plt.show()
and it produces four figures, each with two subplots. The top is always empty. The bottom is filled for three of them with my 2nd dataframe. The indices for each dataframe is a datetimeindex in the format YYYY-mm-DD HH:MM:SS. The data is values from 0-360 nearly randomly across the whole time series, which is for two months.
Here is an example of each figure produced: