ATM 240 - Python, Fall 2022 "Cheat Sheet"

Cheat sheet

This page is http://www.atmos.albany.edu/facstaff/rfovell/ATM240/csheet.html.
Class web page is This page is http://www.atmos.albany.edu/facstaff/rfovell/ATM240/.

This is a living document and will be revised often.

Python version

import sys
print("You are using Python {}.{}.".format(sys.version_info.major, sys.version_info.minor))

Openers [I copy and paste these lines into new notebooks]

%matplotlib inline
 
import matplotlib.pyplot as plt
import matplotlib.cm as cm 
from matplotlib.cm import get_cmap
from matplotlib.colors import from_levels_and_colors
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.ticker import MultipleLocator
import numpy as np 
from numpy import genfromtxt 
import math

from pyproj import Proj 

import xarray as xr 

import cartopy.crs as ccrs 
import cartopy.feature as cfeat
import cartopy.geodesic
import shapely

import datetime
from datetime import datetime

import pandas as pd

# MetPy
from siphon.simplewebservice.wyoming import WyomingUpperAir   # upper air data repository
from metpy.units import units
import metpy.plots as plots
from metpy.plots import Hodograph
import metpy.calc as mpcalc
from mpl_toolkits.axes_grid1.inset_locator import inset_axes  # for hodograph inset

# for WRF
from netCDF4 import Dataset
from wrf import (getvar, to_np, get_cartopy, latlon_coords, vertcross, smooth2d,
                 cartopy_xlim, cartopy_ylim, interpline, CoordPair, destagger, ALL_TIMES)

# smoothing
import scipy.ndimage # for contour smoothing
from scipy.ndimage import gaussian_filter

from scipy import stats
from scipy.stats import norm, invgauss, skew, kurtosis
from scipy.interpolate import interpn # histogram
from scipy.interpolate import splrep, splev # smoothing normal curve


SMALL_SIZE = 12
MEDIUM_SIZE = 14
BIGGER_SIZE = 16

plt.rc('font', size=SMALL_SIZE)          # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE)     # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE)    # fontsize of the x and y labels
plt.rc('xtick', labelsize=MEDIUM_SIZE)    # fontsize of the tick labels
plt.rc('ytick', labelsize=MEDIUM_SIZE)    # fontsize of the tick labels
plt.rc('legend', fontsize=MEDIUM_SIZE)    # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE)  # fontsize of the figure title



Some list stuff

• given a list 'mylist'

# forcibly replace oneth mylist element with new string
mylist[1] = 'goodbye'           

# insert 'everyone' after the twoth (i.e., third) element
mylist.insert(2,'everyone')

# remove the list element matching '2' .. NOT the twoth element!!
mylist.remove(2)

# append provided value to the end of the list
mylist.append(4.5)

• type of variable 'x'

type(x)
• create a list of zeroes
n=20
listofzeros = [0] * n
print(listofzeros)

Numpy and array stuff

• creating an empty array of specified length

# "empty" arrays can contain unwanted junk.  Use zeros instead.
cat = np.empty(8)

• creating an array with arange
a = np.arange(6)          # creates 1D array with values 0,1,2,3,4, and 5
b = np.arange(0,100,20)   # creates 1D array with values 0,20,40,60,80
• defining an array inline with specific values
x = np.array([1.,2.,4.,5.,5.])
y = np.array([0.,2.,4.5,5.,6.])
qi_levels = np.array([0.1,0.2,0.4,0.8,1.6,3.2,6.4,12.8]) # g/kg
• defining an array from a list, various array types
mylist = [[2,3,-5],[21,-2,1]]

a = np.array(mylist,dtype='d')  # double precision floating point
b = np.array(mylist,dtype='f')  # single precision floating point
c = np.array(mylist,dtype='i')  # integer (long int no longer exists)
• defining an array with Numpy functions

a = np.zeros((3,2),dtype='d')   # a 3x2 array filled with double precision zeroes

• shape of an array
print(" shape of array a = ",np.shape(a))
• Slicing and indexing an array
print(x[3:5])     # prints out array entries 4 and 5, but only those two
print(x[3:8])     # does not matter if ask for more entries than exist
print(x[-1])      # print last entry in array
print(x[-2])      # print second to last entry in array 
• Concatenating arrays
# key point is '(( ))'
all_year=np.concatenate((np.array(old_year),np.array(recent_year)))
year=np.concatenate((old_year,recent_year))
• Mean, max, etc..
mean = np.mean(vals)  # mean 
max = np.max(vals)    # max 
min = np.min(vals)    # min 
std = np.std(vals)    # standard deviation  
• Sorting with argsort
# smallest to largest
idx = yyy.argsort()
yyy = yyy[idx]

# largest to smallest
idx = (-yyy).argsort()
yyy = yyy[idx]
• rounding
lfr=np.around(lf,2)    # rounds numpy array 'lf' to 2 decimal places
• genfromtxt
ff = genfromtxt('/spare11/atm240/hrrr_swex_area_data.txt',skip_header=1) 
lat=ff[:,0]   # latitude  
lon=ff[:,1]   # longitude  

