Hovmöller diagrams - Seasonal variation of temperature over West-Africa

HV_Diagram_plot_N3

This post discuss Hovmöller diagrams drawing in python. Hovmöller diagrams is used to plot the time evolution of vertical profiles of scalar quantities such as temperature, density, or concentrations of constituents in the atmosphere or ocean. The axes of a Hovmöller diagram are typically longitude or latitude and time with the value of some field represented through color or shading.

For this exercice Monthly variation of temperature over West-Africa following a latiude and longitude gradient will be ploted.

Load packages

In [1]:
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import metpy.calc as mpcalc
import numpy as np
import xarray as xr
import pandas as pd
In [3]:
#Setting Working directory
import os
os.chdir("D:/Weather_Processed_data" )
os.getcwd()
Out[3]:
'D:\\Weather_Processed_data'

Import data

Near-surface temperature from CRU TS4.04 daily datasets is used. It can be downloaded from this :http://dap.ceda.ac.uk/thredds/fileServer/badc/cru/data/cru_ts/cru_ts_4.04/data/tmp/cru_ts4.04.1901.2019.tmp.dat.nc.gz

In [4]:
# import the dataset
df = xr.open_dataset('cru_ts4.04.1901.2019.tmp.dat.nc')
In [5]:
df #print content
Out[5]:
Show/Hide data repr Show/Hide attributes
xarray.Dataset
    • lat: 360
    • lon: 720
    • time: 1428
    • lon
      (lon)
      float32
      -179.75 -179.25 ... 179.25 179.75
      long_name :
      longitude
      units :
      degrees_east
      array([-179.75, -179.25, -178.75, ...,  178.75,  179.25,  179.75],
            dtype=float32)
    • lat
      (lat)
      float32
      -89.75 -89.25 ... 89.25 89.75
      long_name :
      latitude
      units :
      degrees_north
      array([-89.75, -89.25, -88.75, ...,  88.75,  89.25,  89.75], dtype=float32)
    • time
      (time)
      datetime64[ns]
      1901-01-16 ... 2019-12-16
      long_name :
      time
      array(['1901-01-16T00:00:00.000000000', '1901-02-15T00:00:00.000000000',
             '1901-03-16T00:00:00.000000000', ..., '2019-10-16T00:00:00.000000000',
             '2019-11-16T00:00:00.000000000', '2019-12-16T00:00:00.000000000'],
            dtype='datetime64[ns]')
    • tmp
      (time, lat, lon)
      float32
      ...
      long_name :
      near-surface temperature
      units :
      degrees Celsius
      correlation_decay_distance :
      1200.0
      [370137600 values with dtype=float32]
    • stn
      (time, lat, lon)
      float64
      ...
      description :
      number of stations contributing to each datum
      [370137600 values with dtype=float64]
  • Conventions :
    CF-1.4
    title :
    CRU TS4.04 Mean Temperature
    institution :
    Data held at British Atmospheric Data Centre, RAL, UK.
    source :
    Run ID = 2004151855. Data generated from:tmp.2004011744.dtb
    history :
    Wed 15 Apr 2020 19:58:33 BST : User ianharris : Program makegridsauto.for called by update.for
    references :
    Information on the data is available at http://badc.nerc.ac.uk/data/cru/
    comment :
    Access to these data is available to any registered CEDA user.
    contact :
    support@ceda.ac.uk

Study area - West-Africa

In [6]:
# Latitude and Longitude extent of the study area
lat_slice = slice(5,30)
lon_slice = slice(-15,30)
# Get data, selecting time, lat/lon slice
data=df['tmp'].sel(lat=lat_slice,lon=lon_slice)
In [7]:
# Start figure
fig = plt.figure(figsize=(10,50))

# Use gridspec to help size elements of plot; small top plot and big bottom plot
gs = gridspec.GridSpec(nrows=2, ncols=1, hspace=0.03)

# Tick labels

# Define x, y axis labels
x_tick_labels = [u'20\N{DEGREE SIGN}W',u'15 \N{DEGREE SIGN}W', u'10\N{DEGREE SIGN}W', u'5\N{DEGREE SIGN}W',
                 u'0\N{DEGREE SIGN}',u'5\N{DEGREE SIGN}E',u'10\N{DEGREE SIGN}E',u'15\N{DEGREE SIGN}E',
                 u'20\N{DEGREE SIGN}E',
                 u'25\N{DEGREE SIGN}E',u'30\N{DEGREE SIGN}E']


