6. Climatology and Anomaly#

To start climate analysis, we often begin by analyzing climatology and anomaly.

Calculate Monthly Climatology and Anomaly with groupby#

We first open the OLR file. In the following example, climatology is calculated from 1998 to 2016, and the near real-time period is 2017. The anomaly is defined as the data from 2017 subtracting the climatology.

import numpy as np
import pandas as pd
import xarray as xr 
from cartopy import crs as ccrs   
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import matplotlib as mpl
from matplotlib import pyplot as plt
import cmaps 

mpl.rcParams['figure.dpi'] = 100

# Spatial and temporal range parameters
lats = -20
latn =  30
lon1 =  79 
lon2 = 161
time1 = '2017-12-01'
time2 = '2017-12-31'

# Open file
olr_ds = xr.open_dataset("data/olr.nc") 
# Long term data to calculate climatology
olr = (olr_ds.sel(time=slice('1998-01-01','2016-12-31'),
                    lat=slice(lats,latn),
                    lon=slice(lon1,lon2)).olr)
# Real time period for anomaly.
olrrt = (olr_ds.sel(time=slice('2017-12-01','2017-12-31'),
                    lat=slice(lats,latn),
                    lon=slice(lon1,lon2)).olr)

In Unit 4, we learned how to conditionally control datetime objects using xarray. Similarly, we group and calculate the mean of dates within the same month based on the datetime accessor time.month to obtain the monthly climatology. To group by the same months, we use the xarray function groupby.

olrGB = olr.groupby("time.month")
olrMonClim = olrGB.mean("time")  # The `mean` here is to mean over the same group, not mean over the entire period. 
olrMonClim
<xarray.DataArray 'olr' (month: 12, lat: 50, lon: 82)> Size: 197kB
array([[[270.46295, 270.73306, 270.65665, ..., 257.25116, 256.66913,
         255.88489],
        [269.24954, 268.8672 , 269.106  , ..., 253.44594, 253.16605,
         252.61195],
        [267.06573, 267.1016 , 267.57956, ..., 249.2561 , 248.4184 ,
         247.00945],
        ...,
        [257.9647 , 257.92267, 258.25693, ..., 256.99442, 256.7958 ,
         257.13882],
        [253.88763, 253.44121, 252.49252, ..., 252.67389, 252.67776,
         252.66585],
        [245.34576, 240.01846, 228.33456, ..., 248.6515 , 248.66846,
         248.49454]],

       [[266.90402, 267.24286, 267.5611 , ..., 255.47618, 254.07542,
         253.18782],
        [264.35098, 264.7394 , 264.88358, ..., 251.78017, 251.39395,
         251.59004],
        [261.4748 , 262.0324 , 262.23868, ..., 247.40343, 248.10788,
         248.63504],
...
        [281.75525, 281.8884 , 282.38974, ..., 263.41367, 263.98563,
         264.8334 ],
        [279.72992, 279.606  , 276.21063, ..., 257.44904, 258.60928,
         259.01102],
        [270.86935, 264.80884, 252.717  , ..., 252.37294, 253.24257,
         254.25145]],

       [[277.6884 , 277.96237, 278.4138 , ..., 268.97833, 268.33752,
         267.38034],
        [276.88388, 276.72455, 277.11646, ..., 267.85056, 267.2011 ,
         266.11264],
        [274.72495, 274.7343 , 275.10522, ..., 266.41324, 264.9135 ,
         264.2871 ],
        ...,
        [269.02094, 269.55533, 270.47006, ..., 258.97546, 259.5016 ,
         260.03702],
        [266.66284, 267.0835 , 265.3553 , ..., 253.46599, 253.83995,
         254.14189],
        [258.7794 , 254.24706, 243.16267, ..., 248.40625, 248.42702,
         248.41463]]], dtype=float32)
Coordinates:
  * lon      (lon) float32 328B 79.5 80.5 81.5 82.5 ... 157.5 158.5 159.5 160.5
  * lat      (lat) float32 200B -19.5 -18.5 -17.5 -16.5 ... 26.5 27.5 28.5 29.5
  * month    (month) int64 96B 1 2 3 4 5 6 7 8 9 10 11 12
Attributes:
    standard_name:  toa_outgoing_longwave_flux
    long_name:      NOAA Climate Data Record of Daily Mean Upward Longwave Fl...
    units:          W m-2
    cell_methods:   time: mean area: mean

Similarly, anomalies are obtained by grouping the real-time data using groupby and then subtracting the climatology. The groupby function identifies corresponding month groups.

olra = olrrt.groupby('time.month').mean('time') - olrMonClim
olra
<xarray.DataArray 'olr' (month: 1, lat: 50, lon: 82)> Size: 16kB
array([[[ 9.78241   ,  9.909576  ,  8.493408  , ...,  2.497589  ,
          1.3457642 , -0.74990845],
        [ 8.7960205 ,  9.101654  ,  9.233459  , ...,  0.71640015,
         -0.6616211 , -2.1523438 ],
        [ 7.6419373 ,  9.022461  , 10.015717  , ...,  1.4698181 ,
         -0.83166504, -3.696045  ],
        ...,
        [-6.5422363 , -5.305298  , -6.9757385 , ..., 10.722809  ,
         12.269043  , 13.60672   ],
        [-5.522644  , -5.337219  , -5.0463257 , ...,  7.494705  ,
          8.71553   ,  9.534042  ],
        [-5.712723  , -4.6646423 , -4.2814636 , ...,  4.3125763 ,
          5.6015167 ,  6.79834   ]]], dtype=float32)
Coordinates:
  * lon      (lon) float32 328B 79.5 80.5 81.5 82.5 ... 157.5 158.5 159.5 160.5
  * lat      (lat) float32 200B -19.5 -18.5 -17.5 -16.5 ... 26.5 27.5 28.5 29.5
  * month    (month) int64 8B 12

We plot the anomaly field.

from matplotlib import pyplot as plt
from cartopy import crs as ccrs   
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cmaps

proj = ccrs.PlateCarree() 
fig,ax = plt.subplots(1,1,subplot_kw={'projection':proj}) 
clevs = [-50,-40,-30,-20,-10,-5,5,10,20,30,40,50,60]
olrPlot = (olra[0,:,:]
               .plot.contourf("lon", "lat",              
                              transform=proj,     
                              ax=ax,            
                              levels=clevs,      
                              cmap=cmaps.sunshine_diff_12lev,     
                              add_colorbar=True,  
                              extend='both',  
                              cbar_kwargs={'orientation': 'horizontal', 'aspect': 30, 'label': r'[W m$^{-2}$]', 'ticks':clevs}) 
                              )

ax.set_extent([lon1,lon2,lats,latn],crs=proj)
ax.set_xticks(np.arange(80,180,20), crs=proj)
ax.set_yticks(np.arange(-20,40,10), crs=proj)   
lon_formatter = LONGITUDE_FORMATTER
lat_formatter = LATITUDE_FORMATTER   
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)  
ax.coastlines()                                              
ax.set_ylabel(' ')             
ax.set_xlabel(' ')
plt.title(' ')
plt.title("Dec. 2017 mean OLR anml.", loc='left')  
plt.show()
_images/b26083c1fdde9b1dc93846db4b71b07c761b78390ff01606625510294954426c.png

Calculate daily climatology and anomaly#

Similar to monthly climatology, we can group the data by time.dayofyear to calculate daily climatology.

olrGB = olr.groupby("time.dayofyear")
olrDayClim = olrGB.mean("time")

olra = olrrt.groupby('time.dayofyear') - olrDayClim

