12
ATMOSPHEREOCEAN INTERACTIONS :: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright [email protected] 6.1 THE iris MODULE The iris module is a software package for working with climate data in python. It has been developed by the SciTools initiative at the United Kingdom Met Office. SciTools has provided a detailed tutorial introducing some of the main features of the package as well as a list of instructive examples from climatology, meteorology, and oceanography. If you are using Anaconda or one of its variants, I highly recommend using the conda-forge channel to install iris rather than the scitools channel recommended on the website: jswright$ conda install -c conda-forge iris This channel also has several other tools that are useful for climate data analysis, and provides a convenient way to manage them. Learn more at the conda-forge github site. The logic of iris is a bit different from the netCDF4 + numpy approach to reading and working with climate data. These differences have advantages and disadvantages. Among the advantages, iris provides data structures specifically for working with climate data. These data structures and related tools are also used as building blocks in other useful climate-related modules, such as eofs and windspharm. Disadvantages include a less intuitive syntax and a less mature code infrastructure (iris is sometimes slower and occasionally unstable, but is improving on both counts). In these notes we will work through two examples step by step. 6.2 S URFACE CURRENT ANOMALIES IN THE I NDONESIAN S EAS The first step in starting to use iris is learning how to read and select data for input. After importing the module, you can load netcdf files using the iris.load() function. In [1]: import iris In [2]: iris.load(’./data/plt_current.nc’) 1

6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright [email protected]

Embed Size (px)

Citation preview

Page 1: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

ATMOSPHERE–OCEAN INTERACTIONS :: PYTHON NOTES

6. Some simple applications of Iris

J. S. Wright

[email protected]

6.1 THE iris MODULE

The iris module is a software package for working with climate data in python. It has beendeveloped by the SciTools initiative at the United Kingdom Met Office. SciTools has provideda detailed tutorial introducing some of the main features of the package as well as a list ofinstructive examples from climatology, meteorology, and oceanography. If you are usingAnaconda or one of its variants, I highly recommend using the conda-forge channel to installiris rather than the scitools channel recommended on the website:

jswright$ conda install -c conda-forge iris

This channel also has several other tools that are useful for climate data analysis, and providesa convenient way to manage them. Learn more at the conda-forge github site.

The logic of iris is a bit different from the netCDF4 + numpy approach to reading andworking with climate data. These differences have advantages and disadvantages. Among theadvantages, iris provides data structures specifically for working with climate data. Thesedata structures and related tools are also used as building blocks in other useful climate-relatedmodules, such as eofs and windspharm. Disadvantages include a less intuitive syntax and aless mature code infrastructure (iris is sometimes slower and occasionally unstable, but isimproving on both counts). In these notes we will work through two examples step by step.

6.2 SURFACE CURRENT ANOMALIES IN THE INDONESIAN SEAS

The first step in starting to use iris is learning how to read and select data for input. Afterimporting the module, you can load netcdf files using the iris.load() function.

In [1]: import irisIn [2]: iris.load(’./data/plt_current.nc’)

1

Page 2: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

You can also load data from other common file types used in climatology and related fields,such as grib2. This loads the data as an iris CubeList:

In [3]: print(ncdf)0: eastward_sea_water_velocity / (m s**-1) (depth: 42; lat: 60; lon: 50)1: eastward_sea_water_velocity / (m s**-1) (depth: 42; lat: 60; lon: 50)2: eastward_sea_water_velocity / (m s**-1) (depth: 42; lat: 60; lon: 50)3: northward_sea_water_velocity / (m s**-1) (depth: 42; lat: 60; lon: 50)4: northward_sea_water_velocity / (m s**-1) (depth: 42; lat: 60; lon: 50)5: northward_sea_water_velocity / (m s**-1) (depth: 42; lat: 60; lon: 50)

A CubeList acts like a standard python list, but it is made up of iris Cubes. Cubes are thebasic building blocks of the iris module (a little like arrays in numpy). We can see more detailby printing one of the elements:

In [4]: print(ncdf[1])eastward_sea_water_velocity / (m s**-1) (depth: 42; lat: 60; lon: 50)Dimension coordinates:

depth x - -latitude - x -longitude - - x

Attributes:NCO: "4.6.4"_CoordinateAxes: time depth lat lon_Fillvalue: 9.96921e+36average_op_ncl: dim_avg_n over dimension(s): timeinterval_write: monthlyoffline_operation: time average and spatial interpolationphase: All

Here we can see that a Cube contains both the data and attributes (metadata) associated witha netcdf variable. You can also access the standard attributes:

In [5]: for vv in ncdf:...: print vv.long_name

Zonal CurrentZonal Current Anomaly (El Nino)Zonal Current Anomaly (La Nina)Meridional CurrentMeridional Current Anomaly (La Nina)Meridional Current Anomaly (El Nino)

This behavior becomes very important once we need to select and subset the data. This isdone in iris using the Constraint construct, as shown in the following example. Two typesof constraints are used here, one to select the variables by the standard name and one to selectthe variables by an attribute named phase, which differentiates the climatological currentsfrom the anomalies during El Niño and La Niña:

2

Page 3: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

� �1 i m p o r t iris2

3 # load data4 ncdf = iris.load(’./data/plt_current.nc’)5

6 # constraints7 unme = iris.Constraint(name=’eastward_sea_water_velocity ’)8 vnme = iris.Constraint(name=’northward_sea_water_velocity ’)9 clim = iris.AttributeConstraint(phase=’All’)

10 nino = iris.AttributeConstraint(phase=’Warm’)11 nina = iris.AttributeConstraint(phase=’Cold’)� �

Once we have defined the appropriate constraints, we can select the data. This is done via theCubeList.extract() function:� �

1 # extract variables2 uclim = ncdf.extract(unme & clim)[0]3 vclim = ncdf.extract(vnme & clim)[0]4 unino = ncdf.extract(unme & nino)[0]5 vnino = ncdf.extract(vnme & nino)[0]6 unina = ncdf.extract(unme & nina)[0]7 vnina = ncdf.extract(vnme & nina)[0]� �

A few of the behaviors of iris Constraints are evident from this example. Among otherthings, we can combine constraints using the logical and (&) and or (|). Second, when weextract from a CubeList, we get a CubeList back. In this case each selected CubeList hasonly one element, so we index it with 0 to return a Cube instead. We can also apply one ormore Constraints when loading data to avoid loading large amounts of data that we don’tneed:� �

1 # alternate loading2 vars_clim = iris.load(’./data/plt_current.nc’, constraints=clim)3 vars_nino = iris.load(’./data/plt_current.nc’, constraints=nino)4 vars_nina = iris.load(’./data/plt_current.nc’, constraints=nina)� �

This approach would result in three CubeList objects, each containing one zonal and onemeridional component of the surface currents.

One convenient feature of iris is that it tracks physical units. This is occasionally annoying,particularly if your file uses units that are not recognized by the CF Conventions (such as gpmfor geopotential height). In this case it allows us to convert from units of m s−1 to cm −1 in asingle logical step:� �

1 # convert to cm/s2 f o r cc i n ncdf:3 cc.convert_units(’centimeter -second^-1’)� �

3

Page 4: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

In the standard numpy approach, we would multiply by a constant to do this conversion andmark it with a comment. Note that arithmetic with iris Cubes also checks and updates units,as we will see in the second example.

We can also use Constraints for basic data processing:� �1 # extract uppermost 100m and average2 srfce = iris.Constraint(depth= lambda d: 0 < d < 100)3 uclim = srfce.extract(uclim).collapsed(’depth’, iris.analysis.MEAN)4 vclim = srfce.extract(vclim).collapsed(’depth’, iris.analysis.MEAN)5 unino = srfce.extract(unino).collapsed(’depth’, iris.analysis.MEAN)6 vnino = srfce.extract(vnino).collapsed(’depth’, iris.analysis.MEAN)7 unina = srfce.extract(unina).collapsed(’depth’, iris.analysis.MEAN)8 vnina = srfce.extract(vnina).collapsed(’depth’, iris.analysis.MEAN)� �

In this case the constraint selects all depths less than 100 m and calculates the average overthe depth dimension. Note that extract is used in this case as an intrinsic function of theConstraint rather than as an intrinsic function of a CubeList (or Cube). Functions for dataprocessing are provided in the submodule iris.analysis, which is often imported as ia.

The iris module also includes some basic plotting tools. These are not particularly robust— just a simple set of wrappers around matplotlib with some convenience shortcuts forworking with geospatial data. To create the plots for this example, we need to import severalmodules and prepare some data and plot parameters:� �

1 i m p o r t matplotlib.pyplot as plt , seaborn as sns2 i m p o r t numpy as np3 i m p o r t cartopy.feature as cfeat4

5 # cartopy continents6 land_50m = cfeat.NaturalEarthFeature(’physical ’, ’land’, ’110m’,7 edgecolor=’#666666 ’,8 facecolor=’#999999 ’, zorder =10)9

10 # plots11 fig = plt.figure(figsize =(12, 6))12 fig.subplots_adjust(wspace =0.25)13

14 # ================================================================15 # climatology plot16 #17 # current speed18 speed = ia.maths.exponentiate(uclim*uclim + vclim*vclim , 0.5)19 # contour parameters20 clv = np.linspace(0, 60, 13)21 lon = uclim.coord(’longitude ’).points22 lat = vclim.coord(’latitude ’).points23 # streamplot parameters24 uclim.data.data[uclim.data.mask] = np.NaN25 vclim.data.data[vclim.data.mask] = np.NaN26 lws = 4*speed.data / speed.collapsed ((’longitude ’, ’latitude ’), ia.

MAX).data27 lws[lws < 0.1] = 0.1� �

4

Page 5: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

Once these are in place, we can create a geoaxes instance, add the continents, and plot thecurrents (as streamlines using plt.streamplot) and the current speed (as a filled contourplot using iris.plot.contourf):� �

1 i m p o r t iris.plot as iplt2 i m p o r t cartopy.crs as ccrs3 from cartopy.mpl.gridliner i m p o r t LONGITUDE_FORMATTER ,

LATITUDE_FORMATTER4

5 # plots6 axa = plt.subplot (131, projection=ccrs.PlateCarree ())7 axa.add_feature(land_50m)8 cs0 = iplt.contourf(speed , levels=clv , cmap=plt.cm.Blues , extend=’

max’)9 plt.streamplot(lon , lat , uclim.data , vclim.data , density =1.5, color=

’k’, linewidth=lws ,10 transform=ccrs.PlateCarree (), zorder =2)11 axa.set_xticks( r a n g e (90, 141, 10), crs=ccrs.PlateCarree ())12 axa.set_yticks( r a n g e (-30, 31, 10), crs=ccrs.PlateCarree ())13 axa.xaxis.set_major_formatter(LONGITUDE_FORMATTER)14 axa.yaxis.set_major_formatter(LATITUDE_FORMATTER)� �

Note the use of the LONGITUDE_FORMATTER and LATITUDE_FORMATTER to standardize the axistickmarks into ◦E and ◦S / ◦N. The widths of the streamlines are adjusted to reflect the speedof the currents relative to the maximum speed. The final element in the climatology plot is acolorbar:� �

1 # color bar for climatology plot2 with sns.axes_style(’white ’):3 axa_left = axa.get_position ().bounds [0]4 axa_wdth = axa.get_position ().bounds [2]5 cax = fig.add_axes ([axa_left , 0.12, axa_wdth , 0.02])6 cb0 = plt.colorbar(cs0 , cax , orientation=’horizontal ’)7 cb0.set_ticks ([0, 10, 20, 30, 40, 50, 60])8 cb0.set_label(r’Surface current [cm s$^{-\ mathregular {1}}$]’)� �

This approach uses the axes.get_position().bounds construct to help with constructingthe axis for the colorbar.

After adding two additional panels that show the anomalies in surface currents (and thespeed of those anomalies) during El Niño and La Niña, this script gives us Figure 6.1. See theuploaded code for a full working version of the script (contact me if you need the data file).

6.3 THE ASIAN TROPOPAUSE AEROSOL LAYER

Our second example is a basic descriptive analysis of the aerosol layer that forms near thetropopause during the Asian summer monsoon. This aerosol layer has only emerged withinthe last 15 years. It’s existence results from several special aspects of the Asian monsoon andthe surrounding regions: the severe pollution that often exists in the atmospheric boundary

5

Page 6: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

90°E 100°E 110°E 120°E 130°E 140°E

30°S

20°S

10°S

10°N

20°N

30°Na.Climatology

0 10 20 30 40 50 60

Surface current [cm s−1]

90°E 100°E 110°E 120°E 130°E 140°E

30°S

20°S

10°S

10°N

20°N

30°Nb.El Niño anomaly

90°E 100°E 110°E 120°E 130°E 140°E

30°S

20°S

10°S

10°N

20°N

30°Nc.La Niña anomaly

0 2 4 6 8 10 12 14 16 18 20

Surface current anomaly [cm s−1]

Figure 6.1: Climatological mean (left) and surface current anomalies in the eastern IndianOcean and Indonesian seas associated with El Niño (center) and La Niña (right).

layer over South and East Asia; strong convection associated with the South and East Asianmonsoons, which lifts that pollution into the upper troposphere; and the upper troposphericmonsoon anticyclone, which restricts dispersion of the upper tropospheric aerosol layer onceit forms.

As with the previous example, we start by loading the data.� �1 i m p o r t iris2

3 # ================================================================4 # constants5 grav = iris.coords.AuxCoord (9.80665 , long_name=’

gravity_at_mean_sea_level ’, units=’m s-2’)6

7 # ================================================================8 # data file9 ncdf = iris.load(’./data/m2_aer.ttl.monthly .2000 -2014. nc’)� �

Here, we have also defined an auxiliary coordinate to represent the gravitational constant. Thisapproach uses the intrinsic features of iris to assign a name and units to the gravitationalconstant, which are then propagated through any arithmetic involving other Cubes. We thenextract the aerosol-related variables for a subset of the vertical levels near the tropopause.� �

1 # extract data2 vlev = iris.Constraint(coord_values ={’vertical level’: lambda z: 36

<= z <= 41})3 delp = ncdf.extract(iris.Constraint(name=’pressure_thickness ’) &

vlev)[0]

6

Page 7: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

4 # sulphates5 sulf = ncdf.extract(iris.Constraint(name=’Sulphate aerosol ’) & vlev)

[0]6 sulf.rename(’sulphate_aerosol ’)7 # black carbon8 blkc = ncdf.extract(iris.Constraint(name=’Hydrophilic Black Carbon ’)

& vlev)[0] + \9 ncdf.extract(iris.Constraint(name=’Hydrophobic Black Carbon ’)

& vlev)[0]10 blkc.rename(’black_carbon ’)11 # organic carbon12 orgc = ncdf.extract(iris.Constraint(name=’Hydrophilic Organic Carbon

(Particulate Matter)’) & vlev)[0] + \13 ncdf.extract(iris.Constraint(name=’Hydrophobic Organic Carbon

(Particulate Matter)’) & vlev)[0]14 orgc.rename(’organic_carbon ’)15 # organic carbon16 dust = ncdf.extract(iris.Constraint(name=’Dust Mixing Ratio (bin

001)’) & vlev)[0] + \17 ncdf.extract(iris.Constraint(name=’Dust Mixing Ratio (bin

002)’) & vlev)[0] + \18 ncdf.extract(iris.Constraint(name=’Dust Mixing Ratio (bin

003)’) & vlev)[0] + \19 ncdf.extract(iris.Constraint(name=’Dust Mixing Ratio (bin

004)’) & vlev)[0] + \20 ncdf.extract(iris.Constraint(name=’Dust Mixing Ratio (bin

005)’) & vlev)[0]21 dust.rename(’dust_aerosol ’)� �

We also extract the pressure thickness of each layer, which we then use to calculate mass-weighted vertical integrals:� �

1 i m p o r t iris.analysis as ia2 # ================================================================3 # mass−weighted vertical integrals (gives column mass in kg/m**2)4 sulf = (sulf*delp).collapsed(’vertical level ’, ia.SUM) / grav5 blkc = (blkc*delp).collapsed(’vertical level ’, ia.SUM) / grav6 orgc = (orgc*delp).collapsed(’vertical level ’, ia.SUM) / grav7 dust = (dust*delp).collapsed(’vertical level ’, ia.SUM) / grav� �

