In [1]:
from scipy import ndimage
from scipy import stats
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from matplotlib.ticker import FormatStrFormatter
import cartopy.crs as ccrs
import os
from pathlib import Path
In [2]:
from hypercc.data.box import Box
from hypercc.data.data_set import DataSet
from hypercc.units import unit
from hypercc.filters import (taper_masked_area, gaussian_filter, sobel_filter)
from hypercc.plotting import (
plot_mollweide, plot_orthographic_np, plot_plate_carree,
plot_signal_histogram, earth_plot)
from hypercc.calibration import (calibrate_sobel)
from hyper_canny import cp_edge_thinning, cp_double_threshold
In [3]:
data_folder = Path("/home/bathiany/Sebastian/datamining/edges/obsscan")
#data_set = DataSet([data_folder / 'ERA5_T2m_daily_normalised_JJA_2010_t100.nc'], 't2m')
#data_set = DataSet([data_folder / 'ERA5_T2m_daily_normalised_JJA_2008-2012_t100.nc'], 't2m')
data_set = DataSet([data_folder / 'ERA5_T2m_daily_normalised_JJA_2000-2018_t42.nc'], 't2m')
#data_set = DataSet([data_folder / 'ERA5_T2m_daily_normalised_JJA_t100.nc'], 't2m')
year_ini=2000
daysperyear=92
## smoothing scales
sigma_d = unit('50 km') # space
sigma_t = unit('5 days') # time
### aspect ratio: all weight on time
gamma = 0.00001
scaling_factor = gamma * unit('1 km/year')
sobel_delta_t = unit('1 year')
sobel_delta_d = sobel_delta_t * scaling_factor
sobel_weights = [sobel_delta_t, sobel_delta_d, sobel_delta_d]
Next we define a box
. The box contains all information on the geometry of the data. It loads the lattitudes and longitudes of the grid points from the NetCDF file and computes quantities like resolution.
In [4]:
from datetime import date, timedelta
box = data_set.box
print("({:.6~P}, {:.6~P}, {:.6~P}) per pixel".format(*box.resolution))
for t in box.time[:3]:
print(box.date(t), end=', ')
print(" ...")
dt = box.time[1:] - box.time[:-1]
print("time steps: max", dt.max(), "min", dt.min())
In [5]:
#yearly_data_set = data_set
#box = yearly_data_set.box
data = data_set.data
In [7]:
# integrate data over time
data_int=data*0.0
for timeind in range(0,np.shape(data)[0]):
data_int[timeind,:,:]=np.sum(data[0:timeind,:,:],axis=0)
In [8]:
times=np.shape(data)[0]
years=linspace(year_ini, 2018, 2018-year_ini+1)
days=linspace(1/daysperyear,times/daysperyear,times)+year_ini
In [11]:
# Smoothing
smooth_data = gaussian_filter(box, data_int, [sigma_t, sigma_d, sigma_d])
In [12]:
sb = sobel_filter(box, smooth_data, weight=sobel_weights)
pixel_sb = sobel_filter(box, smooth_data, physical=False)
In [13]:
signal = 1/sb[3]
In [14]:
## quartiles
signalarray = np.asarray(signal).reshape(-1)
signal_no0 = np.ma.masked_equal(signalarray,0)
signal_no0=signal_no0.compressed()
#np.percentile(signal_no0, [25, 50, 75, 100])
In [15]:
## There is no control data for observations => must choose thresholds based on data itself.
# Here, use 75 and 50%ile (quartiles 2 and 3)
upper_threshold=np.percentile(signal_no0, 99.99)
lower_threshold=np.percentile(signal_no0, 95)
In [16]:
from hyper_canny import cp_edge_thinning, cp_double_threshold
In [17]:
# use directions of pixel based sobel transform and magnitudes from calibrated physical sobel.
dat = pixel_sb.transpose([3,2,1,0]).astype('float32')
dat[:,:,:,3] = sb[3].transpose([2,1,0])
mask = cp_edge_thinning(dat)
thinned = mask.transpose([2, 1, 0])
dat = sb.transpose([3,2,1,0]).copy().astype('float32')
In [18]:
edges = cp_double_threshold(data=dat, mask=mask, a=1/upper_threshold, b=1/lower_threshold)
m = edges.transpose([2, 1, 0])
In [68]:
cutoff_length=30 # how many days to either side of the abrupt shift are cut off (the index of the event itself is always cut off)
chunk_max_length=daysperyear*5 # maximum length of chunk of time series to either side of the event
chunk_min_length=daysperyear*2 # minimum length of these chunks
In [69]:
#days = np.array([d.year for d in box.dates])
edges = cp_double_threshold(data=dat, mask=thinned.transpose([2,1,0]), a=1/upper_threshold, b=1/lower_threshold)
m = edges.transpose([2, 1, 0])
idx = np.where(m)
indices=np.asarray(idx)
abruptness3d=m*0.0
shapeidx=np.shape(idx)
nofresults=shapeidx[1]
for result in range(nofresults):
[dim0,dim1,dim2]=indices[:,result]
if m[dim0, dim1, dim2] == 1:
index=dim0
if ( index-cutoff_length >= 0 ) and ( index + cutoff_length + 1 <= np.size(data_int,axis=0) ):
chunk1_data=data_int[0:index-cutoff_length,dim1,dim2]
chunk2_data=data_int[index+cutoff_length+1:,dim1,dim2]
chunk1_days=days[0:index-cutoff_length]
chunk2_days=days[index+cutoff_length+1:]
if size(chunk1_data) > chunk_max_length:
chunk1_start=size(chunk1_data)-chunk_max_length
else:
chunk1_start=0
if size(chunk2_data) > chunk_max_length:
chunk2_end=chunk_max_length
else:
chunk2_end=size(chunk2_data)
chunk1_data_short=chunk1_data[chunk1_start:]
chunk2_data_short=chunk2_data[0:chunk2_end]
N1=size(chunk1_data_short)
N2=size(chunk2_data_short)
if not ((N1 < chunk_min_length) or (N2 < chunk_min_length)):
chunk1_days_short=chunk1_days[chunk1_start:]-days[dim0]
chunk2_days_short=chunk2_days[0:chunk2_end]-days[dim0]
slope_chunk1, intercept_chunk1, r_value, p_value, std_err = stats.linregress(chunk1_days_short, chunk1_data_short)
chunk1_regline=intercept_chunk1 + slope_chunk1*chunk1_days_short
slope_chunk2, intercept_chunk2, r_value, p_value, std_err = stats.linregress(chunk2_days_short, chunk2_data_short)
chunk2_regline=intercept_chunk2 + slope_chunk2*chunk2_days_short
mean_std=(np.nanstd(chunk1_data_short)+np.nanstd(chunk2_data_short))/2
abruptness3d[dim0,dim1,dim2]=abs(intercept_chunk1-intercept_chunk2)/mean_std
abruptness=np.max(abruptness3d,axis=0)
In [70]:
# map of the maximum abruptness at each point
plot_plate_carree(box, abruptness, cmap=my_cmap, vmin=1e-10)
Out[70]:
In [33]:
# map of the maximum abruptness at each point
plot_plate_carree(box, abruptness, cmap=my_cmap, vmin=3)
Out[33]:
In [27]:
mask_max=m*0
for lonind in range(0,np.shape(data)[2]):
for latind in range(0,np.shape(data)[1]):
for timeind in range(0,times):
if abruptness3d[timeind, latind, lonind] == abruptness[latind,lonind]:
mask_max[timeind, latind, lonind] = 1
days_maxpeak=(days[:,None,None]*mask_max).max(axis=0)
maxm=np.nanmax(m, axis=0)
days_maxpeak=days_maxpeak*maxm
minval = np.min(days_maxpeak[np.nonzero(days_maxpeak)])
maxval= np.max(days_maxpeak)
plot_plate_carree(box, days_maxpeak, cmap=my_cmap, vmin=minval, vmax=maxval)
Out[27]:
In [ ]:
fntsiz=40
plt.rcParams['figure.figsize'] = (25, 15)
In [ ]:
# Western Russia: Russian Heatwave 2010
In [36]:
# situation on 1st August 2010
my_cmap = matplotlib.cm.get_cmap('rainbow') #YlGnBu
my_cmap.set_under('w')
plot_plate_carree(box, data[62+daysperyear*(year_ini-2009),:,:], cmap=my_cmap)
Out[36]:
In [149]:
latind=12
lonind=13
In [150]:
ts=data[:,latind,lonind]
abruptness_max=abruptness[latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='2')
## determine year of abrupt shift
index=np.where(abruptness3d[:,latind,lonind]==abruptness_max)
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('Tn', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
xmin=min(days)
xmax=max(days)
xrange=xmax-xmin
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
In [151]:
ts=data_int[:,latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='3')
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('CUSUM(Tn)', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
#ax.text(xpos,ypos,'abruptness: '+ '{:f}'.format(abruptness_max),color='r', size=fntsiz)
In [155]:
# Pacific Ocean: El Nino 2015/16
latind=33
lonind=89
In [156]:
ts=data[:,latind,lonind]
abruptness_max=abruptness[latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='2')
## determine year of abrupt shift
index=np.where(abruptness3d[:,latind,lonind]==abruptness_max)
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('Tn', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
xmin=min(days)
xmax=max(days)
xrange=xmax-xmin
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
In [157]:
ts=data_int[:,latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='3')
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('CUSUM(Tn)', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
#ax.text(xpos,ypos,'abruptness: '+ '{:f}'.format(abruptness_max),color='r', size=fntsiz)
In [158]:
# Southern Alaska
latind=9
lonind=73
In [159]:
ts=data[:,latind,lonind]
abruptness_max=abruptness[latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='2')
## determine year of abrupt shift
index=np.where(abruptness3d[:,latind,lonind]==abruptness_max)
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('Tn', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
xmin=min(days)
xmax=max(days)
xrange=xmax-xmin
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
In [160]:
ts=data_int[:,latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='3')
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('CUSUM(Tn)', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
#ax.text(xpos,ypos,'abruptness: '+ '{:f}'.format(abruptness_max),color='r', size=fntsiz)
In [164]:
# Southern France: Heat wave 2003
latind=16
lonind=1
In [165]:
ts=data[:,latind,lonind]
abruptness_max=abruptness[latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='2')
## determine year of abrupt shift
index=np.where(abruptness3d[:,latind,lonind]==abruptness_max)
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('Tn', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
xmin=min(days)
xmax=max(days)
xrange=xmax-xmin
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
In [166]:
ts=data_int[:,latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='3')
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('CUSUM(Tn)', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
#ax.text(xpos,ypos,'abruptness: '+ '{:f}'.format(abruptness_max),color='r', size=fntsiz)
In [152]:
# Southern Italy: Heat wave 2003
latind=17
lonind=5
In [153]:
ts=data[:,latind,lonind]
abruptness_max=abruptness[latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='2')
## determine year of abrupt shift
index=np.where(abruptness3d[:,latind,lonind]==abruptness_max)
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('Tn', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
xmin=min(days)
xmax=max(days)
xrange=xmax-xmin
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
In [154]:
ts=data_int[:,latind,lonind]
fig = plt.figure()
fig, ax = plt.subplots()
ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
ax.plot(days, ts, 'k', linewidth='3')
ax.axvline(x=days[index], ymin=0, ymax=1, color='r', linestyle="--")
plt.ylabel('CUSUM(Tn)', fontsize=fntsiz)
plt.xlabel('Time', fontsize=fntsiz)
matplotlib.rc('xtick', labelsize=fntsiz)
matplotlib.rc('ytick', labelsize=fntsiz)
ax.tick_params(axis='both', which='major', labelsize=fntsiz)
for year in years:
ax.axvline(x=year, ymin=0, ymax=1, color='gray', linestyle=":")
ymin=min(ts)
ymax=max(ts)
yrange=ymax-ymin
frac=0.025
ypos=ymax-0.025*yrange
xpos=xmin+0.01*xrange
#ax.text(xpos,ypos,'abruptness: '+ '{:f}'.format(abruptness_max),color='r', size=fntsiz)