123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914 |
- """
- Numerical Python functions written for compatibility with MATLAB
- commands with the same names. Most numerical Python functions can be found in
- the `NumPy`_ and `SciPy`_ libraries. What remains here is code for performing
- spectral computations and kernel density estimations.
- .. _NumPy: https://numpy.org
- .. _SciPy: https://www.scipy.org
- Spectral functions
- ------------------
- `cohere`
- Coherence (normalized cross spectral density)
- `csd`
- Cross spectral density using Welch's average periodogram
- `detrend`
- Remove the mean or best fit line from an array
- `psd`
- Power spectral density using Welch's average periodogram
- `specgram`
- Spectrogram (spectrum over segments of time)
- `complex_spectrum`
- Return the complex-valued frequency spectrum of a signal
- `magnitude_spectrum`
- Return the magnitude of the frequency spectrum of a signal
- `angle_spectrum`
- Return the angle (wrapped phase) of the frequency spectrum of a signal
- `phase_spectrum`
- Return the phase (unwrapped angle) of the frequency spectrum of a signal
- `detrend_mean`
- Remove the mean from a line.
- `detrend_linear`
- Remove the best fit line from a line.
- `detrend_none`
- Return the original line.
- """
- import functools
- from numbers import Number
- import numpy as np
- from matplotlib import _api, _docstring, cbook
- def window_hanning(x):
- """
- Return *x* times the Hanning (or Hann) window of len(*x*).
- See Also
- --------
- window_none : Another window algorithm.
- """
- return np.hanning(len(x))*x
- def window_none(x):
- """
- No window function; simply return *x*.
- See Also
- --------
- window_hanning : Another window algorithm.
- """
- return x
- def detrend(x, key=None, axis=None):
- """
- Return *x* with its trend removed.
- Parameters
- ----------
- x : array or sequence
- Array or sequence containing the data.
- key : {'default', 'constant', 'mean', 'linear', 'none'} or function
- The detrending algorithm to use. 'default', 'mean', and 'constant' are
- the same as `detrend_mean`. 'linear' is the same as `detrend_linear`.
- 'none' is the same as `detrend_none`. The default is 'mean'. See the
- corresponding functions for more details regarding the algorithms. Can
- also be a function that carries out the detrend operation.
- axis : int
- The axis along which to do the detrending.
- See Also
- --------
- detrend_mean : Implementation of the 'mean' algorithm.
- detrend_linear : Implementation of the 'linear' algorithm.
- detrend_none : Implementation of the 'none' algorithm.
- """
- if key is None or key in ['constant', 'mean', 'default']:
- return detrend(x, key=detrend_mean, axis=axis)
- elif key == 'linear':
- return detrend(x, key=detrend_linear, axis=axis)
- elif key == 'none':
- return detrend(x, key=detrend_none, axis=axis)
- elif callable(key):
- x = np.asarray(x)
- if axis is not None and axis + 1 > x.ndim:
- raise ValueError(f'axis(={axis}) out of bounds')
- if (axis is None and x.ndim == 0) or (not axis and x.ndim == 1):
- return key(x)
- # try to use the 'axis' argument if the function supports it,
- # otherwise use apply_along_axis to do it
- try:
- return key(x, axis=axis)
- except TypeError:
- return np.apply_along_axis(key, axis=axis, arr=x)
- else:
- raise ValueError(
- f"Unknown value for key: {key!r}, must be one of: 'default', "
- f"'constant', 'mean', 'linear', or a function")
- def detrend_mean(x, axis=None):
- """
- Return *x* minus the mean(*x*).
- Parameters
- ----------
- x : array or sequence
- Array or sequence containing the data
- Can have any dimensionality
- axis : int
- The axis along which to take the mean. See `numpy.mean` for a
- description of this argument.
- See Also
- --------
- detrend_linear : Another detrend algorithm.
- detrend_none : Another detrend algorithm.
- detrend : A wrapper around all the detrend algorithms.
- """
- x = np.asarray(x)
- if axis is not None and axis+1 > x.ndim:
- raise ValueError('axis(=%s) out of bounds' % axis)
- return x - x.mean(axis, keepdims=True)
- def detrend_none(x, axis=None):
- """
- Return *x*: no detrending.
- Parameters
- ----------
- x : any object
- An object containing the data
- axis : int
- This parameter is ignored.
- It is included for compatibility with detrend_mean
- See Also
- --------
- detrend_mean : Another detrend algorithm.
- detrend_linear : Another detrend algorithm.
- detrend : A wrapper around all the detrend algorithms.
- """
- return x
- def detrend_linear(y):
- """
- Return *x* minus best fit line; 'linear' detrending.
- Parameters
- ----------
- y : 0-D or 1-D array or sequence
- Array or sequence containing the data
- See Also
- --------
- detrend_mean : Another detrend algorithm.
- detrend_none : Another detrend algorithm.
- detrend : A wrapper around all the detrend algorithms.
- """
- # This is faster than an algorithm based on linalg.lstsq.
- y = np.asarray(y)
- if y.ndim > 1:
- raise ValueError('y cannot have ndim > 1')
- # short-circuit 0-D array.
- if not y.ndim:
- return np.array(0., dtype=y.dtype)
- x = np.arange(y.size, dtype=float)
- C = np.cov(x, y, bias=1)
- b = C[0, 1]/C[0, 0]
- a = y.mean() - b*x.mean()
- return y - (b*x + a)
- def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None,
- window=None, noverlap=None, pad_to=None,
- sides=None, scale_by_freq=None, mode=None):
- """
- Private helper implementing the common parts between the psd, csd,
- spectrogram and complex, magnitude, angle, and phase spectrums.
- """
- if y is None:
- # if y is None use x for y
- same_data = True
- else:
- # The checks for if y is x are so that we can use the same function to
- # implement the core of psd(), csd(), and spectrogram() without doing
- # extra calculations. We return the unaveraged Pxy, freqs, and t.
- same_data = y is x
- if Fs is None:
- Fs = 2
- if noverlap is None:
- noverlap = 0
- if detrend_func is None:
- detrend_func = detrend_none
- if window is None:
- window = window_hanning
- # if NFFT is set to None use the whole signal
- if NFFT is None:
- NFFT = 256
- if noverlap >= NFFT:
- raise ValueError('noverlap must be less than NFFT')
- if mode is None or mode == 'default':
- mode = 'psd'
- _api.check_in_list(
- ['default', 'psd', 'complex', 'magnitude', 'angle', 'phase'],
- mode=mode)
- if not same_data and mode != 'psd':
- raise ValueError("x and y must be equal if mode is not 'psd'")
- # Make sure we're dealing with a numpy array. If y and x were the same
- # object to start with, keep them that way
- x = np.asarray(x)
- if not same_data:
- y = np.asarray(y)
- if sides is None or sides == 'default':
- if np.iscomplexobj(x):
- sides = 'twosided'
- else:
- sides = 'onesided'
- _api.check_in_list(['default', 'onesided', 'twosided'], sides=sides)
- # zero pad x and y up to NFFT if they are shorter than NFFT
- if len(x) < NFFT:
- n = len(x)
- x = np.resize(x, NFFT)
- x[n:] = 0
- if not same_data and len(y) < NFFT:
- n = len(y)
- y = np.resize(y, NFFT)
- y[n:] = 0
- if pad_to is None:
- pad_to = NFFT
- if mode != 'psd':
- scale_by_freq = False
- elif scale_by_freq is None:
- scale_by_freq = True
- # For real x, ignore the negative frequencies unless told otherwise
- if sides == 'twosided':
- numFreqs = pad_to
- if pad_to % 2:
- freqcenter = (pad_to - 1)//2 + 1
- else:
- freqcenter = pad_to//2
- scaling_factor = 1.
- elif sides == 'onesided':
- if pad_to % 2:
- numFreqs = (pad_to + 1)//2
- else:
- numFreqs = pad_to//2 + 1
- scaling_factor = 2.
- if not np.iterable(window):
- window = window(np.ones(NFFT, x.dtype))
- if len(window) != NFFT:
- raise ValueError(
- "The window length must match the data's first dimension")
- result = np.lib.stride_tricks.sliding_window_view(
- x, NFFT, axis=0)[::NFFT - noverlap].T
- result = detrend(result, detrend_func, axis=0)
- result = result * window.reshape((-1, 1))
- result = np.fft.fft(result, n=pad_to, axis=0)[:numFreqs, :]
- freqs = np.fft.fftfreq(pad_to, 1/Fs)[:numFreqs]
- if not same_data:
- # if same_data is False, mode must be 'psd'
- resultY = np.lib.stride_tricks.sliding_window_view(
- y, NFFT, axis=0)[::NFFT - noverlap].T
- resultY = detrend(resultY, detrend_func, axis=0)
- resultY = resultY * window.reshape((-1, 1))
- resultY = np.fft.fft(resultY, n=pad_to, axis=0)[:numFreqs, :]
- result = np.conj(result) * resultY
- elif mode == 'psd':
- result = np.conj(result) * result
- elif mode == 'magnitude':
- result = np.abs(result) / window.sum()
- elif mode == 'angle' or mode == 'phase':
- # we unwrap the phase later to handle the onesided vs. twosided case
- result = np.angle(result)
- elif mode == 'complex':
- result /= window.sum()
- if mode == 'psd':
- # Also include scaling factors for one-sided densities and dividing by
- # the sampling frequency, if desired. Scale everything, except the DC
- # component and the NFFT/2 component:
- # if we have a even number of frequencies, don't scale NFFT/2
- if not NFFT % 2:
- slc = slice(1, -1, None)
- # if we have an odd number, just don't scale DC
- else:
- slc = slice(1, None, None)
- result[slc] *= scaling_factor
- # MATLAB divides by the sampling frequency so that density function
- # has units of dB/Hz and can be integrated by the plotted frequency
- # values. Perform the same scaling here.
- if scale_by_freq:
- result /= Fs
- # Scale the spectrum by the norm of the window to compensate for
- # windowing loss; see Bendat & Piersol Sec 11.5.2.
- result /= (window**2).sum()
- else:
- # In this case, preserve power in the segment, not amplitude
- result /= window.sum()**2
- t = np.arange(NFFT/2, len(x) - NFFT/2 + 1, NFFT - noverlap)/Fs
- if sides == 'twosided':
- # center the frequency range at zero
- freqs = np.roll(freqs, -freqcenter, axis=0)
- result = np.roll(result, -freqcenter, axis=0)
- elif not pad_to % 2:
- # get the last value correctly, it is negative otherwise
- freqs[-1] *= -1
- # we unwrap the phase here to handle the onesided vs. twosided case
- if mode == 'phase':
- result = np.unwrap(result, axis=0)
- return result, freqs, t
- def _single_spectrum_helper(
- mode, x, Fs=None, window=None, pad_to=None, sides=None):
- """
- Private helper implementing the commonality between the complex, magnitude,
- angle, and phase spectrums.
- """
- _api.check_in_list(['complex', 'magnitude', 'angle', 'phase'], mode=mode)
- if pad_to is None:
- pad_to = len(x)
- spec, freqs, _ = _spectral_helper(x=x, y=None, NFFT=len(x), Fs=Fs,
- detrend_func=detrend_none, window=window,
- noverlap=0, pad_to=pad_to,
- sides=sides,
- scale_by_freq=False,
- mode=mode)
- if mode != 'complex':
- spec = spec.real
- if spec.ndim == 2 and spec.shape[1] == 1:
- spec = spec[:, 0]
- return spec, freqs
- # Split out these keyword docs so that they can be used elsewhere
- _docstring.interpd.update(
- Spectral="""\
- Fs : float, default: 2
- The sampling frequency (samples per time unit). It is used to calculate
- the Fourier frequencies, *freqs*, in cycles per time unit.
- window : callable or ndarray, default: `.window_hanning`
- A function or a vector of length *NFFT*. To create window vectors see
- `.window_hanning`, `.window_none`, `numpy.blackman`, `numpy.hamming`,
- `numpy.bartlett`, `scipy.signal`, `scipy.signal.get_window`, etc. If a
- function is passed as the argument, it must take a data segment as an
- argument and return the windowed version of the segment.
- sides : {'default', 'onesided', 'twosided'}, optional
- Which sides of the spectrum to return. 'default' is one-sided for real
- data and two-sided for complex data. 'onesided' forces the return of a
- one-sided spectrum, while 'twosided' forces two-sided.""",
- Single_Spectrum="""\
- pad_to : int, optional
- The number of points to which the data segment is padded when performing
- the FFT. While not increasing the actual resolution of the spectrum (the
- minimum distance between resolvable peaks), this can give more points in
- the plot, allowing for more detail. This corresponds to the *n* parameter
- in the call to `~numpy.fft.fft`. The default is None, which sets *pad_to*
- equal to the length of the input signal (i.e. no padding).""",
- PSD="""\
- pad_to : int, optional
- The number of points to which the data segment is padded when performing
- the FFT. This can be different from *NFFT*, which specifies the number
- of data points used. While not increasing the actual resolution of the
- spectrum (the minimum distance between resolvable peaks), this can give
- more points in the plot, allowing for more detail. This corresponds to
- the *n* parameter in the call to `~numpy.fft.fft`. The default is None,
- which sets *pad_to* equal to *NFFT*
- NFFT : int, default: 256
- The number of data points used in each block for the FFT. A power 2 is
- most efficient. This should *NOT* be used to get zero padding, or the
- scaling of the result will be incorrect; use *pad_to* for this instead.
- detrend : {'none', 'mean', 'linear'} or callable, default: 'none'
- The function applied to each segment before fft-ing, designed to remove
- the mean or linear trend. Unlike in MATLAB, where the *detrend* parameter
- is a vector, in Matplotlib it is a function. The :mod:`~matplotlib.mlab`
- module defines `.detrend_none`, `.detrend_mean`, and `.detrend_linear`,
- but you can use a custom function as well. You can also use a string to
- choose one of the functions: 'none' calls `.detrend_none`. 'mean' calls
- `.detrend_mean`. 'linear' calls `.detrend_linear`.
- scale_by_freq : bool, default: True
- Whether the resulting density values should be scaled by the scaling
- frequency, which gives density in units of 1/Hz. This allows for
- integration over the returned frequency values. The default is True for
- MATLAB compatibility.""")
- @_docstring.dedent_interpd
- def psd(x, NFFT=None, Fs=None, detrend=None, window=None,
- noverlap=None, pad_to=None, sides=None, scale_by_freq=None):
- r"""
- Compute the power spectral density.
- The power spectral density :math:`P_{xx}` by Welch's average
- periodogram method. The vector *x* is divided into *NFFT* length
- segments. Each segment is detrended by function *detrend* and
- windowed by function *window*. *noverlap* gives the length of
- the overlap between segments. The :math:`|\mathrm{fft}(i)|^2`
- of each segment :math:`i` are averaged to compute :math:`P_{xx}`.
- If len(*x*) < *NFFT*, it will be zero padded to *NFFT*.
- Parameters
- ----------
- x : 1-D array or sequence
- Array or sequence containing the data
- %(Spectral)s
- %(PSD)s
- noverlap : int, default: 0 (no overlap)
- The number of points of overlap between segments.
- Returns
- -------
- Pxx : 1-D array
- The values for the power spectrum :math:`P_{xx}` (real valued)
- freqs : 1-D array
- The frequencies corresponding to the elements in *Pxx*
- References
- ----------
- Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John
- Wiley & Sons (1986)
- See Also
- --------
- specgram
- `specgram` differs in the default overlap; in not returning the mean of
- the segment periodograms; and in returning the times of the segments.
- magnitude_spectrum : returns the magnitude spectrum.
- csd : returns the spectral density between two signals.
- """
- Pxx, freqs = csd(x=x, y=None, NFFT=NFFT, Fs=Fs, detrend=detrend,
- window=window, noverlap=noverlap, pad_to=pad_to,
- sides=sides, scale_by_freq=scale_by_freq)
- return Pxx.real, freqs
- @_docstring.dedent_interpd
- def csd(x, y, NFFT=None, Fs=None, detrend=None, window=None,
- noverlap=None, pad_to=None, sides=None, scale_by_freq=None):
- """
- Compute the cross-spectral density.
- The cross spectral density :math:`P_{xy}` by Welch's average
- periodogram method. The vectors *x* and *y* are divided into
- *NFFT* length segments. Each segment is detrended by function
- *detrend* and windowed by function *window*. *noverlap* gives
- the length of the overlap between segments. The product of
- the direct FFTs of *x* and *y* are averaged over each segment
- to compute :math:`P_{xy}`, with a scaling to correct for power
- loss due to windowing.
- If len(*x*) < *NFFT* or len(*y*) < *NFFT*, they will be zero
- padded to *NFFT*.
- Parameters
- ----------
- x, y : 1-D arrays or sequences
- Arrays or sequences containing the data
- %(Spectral)s
- %(PSD)s
- noverlap : int, default: 0 (no overlap)
- The number of points of overlap between segments.
- Returns
- -------
- Pxy : 1-D array
- The values for the cross spectrum :math:`P_{xy}` before scaling (real
- valued)
- freqs : 1-D array
- The frequencies corresponding to the elements in *Pxy*
- References
- ----------
- Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John
- Wiley & Sons (1986)
- See Also
- --------
- psd : equivalent to setting ``y = x``.
- """
- if NFFT is None:
- NFFT = 256
- Pxy, freqs, _ = _spectral_helper(x=x, y=y, NFFT=NFFT, Fs=Fs,
- detrend_func=detrend, window=window,
- noverlap=noverlap, pad_to=pad_to,
- sides=sides, scale_by_freq=scale_by_freq,
- mode='psd')
- if Pxy.ndim == 2:
- if Pxy.shape[1] > 1:
- Pxy = Pxy.mean(axis=1)
- else:
- Pxy = Pxy[:, 0]
- return Pxy, freqs
- _single_spectrum_docs = """\
- Compute the {quantity} of *x*.
- Data is padded to a length of *pad_to* and the windowing function *window* is
- applied to the signal.
- Parameters
- ----------
- x : 1-D array or sequence
- Array or sequence containing the data
- {Spectral}
- {Single_Spectrum}
- Returns
- -------
- spectrum : 1-D array
- The {quantity}.
- freqs : 1-D array
- The frequencies corresponding to the elements in *spectrum*.
- See Also
- --------
- psd
- Returns the power spectral density.
- complex_spectrum
- Returns the complex-valued frequency spectrum.
- magnitude_spectrum
- Returns the absolute value of the `complex_spectrum`.
- angle_spectrum
- Returns the angle of the `complex_spectrum`.
- phase_spectrum
- Returns the phase (unwrapped angle) of the `complex_spectrum`.
- specgram
- Can return the complex spectrum of segments within the signal.
- """
- complex_spectrum = functools.partial(_single_spectrum_helper, "complex")
- complex_spectrum.__doc__ = _single_spectrum_docs.format(
- quantity="complex-valued frequency spectrum",
- **_docstring.interpd.params)
- magnitude_spectrum = functools.partial(_single_spectrum_helper, "magnitude")
- magnitude_spectrum.__doc__ = _single_spectrum_docs.format(
- quantity="magnitude (absolute value) of the frequency spectrum",
- **_docstring.interpd.params)
- angle_spectrum = functools.partial(_single_spectrum_helper, "angle")
- angle_spectrum.__doc__ = _single_spectrum_docs.format(
- quantity="angle of the frequency spectrum (wrapped phase spectrum)",
- **_docstring.interpd.params)
- phase_spectrum = functools.partial(_single_spectrum_helper, "phase")
- phase_spectrum.__doc__ = _single_spectrum_docs.format(
- quantity="phase of the frequency spectrum (unwrapped phase spectrum)",
- **_docstring.interpd.params)
- @_docstring.dedent_interpd
- def specgram(x, NFFT=None, Fs=None, detrend=None, window=None,
- noverlap=None, pad_to=None, sides=None, scale_by_freq=None,
- mode=None):
- """
- Compute a spectrogram.
- Compute and plot a spectrogram of data in *x*. Data are split into
- *NFFT* length segments and the spectrum of each section is
- computed. The windowing function *window* is applied to each
- segment, and the amount of overlap of each segment is
- specified with *noverlap*.
- Parameters
- ----------
- x : array-like
- 1-D array or sequence.
- %(Spectral)s
- %(PSD)s
- noverlap : int, default: 128
- The number of points of overlap between blocks.
- mode : str, default: 'psd'
- What sort of spectrum to use:
- 'psd'
- Returns the power spectral density.
- 'complex'
- Returns the complex-valued frequency spectrum.
- 'magnitude'
- Returns the magnitude spectrum.
- 'angle'
- Returns the phase spectrum without unwrapping.
- 'phase'
- Returns the phase spectrum with unwrapping.
- Returns
- -------
- spectrum : array-like
- 2D array, columns are the periodograms of successive segments.
- freqs : array-like
- 1-D array, frequencies corresponding to the rows in *spectrum*.
- t : array-like
- 1-D array, the times corresponding to midpoints of segments
- (i.e the columns in *spectrum*).
- See Also
- --------
- psd : differs in the overlap and in the return values.
- complex_spectrum : similar, but with complex valued frequencies.
- magnitude_spectrum : similar single segment when *mode* is 'magnitude'.
- angle_spectrum : similar to single segment when *mode* is 'angle'.
- phase_spectrum : similar to single segment when *mode* is 'phase'.
- Notes
- -----
- *detrend* and *scale_by_freq* only apply when *mode* is set to 'psd'.
- """
- if noverlap is None:
- noverlap = 128 # default in _spectral_helper() is noverlap = 0
- if NFFT is None:
- NFFT = 256 # same default as in _spectral_helper()
- if len(x) <= NFFT:
- _api.warn_external("Only one segment is calculated since parameter "
- f"NFFT (={NFFT}) >= signal length (={len(x)}).")
- spec, freqs, t = _spectral_helper(x=x, y=None, NFFT=NFFT, Fs=Fs,
- detrend_func=detrend, window=window,
- noverlap=noverlap, pad_to=pad_to,
- sides=sides,
- scale_by_freq=scale_by_freq,
- mode=mode)
- if mode != 'complex':
- spec = spec.real # Needed since helper implements generically
- return spec, freqs, t
- @_docstring.dedent_interpd
- def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning,
- noverlap=0, pad_to=None, sides='default', scale_by_freq=None):
- r"""
- The coherence between *x* and *y*. Coherence is the normalized
- cross spectral density:
- .. math::
- C_{xy} = \frac{|P_{xy}|^2}{P_{xx}P_{yy}}
- Parameters
- ----------
- x, y
- Array or sequence containing the data
- %(Spectral)s
- %(PSD)s
- noverlap : int, default: 0 (no overlap)
- The number of points of overlap between segments.
- Returns
- -------
- Cxy : 1-D array
- The coherence vector.
- freqs : 1-D array
- The frequencies for the elements in *Cxy*.
- See Also
- --------
- :func:`psd`, :func:`csd` :
- For information about the methods used to compute :math:`P_{xy}`,
- :math:`P_{xx}` and :math:`P_{yy}`.
- """
- if len(x) < 2 * NFFT:
- raise ValueError(
- "Coherence is calculated by averaging over *NFFT* length "
- "segments. Your signal is too short for your choice of *NFFT*.")
- Pxx, f = psd(x, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
- scale_by_freq)
- Pyy, f = psd(y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
- scale_by_freq)
- Pxy, f = csd(x, y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
- scale_by_freq)
- Cxy = np.abs(Pxy) ** 2 / (Pxx * Pyy)
- return Cxy, f
- class GaussianKDE:
- """
- Representation of a kernel-density estimate using Gaussian kernels.
- Parameters
- ----------
- dataset : array-like
- Datapoints to estimate from. In case of univariate data this is a 1-D
- array, otherwise a 2D array with shape (# of dims, # of data).
- bw_method : str, scalar or callable, optional
- The method used to calculate the estimator bandwidth. This can be
- 'scott', 'silverman', a scalar constant or a callable. If a
- scalar, this will be used directly as `kde.factor`. If a
- callable, it should take a `GaussianKDE` instance as only
- parameter and return a scalar. If None (default), 'scott' is used.
- Attributes
- ----------
- dataset : ndarray
- The dataset passed to the constructor.
- dim : int
- Number of dimensions.
- num_dp : int
- Number of datapoints.
- factor : float
- The bandwidth factor, obtained from `kde.covariance_factor`, with which
- the covariance matrix is multiplied.
- covariance : ndarray
- The covariance matrix of *dataset*, scaled by the calculated bandwidth
- (`kde.factor`).
- inv_cov : ndarray
- The inverse of *covariance*.
- Methods
- -------
- kde.evaluate(points) : ndarray
- Evaluate the estimated pdf on a provided set of points.
- kde(points) : ndarray
- Same as kde.evaluate(points)
- """
- # This implementation with minor modification was too good to pass up.
- # from scipy: https://github.com/scipy/scipy/blob/master/scipy/stats/kde.py
- def __init__(self, dataset, bw_method=None):
- self.dataset = np.atleast_2d(dataset)
- if not np.array(self.dataset).size > 1:
- raise ValueError("`dataset` input should have multiple elements.")
- self.dim, self.num_dp = np.array(self.dataset).shape
- if bw_method is None:
- pass
- elif cbook._str_equal(bw_method, 'scott'):
- self.covariance_factor = self.scotts_factor
- elif cbook._str_equal(bw_method, 'silverman'):
- self.covariance_factor = self.silverman_factor
- elif isinstance(bw_method, Number):
- self._bw_method = 'use constant'
- self.covariance_factor = lambda: bw_method
- elif callable(bw_method):
- self._bw_method = bw_method
- self.covariance_factor = lambda: self._bw_method(self)
- else:
- raise ValueError("`bw_method` should be 'scott', 'silverman', a "
- "scalar or a callable")
- # Computes the covariance matrix for each Gaussian kernel using
- # covariance_factor().
- self.factor = self.covariance_factor()
- # Cache covariance and inverse covariance of the data
- if not hasattr(self, '_data_inv_cov'):
- self.data_covariance = np.atleast_2d(
- np.cov(
- self.dataset,
- rowvar=1,
- bias=False))
- self.data_inv_cov = np.linalg.inv(self.data_covariance)
- self.covariance = self.data_covariance * self.factor ** 2
- self.inv_cov = self.data_inv_cov / self.factor ** 2
- self.norm_factor = (np.sqrt(np.linalg.det(2 * np.pi * self.covariance))
- * self.num_dp)
- def scotts_factor(self):
- return np.power(self.num_dp, -1. / (self.dim + 4))
- def silverman_factor(self):
- return np.power(
- self.num_dp * (self.dim + 2.0) / 4.0, -1. / (self.dim + 4))
- # Default method to calculate bandwidth, can be overwritten by subclass
- covariance_factor = scotts_factor
- def evaluate(self, points):
- """
- Evaluate the estimated pdf on a set of points.
- Parameters
- ----------
- points : (# of dimensions, # of points)-array
- Alternatively, a (# of dimensions,) vector can be passed in and
- treated as a single point.
- Returns
- -------
- (# of points,)-array
- The values at each point.
- Raises
- ------
- ValueError : if the dimensionality of the input points is different
- than the dimensionality of the KDE.
- """
- points = np.atleast_2d(points)
- dim, num_m = np.array(points).shape
- if dim != self.dim:
- raise ValueError(f"points have dimension {dim}, dataset has "
- f"dimension {self.dim}")
- result = np.zeros(num_m)
- if num_m >= self.num_dp:
- # there are more points than data, so loop over data
- for i in range(self.num_dp):
- diff = self.dataset[:, i, np.newaxis] - points
- tdiff = np.dot(self.inv_cov, diff)
- energy = np.sum(diff * tdiff, axis=0) / 2.0
- result = result + np.exp(-energy)
- else:
- # loop over points
- for i in range(num_m):
- diff = self.dataset - points[:, i, np.newaxis]
- tdiff = np.dot(self.inv_cov, diff)
- energy = np.sum(diff * tdiff, axis=0) / 2.0
- result[i] = np.sum(np.exp(-energy), axis=0)
- result = result / self.norm_factor
- return result
- __call__ = evaluate
|