• Make 1D array 3D (clone)

# make pressure 3D array pres
# https://stackoverflow.com/questions/60044087/expand-and-copy-1d-numpy-array-to-3d

pres = np.tile(p_levels[:, np.newaxis, np.newaxis], (1, len(lat), len(lon)))
print(np.shape(pres))
• Mask, masking
compos_reflec=np.where(compos_reflec>5,compos_reflec,np.nan) # replaces reflec < 5 w/ nan so won't plot

• datetime, datetime64

# datetime variables look like 2022-01-01 00:00:00
# numpy datetime64 variables look like 2022-01-01T00:00:00.000000
# to convert a datetime to datetime64:

xday=np.datetime64(datetime(int(year),1,1,0,0))

Some Pandas stuff

# read a CSV dataset and let Pandas parse the date-like variables
df = pd.read_csv('WHO_flu_data_22Nov2020.csv', low_memory=False, parse_dates=True)

# read in the date-like variable, then convert it to datetime
date_temp = df['Start date']
date = pd.to_datetime(date_temp)

# read in space-delimited dataset without header, so provide column names separately
df = pd.read_csv('all_deviations_nysm_july2017.txt',delim_whitespace=True) # if NO HEADER need header=None
df.columns =['station', 'index', 'dwind', 'mean'] 

# Godawful way of reading in pandas month,day,year variables and constructing a datetime variable
year=df['year'].to_numpy().astype(int)
month=df['month'].to_numpy().astype(int)
day=df['day'].to_numpy().astype(int)

date=[]
for i in range(len(year)):
    date.append(datetime(year[i],month[i],day[i]))
#
print(date)

Metpy

# u = west-east wind speed
# v = north-south wind speed

fwdir = mpcalc.wind_direction(u,v) # calc wind direction wdir

# example with attaching units
# this gives temperature irma_tmp units of Kelvin, relative humidity irma_rh units of percent
# function result would be Celsius.  The final piece converts to Kelvin

irma_tdew = mpcalc.dewpoint_from_relative_humidity(irma_tmp * units.K, irma_rh * units.percent).to(units('kelvin'))

Loops and conditionals

• if, elif, else

if(mon == 'FEB'):
	monx = 2
elif(mon == 'MAR'):
	monx = 3
else:
	print(" not february or march")
if(fhr >= start and fhr <= end>):
    [do something]
#
• subsetting data examples
# two conditions
ww = np.copy(ogust)
zz = np.copy(LT)

# or
w = ww[(zzz <= 6) | (zzz >= 18)]
z = zz[(zzz <= 6) | (zzz >= 18)]
# and
w = ww[(zzz >= 6) & (zzz <= 18)]
z = zz[(zzz >= 6) & (zzz <= 18)]
• for loop
for i in range(5):      # this operates on 0, 1, 2, 3, and 4, but not 5
    print(i)
#
• while loop
a = 1
while a < 10:
    print(a)
    a = a + 1

Matplotlib

• Basic plots

fig = plt.figure(figsize=(8,8))                              # plot width, plot height
plt.plot(x,y,c='lightgrey',linewidth=2,label='mylabel')      # line plot
plt.scatter(x,y,c='k',edgecolors='None',s=20)                # scatterplot
plt.bar(x,y,color='lightgrey',linewidth=2,label='mylabel')   # bar chart
plt.legend(loc='upper left')                                 # legend
plt.grid()                                                   # puts a grid on the plot
plt.grid(axis='both', which='both')   # vert grid too        # on multi-y plots, have to do it before cloning x-axis
plt.errorbar(theta_mean-theta2_mean,z_mean,xerr=theta_diff_std,c='grey',linewidth=0.5,zorder=1)
• Contour plots
levels=np.arange(968.,1044.,4.)                              # max level not included!                           
cs = ax.contour(lon,lat,slp,colors='k', levels=levels, linewidths=1, linestyles=':',
                transform=ccrs.PlateCarree())                # transform presumed map-projected data
fmt = '%r '                                                  # format for contour labels
fmt = '%1.1f'												 # float, one decimal place
ax.clabel(cs, cs.levels[::2], inline=True, fmt=fmt, fontsize=10)  # [::2] only every other contour
• Color fill plots
cmap = plt.cm.seismic                                        # color table
norm = plt.Normalize(-5.,5)                                  # values bounds
cm = ax.pcolormesh(lon,lat,snowd,shading='auto',cmap=cmap, norm=norm, 
                   transform=ccrs.PlateCarree())             # transform presumed map-projected data
plt.colorbar(cm, orientation="vertical",shrink=0.5, label='label')  # add colorbar (label optional)

# for orientation="horizontal", use pad=0.01 to reduce gap between plot bottom and colorbar