This procedure is similar to that used to calculate total column water vapor, also sometimescalled precipitable water. We then convert the units to µg m−2 for ease of plotting:� �

1 # convert to Âtg / m**22 sulf.convert_units(’microgram -meter^-2’)3 blkc.convert_units(’microgram -meter^-2’)4 orgc.convert_units(’microgram -meter^-2’)5 dust.convert_units(’microgram -meter^-2’)� �

7

Page 8: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

The next step is to select the months of June to August and average them over all years. Theiris module includes the coord_categorisation sub-module for dealing with this kind ofdata subsetting:� �

1 i m p o r t iris.coord_categorisation as icc2 # ================================================================3 # calculate JJA mean for each variable4 jja = iris.Constraint(season_membership=True)5 icc.add_season_membership(sulf , ’time’, ’jja’, name=’

season_membership ’)6 icc.add_season_membership(blkc , ’time’, ’jja’, name=’

season_membership ’)7 icc.add_season_membership(orgc , ’time’, ’jja’, name=’

season_membership ’)8 icc.add_season_membership(dust , ’time’, ’jja’, name=’

season_membership ’)9 sulf_jja = sulf.extract(jja).collapsed(’time’, ia.MEAN)

10 blkc_jja = blkc.extract(jja).collapsed(’time’, ia.MEAN)11 orgc_jja = orgc.extract(jja).collapsed(’time’, ia.MEAN)12 dust_jja = dust.extract(jja).collapsed(’time’, ia.MEAN)� �

First a new auxiliary time coordinate is added to each Cube. This coordinate takes the booleanvalue True for months corresponding to June, July, and August and the boolean value Falsefor all other months. We then use Cube.extract() to select only the JJA months, and finallycalculate the average over all JJA months using Cube.collapsed(). Note that collapsed()must be applied to a particular dimension, in this case time. The collapsed() function canalso be applied to multiple dimensions at once, as shown in the following example that findsand prints the maximum total column density for each aerosol type:� �

1 # maximum values help us to set the contour levels2 p r i n t ’Sulphate:’, sulf_jja.collapsed ((’latitude ’, ’longitude ’), ia.

MAX).data3 p r i n t ’Black Carbon:’, blkc_jja.collapsed ((’latitude ’, ’longitude ’),

ia.MAX).data4 p r i n t ’Org Carbon:’, orgc_jja.collapsed ((’latitude ’, ’longitude ’),

ia.MAX).data5 p r i n t ’Dust:’, dust_jja.collapsed ((’latitude ’, ’longitude ’), ia.MAX)

.data� �We can then plot maps of each aerosol type, as shown in Figure 6.2. Example code for the

first panel is shown below:� �1 i m p o r t iris.plot as iplt2 i m p o r t matplotlib.pyplot as plt3 i m p o r t numpy as np4 from matplotlib.colors i m p o r t BoundaryNorm5 i m p o r t cartopy.crs as ccrs6 i m p o r t cartopy.feature as cfeat

8

Page 9: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

7 from cartopy.mpl.gridliner i m p o r t LONGITUDE_FORMATTER ,LATITUDE_FORMATTER

8

9 # maps10 fig = plt.figure(figsize =(10, 6))11 fig.subplots_adjust(wspace =0.25, hspace =0.25)12

13 nrm = BoundaryNorm(np.linspace(0, 600, 51), plt.cm.YlOrBr.N)14 axa = plt.subplot (221, projection=ccrs.PlateCarree ())15 axa.add_feature(cfeat.COASTLINE)16 cs1 = iplt.pcolormesh(sulf_jja , norm=nrm , cmap=plt.cm.YlOrBr ,

edgecolors=’face’)17 axa.set_extent ([60, 120, 0, 30])18 axa.set_xticks( r a n g e (60, 121, 20), crs=ccrs.PlateCarree ())19 axa.set_yticks( r a n g e (0, 41, 10), crs=ccrs.PlateCarree ())20 axa.xaxis.set_major_formatter(LONGITUDE_FORMATTER)21 axa.yaxis.set_major_formatter(LATITUDE_FORMATTER)22

23 # colorbar24 l, b, w, h = axa.get_position ().bounds25 cax = fig.add_axes ([l-0.05, b, 0.01, h])26 cb1 = plt.colorbar(cs1 , cax , orientation=’vertical ’)27 cb1.set_ticks(np.linspace(0, 600, 7))28 cb1.set_label(’Mixing ratio [$\mu$g m’+r’$^{-\ mathregular {2}}$]’)29 cax.yaxis.set_ticks_position(’left’)30 cax.yaxis.set_label_position(’left’)� �

Here we have used pcolormesh, which fills each grid cell with a constant color, instead ofcontourf, which attempts to smooth spatial variability in the field. This approach requiresa slightly different syntax (and the use of a BoundaryNorm), but all other elements shouldbe familiar from previous examples. The same approach is used for all four panels (see fullworking code for details).

It is also useful for us to examine the time variability of this Asian tropopause aerosol layer.To do this, we again use iris.coord_categorisation and iris.analysis:� �

1 # calculate add an auxiliary coordinate representing the year2 icc.add_year(sulf , ’time’, name=’year’)3 icc.add_year(blkc , ’time’, name=’year’)4 icc.add_year(orgc , ’time’, name=’year’)5 icc.add_year(dust , ’time’, name=’year’)6

7 # calculate annual summertime means8 sulf_ts = sulf.extract(jja).aggregated_by(’year’, ia.MEAN)9 blkc_ts = blkc.extract(jja).aggregated_by(’year’, ia.MEAN)

10 orgc_ts = orgc.extract(jja).aggregated_by(’year’, ia.MEAN)11 dust_ts = dust.extract(jja).aggregated_by(’year’, ia.MEAN)� �

First we add the year as a second auxiliary time coordinate. We then extract the JJA months asbefore, but use the intrinsic function aggregated_by(’year’) rather than collapsed(’time’)to calculate the means. This yields a time series of 15 spatial distribution, one for the boreal

9

Page 10: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

60°E 80°E 100°E 120°E0°

10°N

20°N

30°N

40°NSulphate aerosol

60°E 80°E 100°E 120°E0°

10°N

20°N

30°N

40°NDust aerosol

60°E 80°E 100°E 120°E0°

10°N

20°N

30°N

40°NBlack carbon

60°E 80°E 100°E 120°E0°

10°N

20°N

30°N

40°NOrganic carbon

0

100

200

300

400

500

600M

ixin

g ra

tio [µ

g m−

2 ]

0

300

600

900

1200

1500

1800

Mix

ing

ratio

[µg

m−

2 ]

0

20

40

60

80

100

120

Mix

ing

ratio

[µg

m−

2 ]

0

50

100

150

200

250

300

Mix

ing

ratio

[µg

m−

2 ]

Figure 6.2: JJA mean upper tropospheric column densities for sulphate aerosols, dust, blackcarbon, and organic carbon from MERRA-2 aerosol reanalyses averaged over 2000–2014.

summer of each year from 2000 through 2014. The next step is to calculate area averages, thusenabling us to plot the time series in a concise way:� �

1 # calculate area averages2 sulf_ts.coord(’latitude ’).guess_bounds ()3 sulf_ts.coord(’longitude ’).guess_bounds ()4 aws = ia.cartography.area_weights(sulf_ts)5 sulf_ts = sulf_ts.collapsed ([’longitude ’, ’latitude ’], ia.MEAN ,

weights=aws)6 blkc_ts = blkc_ts.collapsed ([’longitude ’, ’latitude ’], ia.MEAN ,

weights=aws)7 orgc_ts = orgc_ts.collapsed ([’longitude ’, ’latitude ’], ia.MEAN ,

weights=aws)8 dust_ts = dust_ts.collapsed ([’longitude ’, ’latitude ’], ia.MEAN ,

weights=aws)� �It is important that area means calculated from gridded data account for differences in theareas of different grid cells. Here we do this by first applying coord().guess_bounds() toboth the latitude and longitude coordinates (in some cases this may not be necessary; itdepends on how complete the original file’s metadata is). We can then calculate the areaweights directly using iris.analysis.cartography.area_weights(Cube). Once we havethe area weights, it is straightforward to apply collapsed to calculate the area mean.

10

Page 11: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

The different aerosol types have very different concentrations within the upper troposphere.To avoid having to use four separate y-axes, we can calculate normalized anomalies from thetime mean for each aerosol type:� �

