I am trying to map the difference between climate simulation data and observed data over a set geographical area.
To create the map of just the climate simulation I am using this code
import matplotlib.pyplot as plt
import iris
import iris.plot as iplt
import cartopy
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import iris.analysis.cartography
def main():
#bring in all the models we need and give them a name
CCCma = '/exports/csce/datastore/geos/users/s0XXXX/Climate_Modelling/AFR_44_tas/ERAINT/1979-2012/tas_AFR-44_ECMWF-ERAINT_evaluation_r1i1p1_CCCma-CanRCM4_r2_mon_198901-200912.nc'
#Load exactly one cube from given file
CCCma = iris.load_cube(CCCma)
#we are only interested in the latitude and longitude relevant to Malawi
Malawi = iris.Constraint(grid_longitude=lambda v: 31 <= v <= 36.5, \
grid_latitude=lambda v: -18. <= v <= -8.)
CCCma = CCCma.extract(Malawi)
#time constraint to make all series the same
iris.FUTURE.cell_datetime_objects = True
t_constraint = iris.Constraint(time=lambda cell: 1989 <= cell.point.year <= 2008)
CCCma = CCCma.extract(t_constraint)
#Convert units to match, CORDEX data is in Kelvin but Observed data in Celsius, we would like to show all data in Celsius
CCCma.convert_units('Celsius')
#plot map with physical features
cmap = plt.cm.afmhot_r
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([31, 36.5, -8,-18])
#set axis tick marks
ax.set_xticks([32, 34, 36])
ax.set_yticks([-9, -11, -13, -15, -17])
lon_formatter = LongitudeFormatter(zero_direction_label=True)
lat_formatter = LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
data = CCCma
#take mean of data over all time
plot = iplt.contourf(data.collapsed('time', iris.analysis.MEAN), \
cmap=cmap, levels=[15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],\
extend='both')
#add colour bar index
plt.colorbar(plot)
#give map a title
plt.title('RCP4.5 Mean Temperature 1989-2008', fontsize=10)
plt.show()
if __name__ == '__main__':
main()
How can I amend this to take the difference between the two datasets? I tried this code:
import matplotlib.pyplot as plt
import iris
import iris.plot as iplt
import cartopy
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import iris.analysis.cartography
#this file is split into parts as follows:
#PART 1: load and format CORDEX models
#PART 2: load and format observed data
#PART 3: format data
#PART 4: plot data
def main():
#PART 1: CORDEX MODELS
#bring in all the models we need and give them a name
CCCma = '/exports/csce/datastore/geos/users/s0XXXX/Climate_Modelling/AFR_44_tas/ERAINT/1979-2012/tas_AFR-44_ECMWF-ERAINT_evaluation_r1i1p1_CCCma-CanRCM4_r2_mon_198901-200912.nc'
#Load exactly one cube from given file
CCCma = iris.load_cube(CCCma)
#we are only interested in the latitude and longitude relevant to Malawi
Malawi = iris.Constraint(grid_longitude=lambda v: 31 <= v <= 36.5, \
grid_latitude=lambda v: -18. <= v <= -8.)
CCCma = CCCma.extract(Malawi)
#time constraint to make all series the same
iris.FUTURE.cell_datetime_objects = True
t_constraint = iris.Constraint(time=lambda cell: 1989 <= cell.point.year <= 2008)
CCCma = CCCma.extract(t_constraint)
#PART 2: OBSERVED DATA
#bring in all the files we need and give them a name
CRU= '/exports/csce/datastore/geos/users/s0XXXX/Climate_Modelling/Actual_Data/cru_ts4.00.1901.2015.tmp.dat.nc'
#Load exactly one cube from given file
CRU = iris.load_cube(CRU, 'near-surface temperature')
#we are only interested in the latitude and longitude relevant to Malawi
Malawi = iris.Constraint(longitude=lambda v: 32.5 <= v <= 36., \
latitude=lambda v: -17. <= v <= -9.)
CRU = CRU.extract(Malawi)
#time constraint to make all series the same
iris.FUTURE.cell_datetime_objects = True
t_constraint = iris.Constraint(time=lambda cell: 1989 <= cell.point.year <= 2008)
CRU = CRU.extract(t_constraint)
#PART 3: FORMAT DATA
#Convert units to match
CCCma.convert_units('Celsius')
CRU.convert_units('Celsius')
#Take difference between two datasets
Bias_CCCma = CCCma-CRU
#PART 4: PLOT MAP
#plot map with physical features
cmap = plt.cm.afmhot_r
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([31, 36.5, -8,-18])
#set axis tick marks
ax.set_xticks([32, 34, 36])
ax.set_yticks([-9, -11, -13, -15, -17])
lon_formatter = LongitudeFormatter(zero_direction_label=True)
lat_formatter = LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
data = Bias_CCCma
#take mean of data over all time
plot = iplt.contourf(data.collapsed('time', iris.analysis.MEAN), \
cmap=cmap, levels=[15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],\
extend='both')
#add colour bar index
plt.colorbar(plot)
#give map a title
plt.title('RCP4.5 Mean Temperature 1989-2008', fontsize=10)
plt.show()
if __name__ == '__main__':
main()
However this gives me the following error:
ValueError: This operation cannot be performed as there are differing coordinates (grid_latitude, grid_longitude, time) remaining which cannot be ignored.
I was pretty sure this wasn't going to be so simple, but I'm not sure how to fix it. Any ideas? TIA!
Iris is very strict about matching up the cube coordinates for binary operations and there is an open issue discussing whether and how to make it more flexible ready for version 2. In the meantime, if your cubes are the same shape and you don't mind loading the data, you could just do
If your cubes are different shapes (i.e. the models are on different grids, as Jeremy suggested) or you don't want to load the data, there are a few things to look at:
regrid
one of the cubes to match the other.grid_latitude
andgrid_longitude
mean the same aslatitude
andlongitude
, you canrename
the grid coordinates on one of your cubes. You will also need to ensure the other coordinate metadata matches (e.g.var_name
is often an issue).time
coordinate coming up in your error message is almost certainly due to the unit mismatch you identified in your previous question. I think this issue should go away if you reorder your code to do the time averaging first and then take the difference (the binary operations don't care so much about scalar coordinates).