#####################################################################################
# putting a color bar on only ONE plot without changing plot size
# used in plot_HRRR_NAM_OBS_maxgusts_BOULDER_V2.ipynb
#####################################################################################

# first panel
ax = fig.add_subplot(1,2,1,projection=proj) 

colors = ['blue', 'white', 'red']
cmap = LinearSegmentedColormap.from_list('name', colors)
norm=plt.Normalize(0,50)
cm = ax.pcolormesh(lon_hrrr,lat_hrrr,maxgust_hrrr,shading='auto',alpha=0.5,cmap=cmap, norm=norm, transform=ccrs.PlateCarree())

# second panel, using same cmap and norm
ax = fig.add_subplot(1,2,2,projection=proj) 
cm = ax.pcolormesh(lon_nam,lat_nam,maxgust_nam,shading='auto',alpha=0.5,cmap=cmap, norm=norm, transform=ccrs.PlateCarree())

# now add the colorbar to a NEW axis

cb_ax = fig.add_axes([.91,.124,.02,.754]) # third number controls WIDTH of colorbar
fig.colorbar(cm,orientation='vertical',cax=cb_ax,label="maximum gust (m/s)")#,fraction=0.1)
• Colormaps
# colormap
cmap = ListedColormap(['green','green','green','green','green','green','brown','brown','green','yellow','yellow','yellow','lightgreen','red','yellow','white','white',
         'blue','blue','blue','blue'])
# reverses the colormap
cmap_r = ListedColormap(cmap.colors[::-1])
cm = ax.pcolormesh(lonwrf,latwrf,greenfrac[3,:,:],shading='flat',norm=norm,cmap=cmap_r, transform=ccrs.PlateCarree())
• Force plots to be square
# this bit of magic forces the plot to be square, appropriate because we have a square domain and no map projection
plt.gca().set_aspect('equal', adjustable='box')

# but this works too
# make sure plot is square - appropriate as we have square domain and no map projection
plt.axis('square')
• Adding a 1:1 correspondence line
# dashed 1:1 line.  I know the plot extent is 0-30 in x and y directions
plt.plot([0, 40], [0, 40], c='lightgrey', lw=2, linestyle='dashed')
# fancier version from Matt Seymour
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
 
fig = plt.figure(figsize=(10,10))
ax = plt.axes()
 
# Create linspace for color filling
x = np.linspace(-100, 100, 100)
 
# Fill color above/below y=x line
plt.fill_between(x, x, 100, facecolor='r', alpha=0.07, zorder=0)
plt.fill_between(x, x, -100, facecolor='b', alpha=0.07, zorder=0)
 
# Plot dashed lines every 2 [units]
for k in range(-10,10,2):
    plt.plot(x, x+k, c='0.6', ls='--', lw=1, zorder=1)
 
# Create thick dashed lines at x=0 and y=0
ax.axvline(0, color='k', ls='--', lw=1.5, zorder=2)
ax.axhline(0, color='k', ls='--', lw=1.5, zorder=2)
 
# Set axis limits, labels, and tick labels
plt.xlim(-10,10)
plt.ylim(-10,10)
plt.xlabel('X Variable', fontsize=14)
plt.ylabel('Y Variable', fontsize=14)
plt.xticks(np.arange(-10,10,2), fontsize=12)
plt.yticks(np.arange(-10,10,2), fontsize=12)
 
plt.title('Sample Scatterplot Background', fontsize=16)


# Barbs with specified spacing (q), color (cmap), range (norm), with C = windspeed, and linewidth adj
q=4
uunow=uu10[timex-1,:,:]
vvnow=vv10[timex-1,:,:]
C=np.hypot(uunow,vvnow)
colors = ['blue', 'red']
norm=plt.Normalize(0,40)
cmap = LinearSegmentedColormap.from_list('name', colors)
plt.barbs(to_np(lonwrf[::q,::q]),to_np(latwrf[::q,::q]),to_np(uu10[timex-1,::q,::q]),
          to_np(vv10[timex-1,::q,::q]),C[::q,::q],cmap=cmap,norm=norm,lw=1.5,transform=ccrs.PlateCarree())


# x-axis labels for vertical cross-sections that would otherwise have meaningless labels. 
# labels are forced and so need to be checked for accuracy but avoids MultipleLocator base crap
# first get x dimensions of plot, and calc spacing between ticks (here 4)
xmin, xmax = ax1.get_xlim()
print(xmin,xmax)
plt.xlim(xmin,xmax)
xdif=(xmax-xmin)/4
print(" X ",xmax,xmin,xdif)

# this works for forcing desired x-axis labels
# I know the plot goes from -107 to -103 longitude for fixed latitude
# I know I want 5 labels and I know what xmin, xmax are from get_xlim
labels = ['-107','-106','-105','-104','-103']
xl = np.arange(xmin,xmax+xdif,xdif)
print(" XL ",xl)
plt.xticks(xl,labels)

• Labels
# controlling label sizes for entire notebook
SMALL_SIZE = 12
MEDIUM_SIZE = 14
BIGGER_SIZE = 16

plt.rc('font', size=SMALL_SIZE)          # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE)     # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE)    # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE)    # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE)  # fontsize of the figure title

#

plt.title('This is a test scatterplot')         # plot title
plt.xlabel('x',fontsize='medium')				# x axis label
plt.ylabel('y',fontsize='medium')				# y axis label
plt.xticks(np.arange(0,12,2),fontsize='small') 			# ticks 
plt.yticks(np.arange(0,12,2),fontsize='small') 			# ticks
plt.xlim([0,10])   						# limit range of x axis
plt.ylim([0,10])   						# limit range of y axis

plt.xticks([])                          # hides/suppresses x axis labels and ticks

# Annotating numbers/labels on plots

# gcount, cat, and gcc_3 are 1D arrays with number of items, x, and y axis values, respectively
# the multiplicative factors shift the labels
# the '%.0f' strips off decimal place
for i, label in enumerate(gcount):
    plt.annotate('%.0f'%label, (cat[i]*1.03, gcc_3[i]*1.05))

# Another example of annotating labels
# 'state' is a vector of labels w/ same length as x and y

plt.scatter(x,y,s=25,c='k')

for i,label in enumerate(state):
    plt.annotate(label, (x[i], y[i]),size=10,textcoords="offset points",xytext=(5,5)) # xytext pushes label up and to right a little



# control decimal places on x, y axis labels

from matplotlib.ticker import FormatStrFormatter
ax.yaxis.set_major_formatter(FormatStrFormatter('%.1f')) # one decimal place for y-axis

# control tick mark interval

from matplotlib.ticker import MultipleLocator
plt.xlim(datemin,datemax)
ax.xaxis.set_major_locator(MultipleLocator(10)) # Change major ticks to show every 10.

# this might work better for controlling spacing of date labels
spacing=2
for label in ax.xaxis.get_ticklabels()[::spacing]:
    label.set_visible(False)

# title for a multi-panel plot

plt.suptitle(" this is my title ")

# this can control space between top of plot and suptitle title. But it shifts figtext locations too

fig.subplots_adjust(top=0.95)

# make the title BOLD font

plt.title(" WHO Influenza data for the United States 2019-2020",fontweight='bold')

# add text on or near plot,  Coordinates are in range [0,1]
plt.figtext(0.735,0.84,'Text to be added',size=12)

# this version makes font italic
# adding ha='left' does left justify for horizontal arrangement (ha)
# adding loc='left' starts title at left edge of plot
plt.figtext(0.135,0.85,'Data from https://who.int/toolkits/flunet',size=12,fontstyle='italic')

# adding text including floating point variable information, controlling number of decimals printed
plt.figtext(0.28,0.84,f' CAPE {cape:.1f}',size=12)  

# printing a datetime object as a string, cutting out the ugly fractions of second, and adding a bounding box
plt.figtext(0.24,0.15,'Plot valid '+ str(timenow.values)[0:19],size=12,
    bbox=dict(facecolor='white', edgecolor='black', boxstyle='round,pad=1'))

# transform=ax.transAxes sets location of figtext in frame of the individual frame of multiplot figure [axis called 'ax']
plt.figtext(0.97,0.09,str(z0min)+' <= LU <= '+str(z0thresh),size=12,ha='right',transform=ax.transAxes)

# draw a vertical line at x=0 with color lightgrey
plt.axvline(x=0, lw=2,c='lightgrey')

# draw a horizontal line at y=0 with color lightgrey
plt.axhline(y=0, lw=2,c='lightgrey')

# save a figure, and cut out some whitespace
# facecolor='white', transparent=False  removes transparency
plt.savefig("xplot.png", bbox_inches = 'tight',dpi=300,facecolor='white', transparent=False)
• Date axes / Date axis
datemin=datetime(2019,1,1)
datemax=datetime(2020,11,16)
ax1.xaxis_date()
plt.xlim(datemin,datemax) 


datemin=datetime(2019,4,1,0)
datemax=datetime(2019,5,1,0)
plt.xlim(datemin,datemax)
# Change major ticks to show every 10.
from matplotlib.ticker import MultipleLocator
ax.xaxis.set_major_locator(MultipleLocator(10))
• Manipulating axes

ax2.invert_yaxis()   # inverts y-axis
plt.subplots_adjust(hspace=0.00)  # squeeze out vertical space between plots

• Wind direction on second axis
# the twinx command clones the ax axis object as ax2, so our new plot shares the same x-axis
ax2 = ax.twinx()

# now we plot wind direction, and ask for a different, more reasonable y-axis plot limits
# this plot shows up on the secondary y-axis at right
plt.scatter(date,MTIC1_wdir,c='red',s=7)    # those wind direction dots were too large so we're downsizing
plt.scatter(date1,MOIC1_wdir,c='blue',s=7)    # those wind direction dots were too large so we're downsizing
plt.ylim([0,360])
• Colors

List of named colors

• Linear regression

a, b = np.polyfit(x, y, deg=1)       # fits the linear regression (slope = a, intercept = b)
correlation = np.corrcoef(x,y)[0,1]  # fetches off-diagonal of 2x2 correlation matrix
rsq = correlation*correlation        # computes the R-squared 
f = a*x + b                          # computes the predicted values
plt.scatter(x,y)
plt.plot(x,f,c='red')

# alternative
from sklearn.linear_model import LinearRegression
plt.scatter(x,y)
model = LinearRegression()
model.fit(x.reshape(-1, 1), y)

x_new = np.linspace(np.min(x), np.max(x), 100)   # for least squares line
y_new = model.predict(x_new[:, np.newaxis])
plt.plot(x_new,y_new,c='orange',lw=2)

# np.polyfit wants 2 1-D vectors, so can use np.ravel to flatten 2D arrays
# x and y start out (1059, 1799)
x=np.ravel(wspd[mlevel-1,:,:].values-wspd_hrrr_drift[mlevel-1,:,:])
y=np.ravel(wspd_hrrr[mlevel-1,:,:]-wspd_hrrr_drift[mlevel-1,:,:])
# x and y end up (1905141,)
• 1D histograms (plt.hist)

x = np.copy(GF) # 2D array
x = np.ravel(x) # make it 1D from 2D
print(" shape of x ",np.shape(x))

specified_bins=np.arange(1.0,10,0.1)
specified_midpoints=np.arange(1.1,10,0.1) # another try
print(len(specified_bins))
print(len(specified_midpoints))

sigma = 3 # smoothing

n, bins, patches = plt.hist(x, bins=specified_bins, facecolor='blue', edgecolor='k', alpha=0.5,label="Forecasts",zorder=1)
print(" max n fwind ",np.max(n)," max fwind ",np.max(fwind),len(x))
n = gaussian_filter(n, sigma)
plt.plot(specified_midpoints[5:],n[5:],c='blue',lw=3)

• 2D histograms (np.histogram2d) for scatter density plots


# hfxland, luland are 2D arrays (249x249)
# first, xform into 1D arrays for histogram

xx=np.ravel(hfxland)
yy=np.ravel(luland)
#print(np.shape(xx),np.shape(yy))

# set bins for xx (=qq) and yy (=rr)

qq=np.arange(-100,500,25) # hfx
rr=np.arange(1,17,1) # lu

# 2D histogram

data, x_e, y_e = np.histogram2d( xx, yy, bins = (qq,rr), density = False )
#print(" data ", data)
#print(" x_e ",x_e)
#print(" y_e ", y_e)

# creates the colors to be assigned to the points

z = scipy.interpolate.interpn( ( 0.5*(x_e[1:] + x_e[:-1]) , 0.5*(y_e[1:]+y_e[:-1]) ) , data , np.vstack([xx,yy]).T , method = "splinef2d", bounds_error = False)
#To be sure to plot all data
z[np.where(np.isnan(z))] = 0.0

# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
xx, yy, z = xx[idx], yy[idx], z[idx]
plt.scatter(xx,yy,c=z,edgecolors='None',s=20)



WRF

rainnc = getvar(ncfile1, "RAINNC",timeidx=ALL_TIMES) # get all times, otherwise just gets last one

latwrf = getvar(ncfile1, "XLAT",timeidx=-1) # just need one time
lonwrf = getvar(ncfile1, "XLONG",timeidx=-1)

wspd = getvar(ncfile1, "WSPD",timeidx=ALL_TIMES) # (times,lats,lons)
GF = np.max(wspd,axis=0)/np.mean(wspd,axis=0)    # (lats,lons)

# Read in a series of individual wrfout files, effectively combining them into single dataset
ncfile=[Dataset(location + "wrfout_d01_2007-09-21_06:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_07:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_08:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_09:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_10:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_11:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_12:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_13:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_14:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_15:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_16:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_17:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_18:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_19:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_20:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_21:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_22:00:00"),
        Dataset(location + "wrfout_d01_2007-09-21_23:00:00"),
        Dataset(location + "wrfout_d01_2007-09-22_00:00:00"),
        Dataset(location + "wrfout_d01_2007-09-22_01:00:00"),
        Dataset(location + "wrfout_d01_2007-09-22_02:00:00"),
        Dataset(location + "wrfout_d01_2007-09-22_03:00:00"),
        Dataset(location + "wrfout_d01_2007-09-22_04:00:00"),
        Dataset(location + "wrfout_d01_2007-09-22_05:00:00"),
        Dataset(location + "wrfout_d01_2007-09-22_06:00:00")]

Cartopy

lat_0 = 43
lon_0 = -75