olra
<xarray.DataArray 'olr' (time: 31, lat: 50, lon: 82)> Size: 508kB
array([[[ 15.122101  ,  16.334747  ,  13.599518  , ..., -60.50157   ,
         -79.51955   , -72.87891   ],
        [ 21.117798  ,  19.827057  ,  18.036926  , ..., -60.637543  ,
         -67.54944   , -56.39473   ],
        [ 23.31018   ,  22.088013  ,  21.162445  , ..., -54.980927  ,
         -57.3517    , -61.343475  ],
        ...,
        [ 16.104095  ,  17.379211  ,  15.227692  , ...,  19.058502  ,
          20.518616  ,  17.546478  ],
        [ 10.698151  ,   9.565765  ,   9.262024  , ...,  15.9817505 ,
          16.922485  ,  15.873199  ],
        [  9.011963  ,  10.09436   ,   7.46196   , ...,  25.99179   ,
          22.395844  ,  24.516144  ]],

       [[ 10.832916  ,  11.870026  ,  11.048401  , ...,   7.759735  ,
           3.9926758 , -11.958023  ],
        [ 18.722748  ,  18.02127   ,  16.30832   , ...,  18.752747  ,
          -9.488678  , -19.771454  ],
        [ 29.749542  ,  28.71933   ,  23.702057  , ...,  26.725952  ,
          -5.3364563 , -18.360504  ],
...
        [ 20.139404  ,  17.60962   ,  10.017822  , ...,   1.2883606 ,
          -2.5752106 , -10.758987  ],
        [ 23.506165  ,  20.85086   ,  14.741516  , ...,  -2.9874268 ,
          -4.8914337 ,  -7.4083557 ],
        [ 22.266754  ,  16.122147  ,  13.668915  , ...,   2.6325684 ,
          -4.155838  ,  -3.736206  ]],

       [[ 17.165894  ,  26.317688  ,  26.874603  , ...,  -6.727722  ,
           8.582062  ,   5.428833  ],
        [ 24.211151  ,  29.262146  ,  28.295624  , ...,  -4.6532288 ,
          11.954285  ,  14.010925  ],
        [ 26.614563  ,  29.69284   ,  28.318512  , ...,   7.863983  ,
          19.585327  ,  25.504883  ],
        ...,
        [ 18.78122   ,  14.21608   ,  10.089493  , ...,  12.241455  ,
          12.811462  ,  19.498688  ],
        [ 19.803665  ,  17.64177   ,   8.04245   , ...,  13.380524  ,
          18.12529   ,  19.97647   ],
        [ 16.37706   ,  11.906448  ,   9.84053   , ...,  12.811996  ,
          11.387054  ,  10.267792  ]]], dtype=float32)
Coordinates:
  * time       (time) datetime64[ns] 248B 2017-12-01 2017-12-02 ... 2017-12-31
  * lon        (lon) float32 328B 79.5 80.5 81.5 82.5 ... 158.5 159.5 160.5
  * lat        (lat) float32 200B -19.5 -18.5 -17.5 -16.5 ... 27.5 28.5 29.5
    dayofyear  (time) int64 248B 335 336 337 338 339 340 ... 361 362 363 364 365

Calculate “Smooth” Annual Cycle#

The smooth annual cycle is calculated to preserve the first n harmonic cycles using the Fast Fourier Transform (FFT) method. To perform FFT, we need to use the scipy library. Below is a function adapted from NCAR Command Language (NCL) to calculate the smooth annual cycle. The function requires the unsmoothed annual cycle (clmDay=olrDayClim from the above example) and the number of harmonic cycles nHarm.

from scipy.fft import rfft, irfft

def smthClmDay(clmDay, nHarm):
    nt, ny, nx = clmDay.shape
    cf = rfft(clmDay.values, axis=0)     # xarray.DataArray.values: convert to numpy.ndarray first. 
    cf[nHarm,:,:] = 0.5*cf[nHarm,:,:]    # mini-taper.
    cf[nHarm+1:,:,:] = 0.0               # set all higher coef to 0.0
    icf = irfft(cf, n=nt, axis=0)       # reconstructed series
    clmDaySmth = clmDay.copy(data=icf, deep=False)  
    return(clmDaySmth)

