pywt icon indicating copy to clipboard operation
pywt copied to clipboard

Replicate the example of Torrence & Compo 1998

Open pinshuai opened this issue 5 years ago • 3 comments

Hi, I am trying to replicate the results from the classic wavelet example of Torrence & Compo, 1998 using pywt.cwt(), but could not get it right. I have pasted the original figure produced using Matlab in Torrence & Compo's paper. Their source codes can be found here. A code snippet is copied here:

load 'sst_nino3.dat'   % input SST time series
sst = sst_nino3;

%------------------------------------------------------ Computation

% normalize by standard deviation (not necessary, but makes it easier
% to compare with plot on Interactive Wavelet page, at
% "http://paos.colorado.edu/research/wavelets/plot/"
variance = std(sst)^2;
sst = (sst - mean(sst))/sqrt(variance) ;

n = length(sst);
dt = 0.25 ;
time = [0:length(sst)-1]*dt + 1871.0 ;  % construct time array
xlim = [1870,2000];  % plotting range
pad = 1;      % pad the time series with zeroes (recommended)
dj = 0.25;    % this will do 4 sub-octaves per octave
s0 = 2*dt;    % this says start at a scale of 6 months
j1 = 7/dj;    % this says do 7 powers-of-two with dj sub-octaves each
lag1 = 0.72;  % lag-1 autocorrelation for red noise background
mother = 'Morlet';

% Wavelet transform:
[wave,period,scale,coi] = wavelet(sst,dt,pad,dj,s0,j1,mother);
power = (abs(wave)).^2 ;        % compute wavelet power spectrum

image

I am aware of an example code using pywt for the same dataset (https://github.com/PyWavelets/pywt/blob/master/demo/cwt_analysis.py). But it was using a different wavelet function (i.e., cmor) instead of the original Morlet function. Also the scales used are not the same. Below is the figure from the example code in pywt.

url = 'http://paos.colorado.edu/research/wavelets/wave_idl/nino3sst.txt'
sst = np.genfromtxt(url, skip_header=19)

t0 = 1871.0
dt = 0.25  # In years

# We also create a time array in years.
N = sst.size
time = np.arange(0, N) * dt + t0

# time, sst = pywt.data.nino()
# dt = time[1] - time[0]

# Taken from http://nicolasfauchereau.github.io/climatecode/posts/wavelet-analysis-in-python/
wavelet = 'cmor1.5-1.0'

scales = np.arange(1, 128)

[cfs, frequencies] = pywt.cwt(sst, scales, wavelet, dt)
power = (abs(cfs)) ** 2

period = 1. / frequencies
levels = [0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8]
f, ax = plt.subplots(figsize=(15, 10))
ax.contourf(time, np.log2(period), np.log2(power), np.log2(levels),
            extend='both')

ax.set_title('%s Wavelet Power Spectrum (%s)' % ('Nino1+2', wavelet))
ax.set_ylabel('Period (years)')
Yticks = 2 ** np.arange(np.ceil(np.log2(period.min())),
                        np.ceil(np.log2(period.max())))
ax.set_yticks(np.log2(Yticks))
ax.set_yticklabels(Yticks)
ax.invert_yaxis()
ylim = ax.get_ylim()
ax.set_ylim(ylim[0], -1)

image

If I change the wavelet function to morlet (wavelet = 'morl'), I got the following plot, which looks quite different from the original plot.

image

So, is cmor1.5-1.0 actually similar to Morlet used in the Matlab code? How to choose the scales in pywt so that I can replicate the Matlab example?

pinshuai avatar Oct 07 '19 21:10 pinshuai

Taking a quick look at the following section of Torrence's code: https://github.com/chris-torrence/wavelets/blob/master/wave_python/waveletFunctions.py#L170-L182

It appears that he defines the wavelet directly in the frequency domain (whereas the corresponding formulas in PyWavelets are in the time domain). The fact that the waveform is being multiplied by a Heaviside (step) function in frequency is going to make it complex-valued in the time domain (If you recall FFT theory, real-valued functions would have conjugate-symmetry in the frequency domain)

Based on that, I would say that "Morlet" in Torrence's toolbox corresponds to "cmor" in PyWavelets. I haven't tried to verify if the bandwidth / center frequency constants exactly match cmor1.5-1.0, but it is definitely a "cmor" wavelet and not a "morl" wavelet.

grlee77 avatar Oct 07 '19 22:10 grlee77

Thank you for the clarification. I am not very familiar with the FFT theory, but I think they must use a cmor wavelet since the plot generated by cmor1.5-1.0 look very similar to the original spectral plot.

In the original Matlab code, they used this equation to generate the scales.

In your example, you used scales = np.arange(1, 128) to generate a uniform distributed scales. Do you have any reasoning behind that? Or do you have any suggestions on choosing appropriate scales? Thanks.

pinshuai avatar Oct 08 '19 00:10 pinshuai

Having the same issue. I'm not sure how to choose: 1. the scales based on a frequency vector, 2. how to align the two outputs. Any help on this would be appreciated.

khider avatar Nov 04 '21 21:11 khider