y_tick_labels = [u'5\N{DEGREE SIGN}N', u'10 \N{DEGREE SIGN}N',
                 u'15\N{DEGREE SIGN}N',u'20\N{DEGREE SIGN}N',u'25\N{DEGREE SIGN}N',u'30\N{DEGREE SIGN}N']
          
    
# Set geographic reference
ax1 = fig.add_subplot(gs[0, 0], projection=ccrs.PlateCarree(central_longitude=0))
ax1.set_extent([-15,30,0,30], ccrs.PlateCarree(central_longitude=0)) #lon min, max, lat min, max
ax1.set_xticks([-20,-15,-10, -5, 0, 5,10,20,25,30])
ax1.set_xticklabels(x_tick_labels)
ax1.set_yticks([5,10,15,20,25,30])
ax1.set_yticklabels(y_tick_labels)
ax1.grid(linestyle='dotted', linewidth=2)

# Add geopolitical boundaries for map reference
ax1.add_feature(cfeature.COASTLINE.with_scale('50m'))
ax1.add_feature(cfeature.LAKES.with_scale('50m'), color='black', linewidths=0.5)
ax1.add_feature(cfeature.BORDERS, linewidths=1)


# Set some titles
plt.title('Hovmoller Diagram over West-Africa', loc='left',fontsize=15)

plt.show()
C:\Users\lenovo\Anaconda3\lib\site-packages\cartopy\io\__init__.py:260: DownloadWarning: Downloading: http://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_boundary_lines_land.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)

Compute monthly mean around the datasets