fig = plt.figure(figsize=(10,8)) 
ax = fig.add_subplot(1,1,1,projection=ccrs.LambertConformal(central_latitude=lat_0, central_longitude=lon_0)) 
ax.set_extent([-80,-71.5,40.2,45.25])

ax.add_feature(cfeat.LAND)  				# adds colorshading [yellow] to land areas
ax.add_feature(cfeat.OCEAN) 				# adds colorshading [blue] to ocean areas
ax.add_feature(cfeat.COASTLINE,alpha=0.5)
ax.add_feature(cfeat.BORDERS,linestyle=':') # colon asks for dotted line
ax.add_feature(cfeat.LAKES,alpha=0.5) 		# alpha sets transparency level
ax.add_feature(cfeat.RIVERS)
ax.add_feature(cfeat.STATES,linestyle=':') 
ax.gridlines()

ax.scatter(-73.8,42.7,marker='*',s=400,c='k',transform=ccrs.PlateCarree())

# add a circle about a lat/lon location

radius_in_meters = 1000.
circle_points = cartopy.geodesic.Geodesic().circle(lon=lons[0], lat=lats[0], radius=radius_in_meters, endpoint=False)
geom = shapely.geometry.Polygon(circle_points)
ax.add_geometries((geom,), crs=ccrs.PlateCarree(), facecolor='none', edgecolor='red', linewidth=1)

# adding on a Google-derived relief map as background

from cartopy.io.img_tiles import GoogleTiles

# for google tiles relief map
# https://stackoverflow.com/questions/37423997/cartopy-shaded-relief

class ShadedReliefESRI(GoogleTiles):
    # shaded relief
    def _image_url(self, tile):
        x, y, z = tile
        url = ('https://server.arcgisonline.com/ArcGIS/rest/services/' \
               'World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}.jpg').format(
               z=z, y=y, x=x)
        return url


ax = plt.axes(projection=ShadedReliefESRI().crs)
ax.set_extent([-112.64,-112.02,39.13,39.62])
ax.add_image(ShadedReliefESRI(), 8) # 12 is higher-res and sharper
plt.show()

# A map with an inset, as used in plot_GFS_GRIB_analyses.ipynb
fig = plt.figure(figsize=(16, 10))

# select our central latitude and longitude
centlon = -97.5
centlat = 40.

# Select a projection
proj=ccrs.LambertConformal(central_latitude=centlat, central_longitude=centlon)

# create our axis object 'ax', specifying Lambert Conformal projection
ax = fig.add_subplot(1,1,1,projection=proj) 

# The features function we defined adds some cartopy features on.  We pass it 'ax'
features(ax)

plot_bounds = [-135,-75,20,55]
ax.set_extent(plot_bounds)

# pcolormesh
colors = ['skyblue', 'white', 'red']
cmap = LinearSegmentedColormap.from_list('name', colors)
norm = plt.Normalize(0, 60)
cm = ax.pcolormesh(lon_hrrr,lat_hrrr,wspd_diff,shading='auto',cmap=cmap, norm=norm, transform=ccrs.PlateCarree())
plt.colorbar(cm, orientation="vertical",shrink=0.5,label="wind speed (m/s)") 

q=10
plt.barbs(to_np(lon_hrrr[::q]),to_np(lat_hrrr[::q]),to_np(uu[::q,::q]),to_np(vv[::q,::q]),color='black',zorder=40,transform=ccrs.PlateCarree())

# and add a title
plt.title(" Level "+str(p_levels[mlevel-1])+" mb GFS wind speed analysis (m/s) run "+str(filename)[0:14]); # semicolon suppresses unwanted text printed to screen

# add inset locator box to main map
# https://stackoverflow.com/questions/55303911/add-polygon-box-to-cartopy-python
geom = geometry.box(minx=-110,maxx=-100,miny=36,maxy=43)
ax.add_geometries([geom], crs=cartopy.crs.PlateCarree(), linewidth=3, facecolor='none', edgecolor='k')

##############################################################################################################
# inset plot
##############################################################################################################

# https://stackoverflow.com/questions/45527584/how-to-easily-add-a-sub-axes-with-proper-position-and-size-in-matplotlib-and-car
# https://scipython.com/blog/inset-plots-in-matplotlib/

# select our central latitude and longitude for INSET
centlon = -105.27055
centlat = 40.01499
proj2=ccrs.LambertConformal(central_latitude=centlat, central_longitude=centlon)
ax2 = plt.axes([0, 0, 1, 1], projection=proj2)
ax2.set_global()

# Manually set the position and relative size of the inset axes within ax1
ip = InsetPosition(ax, [0.68,0.02,0.35,0.35]) #[0.4,0.2,0.5,0.5]
ax2.set_axes_locator(ip)

plot_bounds = [-110,-100,36,43]
ax2.set_extent(plot_bounds)
norm = plt.Normalize(0, 60)
cm = ax2.pcolormesh(lon_hrrr,lat_hrrr,wspd_diff,shading='auto',cmap=cmap, norm=norm, zorder=1,transform=ccrs.PlateCarree())
features(ax2)
ax2.add_feature(cfeat.STATES.with_scale('50m'),linestyle='dotted',linewidth=2)
ax2.scatter(-105.27055,40.01499,marker='*',s=200,c='w',edgecolors='k',transform=ccrs.PlateCarree(),zorder=50) # Boulder
ax2.scatter(-106.29,40.01,marker='o',s=125,c='w',edgecolors='k',transform=ccrs.PlateCarree(),zorder=50) # upwind

qlon=7
qlat=4
print(np.shape(lon_hrrr[::qlon]),np.shape(lat_hrrr[::qlat]),np.shape(uu[::qlon,::qlat]))
plt.barbs(to_np(lon_hrrr[::qlon]),to_np(lat_hrrr[::qlat]),to_np(uu[::qlat,::qlon]),to_np(vv[::qlat,::qlon]),color='black',zorder=40,transform=ccrs.PlateCarree())

########## END ############

Plotting a sounding

# create figure and skew plot
fig = plt.figure(figsize=(10,10))
skew = plots.SkewT(fig)

# plot T and Td
skew.plot(p, T, 'red')
skew.plot(p, Td, 'green')

# now the wind barbs, at selected vertical interval
interval = np.arange(100,1000,50) * units.hPa  # remember it does not include the last value of range!!!
idx = mpcalc.resample_nn_1d(p, interval)  # resample, using nearest-neighbor (nn), in 1 dimension (1d). Returns indices
skew.plot_barbs(p[idx], u[idx], v[idx])  # Now only for indices defined in last cell

# set axis limits
skew.ax.set_ylim(1000,100) # limit y-axis to 1000 to 100 mb
skew.ax.set_xlim(-50,30)   # limit x-axis to -50 to +30 C

# plot the reference curves/lines
skew.plot_dry_adiabats()
skew.plot_moist_adiabats()
skew.plot_mixing_lines()

# define parcel
parcel_path = mpcalc.parcel_profile(p, T[0], Td[0])

# calculate CAPE and CIN
cape, cin = mpcalc.cape_cin(p, T, Td, parcel_path)

# shade CAPE and CIN
skew.shade_cape(p, T, parcel_path)
skew.shade_cin(p, T, parcel_path)

# calculate parameters for LFC and EL defaulting to surface parcel
# and indicate on plot if they exist
lfc_p, lfc_t = mpcalc.lfc(p, T, Td)
el_p, el_t = mpcalc.el(p, T, Td)
if lfc_p:
    skew.ax.axhline(lfc_p)   
if el_p:
    skew.ax.axhline(el_p)

# print CAPE and CIN on skew-t, in a box with black border and white background
plt.figtext(0.29,0.16,f' CAPE {cape:.1f} \n CIN {cin:.1f} ',size=12,
            bbox=dict(facecolor='white', edgecolor='black', boxstyle='round,pad=1'))

# make the hodograph inset
# ax_hod = inset_axes(skew.ax, '35%', '35%', loc=1) # default but may obscure wind barbs
ax_hod = inset_axes(skew.ax, '35%', '35%', loc='upper right', bbox_transform=skew.ax.transAxes, 
                    bbox_to_anchor=(-0.05, 0., 1, 1))  # shifts hodo a bit to the left
h = Hodograph(ax_hod, component_range=80.)
h.add_grid(increment=20)
h.plot_colormapped(u, v, height);

Reading GRIB datasets

• Example for NAM GRIB2 file

ds = xr.open_dataset("/spare11/atm240/EXAMPLE02/nam.t00z.conusnest.hiresf06.tm00.grib2",engine='pynio') 


# get catalogue of variable names (could yield LOTS of output)
names =  ds.variables.keys()
print("Variable names:", names) 

# ------------------------------------------------------------------------------------------------- 
# some fields to extract from GRIB2 data. We're plotting 500 mb height and SLP here. 
# -------------------------------------------------------------------------------------------------  
hgt = ds.HGT_P0_L100_GLC0.sel(lv_ISBL0 = 50000.0)        # 500 mb height  
u10m = ds.UGRD_P0_L103_GLC0.sel(lv_HTGL8 = 10.0)         # 10 m wind speed (u)  
v10m = ds.VGRD_P0_L103_GLC0.sel(lv_HTGL8 = 10.0)         # 10 m wind speed (v) 
hgt0 = ds.HGT_P0_L1_GLC0                                 # terrain height 
u80m = ds.UGRD_P0_L103_GLC0.sel(lv_HTGL8 = 80.0)         # 80 m wind speed (u) 
v80m = ds.VGRD_P0_L103_GLC0.sel(lv_HTGL8 = 80.0)         # 80 m wind speed (v) 
slp_pa  = ds.PRMSL_P0_L101_GLC0                          # sea-level pressure (SLP) 
• Great circle distance between 2 locations, in miles

# lat/lon in degrees
x1 = math.radians(n_lat) # location 1
y1 = math.radians(n_lon)
x2 = math.radians(target_lat) # location 2
y2 = math.radians(target_lon)
        
# Great circle distance in radians
angle1 = math.acos(math.sin(x1) * math.sin(x2) 
				+ math.cos(x1) * math.cos(x2) * math.cos(y1 - y2))

# Convert back to degrees.
angle1 = math.degrees(angle1)

# Each degree on a great circle of Earth is 60 nautical miles.
distance1 = 60.0 * angle1
        
# Convert nautical miles to miles
gcd= distance1*1.15078
• cfgrib for GRIB2 file
filter_by_keys
filter_by_keys={'typeOfLevel': 'meanSea'}
    filter_by_keys={'typeOfLevel': 'hybrid'}
    filter_by_keys={'typeOfLevel': 'atmosphere'}
    filter_by_keys={'typeOfLevel': 'surface'}
    filter_by_keys={'typeOfLevel': 'planetaryBoundaryLayer'}
    filter_by_keys={'typeOfLevel': 'isobaricInPa'}
    filter_by_keys={'typeOfLevel': 'isobaricInhPa'}
    filter_by_keys={'typeOfLevel': 'heightAboveGround'}
    filter_by_keys={'typeOfLevel': 'depthBelowLandLayer'}
    filter_by_keys={'typeOfLevel': 'heightAboveSea'}
    filter_by_keys={'typeOfLevel': 'atmosphereSingleLayer'}
    
    filter_by_keys={'typeOfLevel': 'lowCloudLayer'}
    filter_by_keys={'typeOfLevel': 'middleCloudLayer'}
    filter_by_keys={'typeOfLevel': 'highCloudLayer'}
    filter_by_keys={'typeOfLevel': 'cloudCeiling'}
    filter_by_keys={'typeOfLevel': 'convectiveCloudBottom'}
    filter_by_keys={'typeOfLevel': 'lowCloudBottom'}
    filter_by_keys={'typeOfLevel': 'middleCloudBottom'}
    filter_by_keys={'typeOfLevel': 'highCloudBottom'}
    filter_by_keys={'typeOfLevel': 'convectiveCloudTop'}
    filter_by_keys={'typeOfLevel': 'lowCloudTop'}
    filter_by_keys={'typeOfLevel': 'middleCloudTop'}
    filter_by_keys={'typeOfLevel': 'highCloudTop'}
    filter_by_keys={'typeOfLevel': 'convectiveCloudLayer'}
    filter_by_keys={'typeOfLevel': 'boundaryLayerCloudLayer'}
    filter_by_keys={'typeOfLevel': 'nominalTop'}
    
    filter_by_keys={'typeOfLevel': 'heightAboveGroundLayer'}
    
    filter_by_keys={'typeOfLevel': 'tropopause'}
    filter_by_keys={'typeOfLevel': 'maxWind'}
    filter_by_keys={'typeOfLevel': 'isothermZero'}
    filter_by_keys={'typeOfLevel': 'highestTroposphericFreezing'}
    filter_by_keys={'typeOfLevel': 'pressureFromGroundLayer'}
    filter_by_keys={'typeOfLevel': 'sigmaLayer'}
    filter_by_keys={'typeOfLevel': 'sigma'}
    filter_by_keys={'typeOfLevel': 'potentialVorticity'}

ds_hybrid = xr.open_dataset(location+filename,engine='cfgrib',filter_by_keys={'typeOfLevel': 'hybrid'})
ds_pressure = xr.open_dataset(location+filename,engine='cfgrib',filter_by_keys={'typeOfLevel': 'isobaricInhPa'})
ds_sfc = xr.open_dataset(location+filename_sfc,engine='cfgrib',
                                filter_by_keys={'level':10,'stepType': 'instant','typeOfLevel': 'heightAboveGround'})


Extra stuff

• Labeling contours, contour labeling

theta_contours = ax1.contour(x, cross.gh[:,:], cross.theta[:,:],
                                     levels= thlevs, colors = 'k')
            
theta_contours.clabel(theta_contours.levels[1::2], fontsize=8, colors='k',
                              inline=1,inline_spacing=8, fmt='%i', rightside_up=True,
                              use_clabeltext=True) 

Opening remote files

• Example from https://towardsdatascience.com/an-efficient-way-to-read-data-from-the-web-directly-into-python-a526a0b4f4cb


import urllib.request

url = 'ftp://ftp2.psl.noaa.gov/Datasets/ncep.reanalysis2.dailyavgs/gaussian_grid/air.2m.gauss.1979.nc'

req = urllib.request.Request(url)

with urllib.request.urlopen(req) as resp:
    ds = xr.open_dataset(io.BytesIO(resp.read()))

ds

Objects

# Used minisom to create an object called 'som'. This command
# prints out all of its attributes
print(dir(som))