olrDayClim_sm = smthClmDay(olrDayClim, 3)
olrDayClim_sm
<xarray.DataArray 'olr' (dayofyear: 366, lat: 50, lon: 82)> Size: 6MB
array([[[273.80515, 274.10938, 274.2879 , ..., 261.79456, 260.9497 ,
         259.96237],
        [272.50845, 272.31113, 272.69553, ..., 259.65186, 259.05936,
         258.1859 ],
        [270.22046, 270.31717, 270.83304, ..., 256.96295, 255.90952,
         254.99857],
        ...,
        [260.15997, 260.5604 , 261.29028, ..., 257.65775, 257.77252,
         258.23932],
        [256.91174, 257.18832, 256.1388 , ..., 252.47044, 252.64952,
         252.72314],
        [249.13118, 244.76389, 233.80005, ..., 247.78957, 247.84799,
         247.76076]],

       [[273.66   , 273.9643 , 274.14062, ..., 261.48926, 260.66068,
         259.6891 ],
        [272.35568, 272.1524 , 272.53165, ..., 259.30475, 258.74103,
         257.90274],
        [270.05838, 270.1501 , 270.6593 , ..., 256.57834, 255.56288,
         254.67543],
...
        [260.96295, 261.39435, 262.1524 , ..., 257.77435, 257.91214,
         258.39255],
        [257.76877, 258.06982, 256.98972, ..., 252.51714, 252.7237 ,
         252.81175],
        [249.98839, 245.61041, 234.62175, ..., 247.77896, 247.85405,
         247.79373]],

       [[273.94824, 274.25226, 274.4333 , ..., 262.10028, 261.2389 ,
         260.23553],
        [272.65866, 272.46793, 272.85754, ..., 259.9982 , 259.3765 ,
         258.46808],
        [270.37982, 270.48196, 271.00443, ..., 257.34586, 256.2545 ,
         255.32027],
        ...,
        [260.5534 , 260.96948, 261.71365, ..., 257.7139 , 257.84018,
         258.31378],
        [257.33243, 257.6214 , 256.55753, ..., 252.49162, 252.68436,
         252.76512],
        [249.55336, 245.18178, 234.20656, ..., 247.78218, 247.84875,
         247.7748 ]]], dtype=float32)
Coordinates:
  * lon        (lon) float32 328B 79.5 80.5 81.5 82.5 ... 158.5 159.5 160.5
  * lat        (lat) float32 200B -19.5 -18.5 -17.5 -16.5 ... 27.5 28.5 29.5
  * dayofyear  (dayofyear) int64 3kB 1 2 3 4 5 6 7 ... 361 362 363 364 365 366
Attributes:
    standard_name:  toa_outgoing_longwave_flux
    long_name:      NOAA Climate Data Record of Daily Mean Upward Longwave Fl...
    units:          W m-2
    cell_methods:   time: mean area: mean

We compare the smoothed and unsmoothed climatology time series over the Australia monsoon region (115˚-150˚E, 2˚-15˚S).

plt.figure()                   
lp = (olrDayClim.sel(lon=slice(115,150),lat=slice(-15,-2))
                .mean(["lat","lon"])
                .plot.line(x="dayofyear"))
lp_sm = (olrDayClim_sm.sel(lon=slice(115,150),lat=slice(-15,-2))
                      .mean(["lat","lon"])
                      .plot.line(x="dayofyear"))
plt.show()
_images/54b6d2ff4942d2bf1bc2fa0a9f4bf30dd0f08c450786e43d888d25c5232902e8.png

The x-axis is the day of year and the y-axis is the OLR values. The blue curve is the unsmoothed annual cycle, and the orange curve is the smoothed annual cycle by preserving the first three harmonics.