In [8]:
monmean=data.groupby('time.month').mean()
monmean
Out[8]:
Show/Hide data repr Show/Hide attributes
xarray.DataArray
'tmp'
  • month: 12
  • lat: 50
  • lon: 90
  • nan nan nan nan nan ... 14.784033 14.591595 14.117646 13.945382
    array([[[       nan,        nan,        nan, ..., 26.329407 ,
             26.811762 , 26.909239 ],
            [       nan,        nan,        nan, ..., 26.733612 ,
             26.92773  , 27.318487 ],
            [       nan,        nan,        nan, ..., 27.350418 ,
             27.936129 , 28.130255 ],
            ...,
            [       nan, 15.848743 , 15.1327715, ..., 12.519327 ,
             12.428573 , 12.830253 ],
            [       nan,        nan, 15.131092 , ..., 12.877311 ,
             12.298319 , 12.105881 ],
            [       nan,        nan,        nan, ..., 13.259663 ,
             12.673945 , 12.483192 ]],
    
           [[       nan,        nan,        nan, ..., 27.357983 ,
             27.759665 , 27.949577 ],
            [       nan,        nan,        nan, ..., 27.868904 ,
             28.065542 , 28.559662 ],
            [       nan,        nan,        nan, ..., 28.689913 ,
             29.265549 , 29.366385 ],
            ...,
            [       nan, 16.39832  , 15.793272 , ..., 13.731933 ,
             13.736974 , 14.036977 ],
            [       nan,        nan, 15.690756 , ..., 13.999159 ,
             13.302521 , 13.214286 ],
            [       nan,        nan,        nan, ..., 14.067228 ,
             13.589075 , 13.390757 ]],
    
           [[       nan,        nan,        nan, ..., 27.65294  ,
             28.151255 , 28.248732 ],
            [       nan,        nan,        nan, ..., 28.42857  ,
             28.627737 , 29.11597  ],
            [       nan,        nan,        nan, ..., 29.408398 ,
             29.998322 , 30.200005 ],
            ...,
            [       nan, 17.618486 , 17.023531 , ..., 16.79496  ,
             16.71513  , 17.02857  ],
            [       nan,        nan, 16.824371 , ..., 16.674793 ,
             16.09496  , 16.018484 ],
            [       nan,        nan,        nan, ..., 16.557146 ,
             16.084036 , 15.873112 ]],
    
           ...,
    
           [[       nan,        nan,        nan, ..., 24.918493 ,
             25.320168 , 25.31177  ],
            [       nan,        nan,        nan, ..., 25.520166 ,
             25.710089 , 26.110088 ],
            [       nan,        nan,        nan, ..., 26.423527 ,
             27.012606 , 27.112612 ],
            ...,
            [       nan, 21.93025  , 21.238653 , ..., 23.309244 ,
             23.319334 , 23.707567 ],
            [       nan,        nan, 21.032778 , ..., 23.292442 ,
             22.873106 , 22.789915 ],
            [       nan,        nan,        nan, ..., 23.25462  ,
             22.86219  , 22.874792 ]],
    
           [[       nan,        nan,        nan, ..., 25.497478 ,
             25.783184 , 25.776468 ],
            [       nan,        nan,        nan, ..., 26.090755 ,
             26.184875 , 26.572273 ],
            [       nan,        nan,        nan, ..., 26.906729 ,
             27.48236  , 27.475634 ],
            ...,
            [       nan, 19.6521   , 19.05798  , ..., 18.378992 ,
             18.394117 , 18.791597 ],
            [       nan,        nan, 18.857143 , ..., 18.637821 ,
             18.152103 , 18.066385 ],
            [       nan,        nan,        nan, ..., 18.89664  ,
             18.423527 , 18.353779 ]],
    
           [[       nan,        nan,        nan, ..., 25.636984 ,
             26.136148 , 26.215971 ],
            [       nan,        nan,        nan, ..., 26.051266 ,
             26.342854 , 26.729412 ],
            [       nan,        nan,        nan, ..., 26.770586 ,
             27.36134  , 27.546232 ],
            ...,
            [       nan, 17.111767 , 16.428572 , ..., 13.858821 ,
             13.771425 , 14.178992 ],
            [       nan,        nan, 16.32437  , ..., 14.220174 ,
             13.642017 , 13.556305 ],
            [       nan,        nan,        nan, ..., 14.591595 ,
             14.117646 , 13.945382 ]]], dtype=float32)
    • lon
      (lon)
      float32
      -14.75 -14.25 ... 29.25 29.75
      long_name :
      longitude
      units :
      degrees_east
      array([-14.75, -14.25, -13.75, -13.25, -12.75, -12.25, -11.75, -11.25, -10.75,
             -10.25,  -9.75,  -9.25,  -8.75,  -8.25,  -7.75,  -7.25,  -6.75,  -6.25,
              -5.75,  -5.25,  -4.75,  -4.25,  -3.75,  -3.25,  -2.75,  -2.25,  -1.75,
              -1.25,  -0.75,  -0.25,   0.25,   0.75,   1.25,   1.75,   2.25,   2.75,
               3.25,   3.75,   4.25,   4.75,   5.25,   5.75,   6.25,   6.75,   7.25,
               7.75,   8.25,   8.75,   9.25,   9.75,  10.25,  10.75,  11.25,  11.75,
              12.25,  12.75,  13.25,  13.75,  14.25,  14.75,  15.25,  15.75,  16.25,
              16.75,  17.25,  17.75,  18.25,  18.75,  19.25,  19.75,  20.25,  20.75,
              21.25,  21.75,  22.25,  22.75,  23.25,  23.75,  24.25,  24.75,  25.25,
              25.75,  26.25,  26.75,  27.25,  27.75,  28.25,  28.75,  29.25,  29.75],
            dtype=float32)
    • lat
      (lat)
      float32
      5.25 5.75 6.25 ... 29.25 29.75
      long_name :
      latitude
      units :
      degrees_north
      array([ 5.25,  5.75,  6.25,  6.75,  7.25,  7.75,  8.25,  8.75,  9.25,  9.75,
             10.25, 10.75, 11.25, 11.75, 12.25, 12.75, 13.25, 13.75, 14.25, 14.75,
             15.25, 15.75, 16.25, 16.75, 17.25, 17.75, 18.25, 18.75, 19.25, 19.75,
             20.25, 20.75, 21.25, 21.75, 22.25, 22.75, 23.25, 23.75, 24.25, 24.75,
             25.25, 25.75, 26.25, 26.75, 27.25, 27.75, 28.25, 28.75, 29.25, 29.75],
            dtype=float32)
    • month
      (month)
      int64
      1 2 3 4 5 6 7 8 9 10 11 12
      array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12], dtype=int64)

Compute weights over latitude dimension

In [9]:
# Get times and make array of datetime objects
month = monmean.month.values

# Compute weights and take weighted average over latitude dimension
weights = np.cos(np.deg2rad(monmean.lat.values))
avg_data = (monmean * weights[None, :, None]).sum(dim='lat') / np.sum(weights)

