Source code for envtoolkit.spectral


""" Functions/classes relative to time-series """

from datetime import datetime, timedelta
import numpy as np
import spectrum as sp
import pylab as plt


[docs]def multitaper(xdata, deltat=1., nbandw=3, nfft=None): """ Computes the power spectrum using the multi-taper method This method uses adaptive weighting. Adapted from the `ptmtPH.m <http://www.people.fas.harvard.edu/~phuybers/Mfiles/>`_ script by Peter Huybers :param numpy.array xdata: input data vector. :param float deltat: sampling interval :param int nbandw: time bandwidth product, acceptable values range from 0:.5:length(x)/2-1. 2*nbandw-1 dpss tapers are applied except if nbandw=0 a boxcar window is applied and if nbandw = .5 (or 1) a single dpss taper is applied. :param int nfft: number of frequencies to evaluate P at, default is length(x) for the two-sided transform. :return: a tuple (P, s, ci) with: - P: Power spectrum computed via the multi-taper method. - s: Frequency vector. - ci: 95% confidence intervals. :rtype: tuple """ if xdata.ndim != 1: raise IOError("The dataset must be one-dimensional. " + "It currently has %d dimensions." %xdata.ndim) if nfft is None: nfft = len(xdata) k = np.min([np.round(2*nbandw), len(xdata)]) k = np.max([k-1, 1]) # use of the dpss function of the spectrum library taper, eigval = sp.dpss(len(xdata), NW=nbandw, k=k) if len(xdata) <= nfft: fftcoef = np.abs(np.fft.fft(taper*np.transpose(np.tile(xdata, (k, 1))), n=nfft, axis=0))**2 else: raise IOError('The case of len(xdata)>nfft is not implemented yet.') # Iteration to determine adaptive weights: if k > 1: xdata = np.mat(xdata).T sig2 = xdata.T*xdata/len(xdata) # power spectrum = (fftcoef[:, 0] + fftcoef[:, 1]) / 2. # initial spectrum estimate spectrum_temp = np.zeros(nfft) eigval = np.mat(eigval) k = np.mat(np.ones((1, k))) while np.sum(np.abs(spectrum-spectrum_temp)/nfft) > .0005*sig2/nfft: weights = np.array((np.mat(spectrum).T*k) /(np.mat(spectrum).T*eigval+np.mat(np.ones((nfft, 1)))*np.mat(sig2*(1-eigval)))) # weights weights = weights**2*np.array(np.mat(np.ones((nfft, 1)))*eigval) spectrum_temp = np.sum(weights*fftcoef, axis=1) / np.sum(weights, axis=1) spectrum, spectrum_temp = spectrum_temp, spectrum # swap spectrum and spectrum_tep error = 2*np.sum(weights**2*np.array(np.mat(np.ones((nfft, 1)))*eigval), axis=1)**2 error = error/np.sum(weights**4*np.array(np.mat(np.ones((nfft, 1)))*np.mat(np.array(eigval)**2)), axis=1) select = np.arange(0, (nfft+1)/2+1) spectrum = spectrum[select] freq = np.arange(0, 1/deltat, 1/(nfft*deltat))[select] error = error[select] error = np.array([1/(1-2/(9*error) - 1.96*np.sqrt(2./(9*error)))**3, 1/(1-2/(9*error) + 1.96*np.sqrt(2/(9*error)))**3]) return spectrum, freq, error
[docs]def plot_spectra(xdata, deltat, ferror, spec_type='variance', nbandw=3, **kwargs): """ Plot the power spectrum of a time-series. The power-spectrum is computed by using the :py:func:`envtoolkit.spectra.multitaper` function. Adapted from the JD_spectra script of Julie Deshayes. :param numpy.array xdata: time series whose spectrum to plot :param float deltat: time step of the time series in seconds :param float ferror: the frequency where to do draw the errorbar :param str spec_type: 'variance' for variance spectrum (spectrum, in $unit^2 cpy^{-1}$), else energy spectrum (freq*spectrim, in $unit^2$). :param int nbandw: time bandwidth product (multitaper used 2*nbandw-1 tapers) :param str ylabel: label of the yaxis :param **kwargs: plot additional arguments :return: A tuple (Px,F,Pxc) with: - Px: the spectrum vector - F: the frequency vector - PxC: the errorbar vector :rtype: tuple """ xdata = xdata - np.mean(xdata) [spectrum, freq, error] = multitaper(xdata, nbandw=nbandw) freq = freq*365*86400/deltat # to get the result in cpy spectrum = spectrum/(365*86400/deltat) # to get the result in cpy^{-1} error = error/(365*86400/deltat) # to get the result in cpy^{-1} barmax = error[0, 0]*1e2 barmin = error[1, 0]*1e2 freq = np.ma.masked_equal(freq, 0) if spec_type == 'variance': plt.plot(freq, spectrum, **kwargs) plt.plot([ferror, ferror], [barmin, barmax], marker='_', **kwargs) else: plt.plot(freq, freq*spectrum, **kwargs) plt.grid(True, which='both', axis='x') plt.grid(True, which='major', axis='y') plt.gca().set_yscale('log') plt.gca().set_xscale('log') return spectrum, freq, error
[docs]def plot_slope(spectrum, freq, fmin=None, fmax=None, offy=0, **kwargs): """ Draws the slope of the spectrum. :param numpy.array spectrum: frequency spectrum (output of the :py:func:`plot_spectra` function) :param numpy.array freq: frequency vector (output of the :py:func:`plot_spectra` function) :param axes ax: axes on which to draw the slope :param float fmin: the first frequency on which to compute the slope :param float fmax: the last frequency on which to compute the slope :param float offy: the y-offset of the slope :param dict **kwargs: additional plotting arguments :return: the value of the slope :rtype: float """ # if fmin or fmax are None, we set to the min # and max values of freq if fmin is None: fmin = freq.min() if fmax is None: fmax = freq.max() islope = np.nonzero((freq >= fmin) & (freq <= fmax) & (freq != 0))[0] fout = freq[islope] pxout = spectrum[islope] yslope = np.log(pxout) # pylint: disable=no-member xslope = np.log(fout) # pylint: disable=no-member # calculation of the polyfit pol = np.polyfit(xslope, yslope, 1) # calculation of the trend trend = np.exp(pol[0]*xslope + pol[1] + offy) # plotting the trend plt.plot(fout, trend, **kwargs) return pol[0]
[docs]def plot_ref_slope(fmin, fmax, yinterc, slope=2, **kwargs): """ Draws reference slopes on a log spectral plot. :param float fmin: frequency where to start the reference the slope :param float fmax: frequency where to end the reference the slope :param float yinterc: y intercept of the slopes :param float kval: the slope that we :param dict **kwargs: additional line plotting arguments """ xdata = np.linspace(fmin, fmax, 5) yout = np.log(yinterc) - slope*(np.log(fmin) - np.log(xdata)) # pylint:disable=no-member yout = np.exp(yout) plt.plot(xdata, yout, **kwargs) plt.text(xdata[-1], yout[-1], ' k = -' + str(slope), ha='center', va='center', color='k', bbox=dict(boxstyle="round", fc="0.9"))