1 # normalized anomalies from time mean2 sulf_ts = (sulf_ts - sulf_ts.collapsed(’time’, ia.MEAN)) / sulf_ts.

collapsed(’time’, ia.STD_DEV)3 blkc_ts = (blkc_ts - blkc_ts.collapsed(’time’, ia.MEAN)) / blkc_ts.

collapsed(’time’, ia.STD_DEV)4 orgc_ts = (orgc_ts - orgc_ts.collapsed(’time’, ia.MEAN)) / orgc_ts.

collapsed(’time’, ia.STD_DEV)5 dust_ts = (dust_ts - dust_ts.collapsed(’time’, ia.MEAN)) / dust_ts.

collapsed(’time’, ia.STD_DEV)� �The normalized anomaly is defined as the field minus its time mean and divided by its temporalstandard deviation, as illustrated in this script. We can then plot the time series:� �

1 # time series plot2 sns. s e t (style=’darkgrid ’)3 plt.figure(figsize =(9, 4))4 iplt.plot(sulf_ts , color=’#7570b3’, linewidth =2, linestyle=’-’,

alpha =0.6, label=’Sulphate aerosol ’)5 iplt.plot(dust_ts , color=’#d95f02 ’, linewidth =2, linestyle=’-’,

alpha =0.6, label=’Dust’)6 iplt.plot(orgc_ts , color=’#1b9e77’, linewidth =2, linestyle=’-’,

alpha =0.6, label=’Organic carbon ’)7 iplt.plot(blkc_ts , color=’#666666 ’, linewidth =2, linestyle=’-’,

alpha =0.6, label=’Black carbon ’)8 plt.ylim((-4, 4))9 plt.title(’MERRA -2 Asian tropopause aerosol layer variability ’)

10 plt.xlabel(’Year’)11 plt.ylabel(’Normalized anomaly ’)12 plt.legend ()13 plt.savefig(’./figs/atal_timeseries.pdf’, bbox_inches=’tight’,

facecolor=’none’, edgecolor=’none’)� �As a bonus, we can calculate and plot the trends in each aerosol type over the 15-year

analysis period. For sulphates:� �1 from scipy.stats i m p o r t theilslopes2 # trends3 xc = sulf_ts.coord(’time’)4 xs = xc.points5 xm = np.percentile(xs, 50)6 ts = theilslopes(sulf_ts.data , xs -xm, 0.9)7 trnd = sulf_ts.copy(data=ts[1] + ts[0]*(xs-xm))8 i f (ts[2] > 0) == (ts[3] > 0):9 iplt.plot(trnd , color=’#7570 b3’, linewidth =1, linestyle=’-’,

label=’’)10 e l s e :11 iplt.plot(trnd , color=’#7570 b3’, linewidth =1, linestyle=’--’,

label=’’)� �11

Page 12: 6. Some simple applications of Iris - University of Cambridge · ATMOSPHERE–OCEAN INTERACTIONS:: PYTHON NOTES 6. Some simple applications of Iris J. S. Wright jswright@tsinghua.edu.cn

2002 2004 2006 2008 2010 2012 2014Year

4

3

2

1

0

1

2

3

4N

orm

aliz

ed a

nom

aly

MERRA-2 Asian tropopause aerosol layer variability

Sulphate aerosolDustOrganic carbonBlack carbon

Figure 6.3: Interannual variability of boreal summer column amounts of sulphate aerosols,dust, black carbon, and organic carbon in the upper troposphere above Asia fromMERRA-2 aerosol reanalyses. Trends are based on the Theil–Sen estimator. Solidtrend lines indicate statistical significance at the 90% confidence level.

In this case we use the robust Theil–Sen estimator rather than ordinary least squares. TheTheil–Sen linear fit is especially useful in cases like this, where we have short time series and ahandful of rather large outliers. The Theil–Sen estimator finds the median slope among allpairs of points, and is less sensitive to outliers and to the choice of start and end points thanmany of the more widely-known best fit lines. The results are shown in Figure 6.3. The MERRA-2 reanalysis indicates that sulphates, black carbon, and organic carbon have all experiencedstatistically significant increases in this part of the atmosphere since 2000.

12