#Specify longitude values for chosen domain
lons = monmean.lon.values

Monthly Longitudunal over time series

In [10]:
# Define x, y axis labels
x_tick_labels = [u'15 \N{DEGREE SIGN}W', u'10\N{DEGREE SIGN}W', u'5\N{DEGREE SIGN}W',
                 u'0\N{DEGREE SIGN}',
                 u'5\N{DEGREE SIGN}E',u'10\N{DEGREE SIGN}E',u'15\N{DEGREE SIGN}E',u'20\N{DEGREE SIGN}E',
                u'25\N{DEGREE SIGN}E',u'30\N{DEGREE SIGN}E']

y_tick_labels=['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEV']

fig = plt.figure(figsize=(10,7))
#gs = gridspec.GridSpec(nrows=1, ncols=1, hspace=0.03)

#Bottom plot for Hovmoller diagram
ax= plt.axes()
ax.invert_yaxis()  # Reverse the time order to do oldest first

# Plot of chosen variable averaged over latitude and slightly smoothed
z=mpcalc.smooth_n_point(avg_data, 9, 2)
clevs= np.arange(z.min(),z.max(),1) # color levels

#cf = ax.contourf(lons,month, mpcalc.smooth_n_point(avg_data, 9, 2), clevs, cmap=plt.cm.bwr, extend='both')
cf = ax.contourf(lons,month, mpcalc.smooth_n_point(avg_data, 9, 2), clevs, cmap=plt.cm.RdBu_r, extend='both')

cs = ax.contour(lons,month, mpcalc.smooth_n_point(avg_data, 9, 2), clevs, colors='k', linewidths=False)
cbar = plt.colorbar(cf, orientation='horizontal', pad=0.1, aspect=50, extend=True)
cbar.set_label('Deg [C]')

# Make some ticks and tick labels
ax.set_xticks([-15,-10, -5, 0, 5, 10,15,20,25,30])
ax.set_xticklabels(x_tick_labels)
ax.set_yticks(month)
ax.set_yticklabels(y_tick_labels)


#import time series
time=df.time.values.astype('datetime64[ms]').astype('O')

# Set some titles
plt.title('Mean Temperature', loc='left', fontsize=10)
plt.title('Time Range: {0:%Y/%m/%d} - {1:%Y/%m/%d}'.format(time[0], time[-1]),
          loc='right', fontsize=10)

plt.show()
In [11]:
#Get more Colormaps by doing a tab completion on the plt.cm module: plt.cm.<TAB>

Monthly Latidunal over time series

In [12]:
# Compute weights and take weighted average over latitude dimension
weights = np.cos(np.deg2rad(monmean.lon.values))
avg_data = (monmean * weights[None,None,:]).sum(dim='lon') / np.sum(weights)

# Specify latitude values for chosen domain
lats = data.lat.values
In [13]:
#Reordering dimensions
avg_data=avg_data.transpose('lat', 'month')


# set figure dimensions and axis
fig = plt.figure(figsize=(15,8))
ax2 = plt.axes()

# Plot of chosen variable averaged over longitude and slightly smoothed
z=mpcalc.smooth_n_point(avg_data, 9, 2)
clevs= np.arange(z.min(),z.max(),1) # color levels
cf = ax2.contourf(month,lats,z, clevs, cmap=plt.cm.Spectral_r, extend='both')


cs = ax2.contour(month,lats,z, clevs, colors='k', linewidths=0)
cbar = plt.colorbar(cf, orientation='vertical', pad=0.04, aspect=50, extendrect=True)
cbar.set_label('Deg [C]')


#Define x  and y labels plots
x_tick_labels=['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEV']
ax2.set_xticks(month)
ax2.set_xticklabels(x_tick_labels)

# Set some titles
plt.title('Mean Temperature', loc='left', fontsize=10)
plt.title('Time Range: {0:%Y/%m/%d} - {1:%Y/%m/%d}'.format(time[0], time[-1]),
          loc='right', fontsize=10)
plt.ylabel("Latitude \N{DEGREE SIGN} N", Fontsize=10)
plt.xlabel("Monthly Mean", Fontsize=10)

plt.show()
In [14]:
#part of this code is inspired from this post: https://unidata.github.io/python-gallery/examples/Hovmoller_Diagram.html

5 comments: