Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/mlab.py : 10%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Numerical python functions written for compatibility with MATLAB
3commands with the same names. Most numerical python functions can be found in
4the `numpy` and `scipy` libraries. What remains here is code for performing
5spectral computations.
7Spectral functions
8-------------------
10`cohere`
11 Coherence (normalized cross spectral density)
13`csd`
14 Cross spectral density using Welch's average periodogram
16`detrend`
17 Remove the mean or best fit line from an array
19`psd`
20 Power spectral density using Welch's average periodogram
22`specgram`
23 Spectrogram (spectrum over segments of time)
25`complex_spectrum`
26 Return the complex-valued frequency spectrum of a signal
28`magnitude_spectrum`
29 Return the magnitude of the frequency spectrum of a signal
31`angle_spectrum`
32 Return the angle (wrapped phase) of the frequency spectrum of a signal
34`phase_spectrum`
35 Return the phase (unwrapped angle) of the frequency spectrum of a signal
37`detrend_mean`
38 Remove the mean from a line.
40`detrend_linear`
41 Remove the best fit line from a line.
43`detrend_none`
44 Return the original line.
46`stride_windows`
47 Get all windows in an array in a memory-efficient manner
49`stride_repeat`
50 Repeat an array in a memory-efficient manner
52`apply_window`
53 Apply a window along a given axis
54"""
56import csv
57import inspect
58from numbers import Number
60import numpy as np
62import matplotlib.cbook as cbook
63from matplotlib import docstring
66def window_hanning(x):
67 '''
68 Return x times the hanning window of len(x).
70 See Also
71 --------
72 window_none : Another window algorithm.
73 '''
74 return np.hanning(len(x))*x
77def window_none(x):
78 '''
79 No window function; simply return x.
81 See Also
82 --------
83 window_hanning : Another window algorithm.
84 '''
85 return x
88@cbook.deprecated("3.2")
89def apply_window(x, window, axis=0, return_window=None):
90 '''
91 Apply the given window to the given 1D or 2D array along the given axis.
93 Parameters
94 ----------
95 x : 1D or 2D array or sequence
96 Array or sequence containing the data.
98 window : function or array.
99 Either a function to generate a window or an array with length
100 *x*.shape[*axis*]
102 axis : integer
103 The axis over which to do the repetition.
104 Must be 0 or 1. The default is 0
106 return_window : bool
107 If true, also return the 1D values of the window that was applied
108 '''
109 x = np.asarray(x)
111 if x.ndim < 1 or x.ndim > 2:
112 raise ValueError('only 1D or 2D arrays can be used')
113 if axis+1 > x.ndim:
114 raise ValueError('axis(=%s) out of bounds' % axis)
116 xshape = list(x.shape)
117 xshapetarg = xshape.pop(axis)
119 if np.iterable(window):
120 if len(window) != xshapetarg:
121 raise ValueError('The len(window) must be the same as the shape '
122 'of x for the chosen axis')
123 windowVals = window
124 else:
125 windowVals = window(np.ones(xshapetarg, dtype=x.dtype))
127 if x.ndim == 1:
128 if return_window:
129 return windowVals * x, windowVals
130 else:
131 return windowVals * x
133 xshapeother = xshape.pop()
135 otheraxis = (axis+1) % 2
137 windowValsRep = stride_repeat(windowVals, xshapeother, axis=otheraxis)
139 if return_window:
140 return windowValsRep * x, windowVals
141 else:
142 return windowValsRep * x
145def detrend(x, key=None, axis=None):
146 '''
147 Return x with its trend removed.
149 Parameters
150 ----------
151 x : array or sequence
152 Array or sequence containing the data.
154 key : {'default', 'constant', 'mean', 'linear', 'none'} or function
155 Specifies the detrend algorithm to use. 'default' is 'mean', which is
156 the same as `detrend_mean`. 'constant' is the same. 'linear' is
157 the same as `detrend_linear`. 'none' is the same as
158 `detrend_none`. The default is 'mean'. See the corresponding
159 functions for more details regarding the algorithms. Can also be a
160 function that carries out the detrend operation.
162 axis : integer
163 The axis along which to do the detrending.
165 See Also
166 --------
167 detrend_mean : Implementation of the 'mean' algorithm.
168 detrend_linear : Implementation of the 'linear' algorithm.
169 detrend_none : Implementation of the 'none' algorithm.
170 '''
171 if key is None or key in ['constant', 'mean', 'default']:
172 return detrend(x, key=detrend_mean, axis=axis)
173 elif key == 'linear':
174 return detrend(x, key=detrend_linear, axis=axis)
175 elif key == 'none':
176 return detrend(x, key=detrend_none, axis=axis)
177 elif callable(key):
178 x = np.asarray(x)
179 if axis is not None and axis + 1 > x.ndim:
180 raise ValueError(f'axis(={axis}) out of bounds')
181 if (axis is None and x.ndim == 0) or (not axis and x.ndim == 1):
182 return key(x)
183 # try to use the 'axis' argument if the function supports it,
184 # otherwise use apply_along_axis to do it
185 try:
186 return key(x, axis=axis)
187 except TypeError:
188 return np.apply_along_axis(key, axis=axis, arr=x)
189 else:
190 raise ValueError(
191 f"Unknown value for key: {key!r}, must be one of: 'default', "
192 f"'constant', 'mean', 'linear', or a function")
195@cbook.deprecated("3.1", alternative="detrend_mean")
196def demean(x, axis=0):
197 '''
198 Return x minus its mean along the specified axis.
200 Parameters
201 ----------
202 x : array or sequence
203 Array or sequence containing the data
204 Can have any dimensionality
206 axis : integer
207 The axis along which to take the mean. See numpy.mean for a
208 description of this argument.
210 See Also
211 --------
212 detrend_mean : Same as `demean` except for the default *axis*.
213 '''
214 return detrend_mean(x, axis=axis)
217def detrend_mean(x, axis=None):
218 '''
219 Return x minus the mean(x).
221 Parameters
222 ----------
223 x : array or sequence
224 Array or sequence containing the data
225 Can have any dimensionality
227 axis : integer
228 The axis along which to take the mean. See numpy.mean for a
229 description of this argument.
231 See Also
232 --------
233 detrend_linear : Another detrend algorithm.
234 detrend_none : Another detrend algorithm.
235 detrend : A wrapper around all the detrend algorithms.
236 '''
237 x = np.asarray(x)
239 if axis is not None and axis+1 > x.ndim:
240 raise ValueError('axis(=%s) out of bounds' % axis)
242 return x - x.mean(axis, keepdims=True)
245def detrend_none(x, axis=None):
246 '''
247 Return x: no detrending.
249 Parameters
250 ----------
251 x : any object
252 An object containing the data
254 axis : integer
255 This parameter is ignored.
256 It is included for compatibility with detrend_mean
258 See Also
259 --------
260 detrend_mean : Another detrend algorithm.
261 detrend_linear : Another detrend algorithm.
262 detrend : A wrapper around all the detrend algorithms.
263 '''
264 return x
267def detrend_linear(y):
268 '''
269 Return x minus best fit line; 'linear' detrending.
271 Parameters
272 ----------
273 y : 0-D or 1-D array or sequence
274 Array or sequence containing the data
276 axis : integer
277 The axis along which to take the mean. See numpy.mean for a
278 description of this argument.
280 See Also
281 --------
282 detrend_mean : Another detrend algorithm.
283 detrend_none : Another detrend algorithm.
284 detrend : A wrapper around all the detrend algorithms.
285 '''
286 # This is faster than an algorithm based on linalg.lstsq.
287 y = np.asarray(y)
289 if y.ndim > 1:
290 raise ValueError('y cannot have ndim > 1')
292 # short-circuit 0-D array.
293 if not y.ndim:
294 return np.array(0., dtype=y.dtype)
296 x = np.arange(y.size, dtype=float)
298 C = np.cov(x, y, bias=1)
299 b = C[0, 1]/C[0, 0]
301 a = y.mean() - b*x.mean()
302 return y - (b*x + a)
305def stride_windows(x, n, noverlap=None, axis=0):
306 '''
307 Get all windows of x with length n as a single array,
308 using strides to avoid data duplication.
310 .. warning::
312 It is not safe to write to the output array. Multiple
313 elements may point to the same piece of memory,
314 so modifying one value may change others.
316 Parameters
317 ----------
318 x : 1D array or sequence
319 Array or sequence containing the data.
321 n : integer
322 The number of data points in each window.
324 noverlap : integer
325 The overlap between adjacent windows.
326 Default is 0 (no overlap)
328 axis : integer
329 The axis along which the windows will run.
331 References
332 ----------
333 `stackoverflow: Rolling window for 1D arrays in Numpy?
334 <http://stackoverflow.com/a/6811241>`_
335 `stackoverflow: Using strides for an efficient moving average filter
336 <http://stackoverflow.com/a/4947453>`_
337 '''
338 if noverlap is None:
339 noverlap = 0
341 if noverlap >= n:
342 raise ValueError('noverlap must be less than n')
343 if n < 1:
344 raise ValueError('n cannot be less than 1')
346 x = np.asarray(x)
348 if x.ndim != 1:
349 raise ValueError('only 1-dimensional arrays can be used')
350 if n == 1 and noverlap == 0:
351 if axis == 0:
352 return x[np.newaxis]
353 else:
354 return x[np.newaxis].transpose()
355 if n > x.size:
356 raise ValueError('n cannot be greater than the length of x')
358 # np.lib.stride_tricks.as_strided easily leads to memory corruption for
359 # non integer shape and strides, i.e. noverlap or n. See #3845.
360 noverlap = int(noverlap)
361 n = int(n)
363 step = n - noverlap
364 if axis == 0:
365 shape = (n, (x.shape[-1]-noverlap)//step)
366 strides = (x.strides[0], step*x.strides[0])
367 else:
368 shape = ((x.shape[-1]-noverlap)//step, n)
369 strides = (step*x.strides[0], x.strides[0])
370 return np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides)
373@cbook.deprecated("3.2")
374def stride_repeat(x, n, axis=0):
375 '''
376 Repeat the values in an array in a memory-efficient manner. Array x is
377 stacked vertically n times.
379 .. warning::
381 It is not safe to write to the output array. Multiple
382 elements may point to the same piece of memory, so
383 modifying one value may change others.
385 Parameters
386 ----------
387 x : 1D array or sequence
388 Array or sequence containing the data.
390 n : integer
391 The number of time to repeat the array.
393 axis : integer
394 The axis along which the data will run.
396 References
397 ----------
398 `stackoverflow: Repeat NumPy array without replicating data?
399 <http://stackoverflow.com/a/5568169>`_
400 '''
401 if axis not in [0, 1]:
402 raise ValueError('axis must be 0 or 1')
403 x = np.asarray(x)
404 if x.ndim != 1:
405 raise ValueError('only 1-dimensional arrays can be used')
407 if n == 1:
408 if axis == 0:
409 return np.atleast_2d(x)
410 else:
411 return np.atleast_2d(x).T
412 if n < 1:
413 raise ValueError('n cannot be less than 1')
415 # np.lib.stride_tricks.as_strided easily leads to memory corruption for
416 # non integer shape and strides, i.e. n. See #3845.
417 n = int(n)
419 if axis == 0:
420 shape = (n, x.size)
421 strides = (0, x.strides[0])
422 else:
423 shape = (x.size, n)
424 strides = (x.strides[0], 0)
426 return np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides)
429def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None,
430 window=None, noverlap=None, pad_to=None,
431 sides=None, scale_by_freq=None, mode=None):
432 '''
433 This is a helper function that implements the commonality between the
434 psd, csd, spectrogram and complex, magnitude, angle, and phase spectrums.
435 It is *NOT* meant to be used outside of mlab and may change at any time.
436 '''
437 if y is None:
438 # if y is None use x for y
439 same_data = True
440 else:
441 # The checks for if y is x are so that we can use the same function to
442 # implement the core of psd(), csd(), and spectrogram() without doing
443 # extra calculations. We return the unaveraged Pxy, freqs, and t.
444 same_data = y is x
446 if Fs is None:
447 Fs = 2
448 if noverlap is None:
449 noverlap = 0
450 if detrend_func is None:
451 detrend_func = detrend_none
452 if window is None:
453 window = window_hanning
455 # if NFFT is set to None use the whole signal
456 if NFFT is None:
457 NFFT = 256
459 if mode is None or mode == 'default':
460 mode = 'psd'
461 cbook._check_in_list(
462 ['default', 'psd', 'complex', 'magnitude', 'angle', 'phase'],
463 mode=mode)
465 if not same_data and mode != 'psd':
466 raise ValueError("x and y must be equal if mode is not 'psd'")
468 # Make sure we're dealing with a numpy array. If y and x were the same
469 # object to start with, keep them that way
470 x = np.asarray(x)
471 if not same_data:
472 y = np.asarray(y)
474 if sides is None or sides == 'default':
475 if np.iscomplexobj(x):
476 sides = 'twosided'
477 else:
478 sides = 'onesided'
479 cbook._check_in_list(['default', 'onesided', 'twosided'], sides=sides)
481 # zero pad x and y up to NFFT if they are shorter than NFFT
482 if len(x) < NFFT:
483 n = len(x)
484 x = np.resize(x, NFFT)
485 x[n:] = 0
487 if not same_data and len(y) < NFFT:
488 n = len(y)
489 y = np.resize(y, NFFT)
490 y[n:] = 0
492 if pad_to is None:
493 pad_to = NFFT
495 if mode != 'psd':
496 scale_by_freq = False
497 elif scale_by_freq is None:
498 scale_by_freq = True
500 # For real x, ignore the negative frequencies unless told otherwise
501 if sides == 'twosided':
502 numFreqs = pad_to
503 if pad_to % 2:
504 freqcenter = (pad_to - 1)//2 + 1
505 else:
506 freqcenter = pad_to//2
507 scaling_factor = 1.
508 elif sides == 'onesided':
509 if pad_to % 2:
510 numFreqs = (pad_to + 1)//2
511 else:
512 numFreqs = pad_to//2 + 1
513 scaling_factor = 2.
515 if not np.iterable(window):
516 window = window(np.ones(NFFT, x.dtype))
517 if len(window) != NFFT:
518 raise ValueError(
519 "The window length must match the data's first dimension")
521 result = stride_windows(x, NFFT, noverlap, axis=0)
522 result = detrend(result, detrend_func, axis=0)
523 result = result * window.reshape((-1, 1))
524 result = np.fft.fft(result, n=pad_to, axis=0)[:numFreqs, :]
525 freqs = np.fft.fftfreq(pad_to, 1/Fs)[:numFreqs]
527 if not same_data:
528 # if same_data is False, mode must be 'psd'
529 resultY = stride_windows(y, NFFT, noverlap)
530 resultY = detrend(resultY, detrend_func, axis=0)
531 resultY = resultY * window.reshape((-1, 1))
532 resultY = np.fft.fft(resultY, n=pad_to, axis=0)[:numFreqs, :]
533 result = np.conj(result) * resultY
534 elif mode == 'psd':
535 result = np.conj(result) * result
536 elif mode == 'magnitude':
537 result = np.abs(result) / np.abs(window).sum()
538 elif mode == 'angle' or mode == 'phase':
539 # we unwrap the phase later to handle the onesided vs. twosided case
540 result = np.angle(result)
541 elif mode == 'complex':
542 result /= np.abs(window).sum()
544 if mode == 'psd':
546 # Also include scaling factors for one-sided densities and dividing by
547 # the sampling frequency, if desired. Scale everything, except the DC
548 # component and the NFFT/2 component:
550 # if we have a even number of frequencies, don't scale NFFT/2
551 if not NFFT % 2:
552 slc = slice(1, -1, None)
553 # if we have an odd number, just don't scale DC
554 else:
555 slc = slice(1, None, None)
557 result[slc] *= scaling_factor
559 # MATLAB divides by the sampling frequency so that density function
560 # has units of dB/Hz and can be integrated by the plotted frequency
561 # values. Perform the same scaling here.
562 if scale_by_freq:
563 result /= Fs
564 # Scale the spectrum by the norm of the window to compensate for
565 # windowing loss; see Bendat & Piersol Sec 11.5.2.
566 result /= (np.abs(window)**2).sum()
567 else:
568 # In this case, preserve power in the segment, not amplitude
569 result /= np.abs(window).sum()**2
571 t = np.arange(NFFT/2, len(x) - NFFT/2 + 1, NFFT - noverlap)/Fs
573 if sides == 'twosided':
574 # center the frequency range at zero
575 freqs = np.concatenate((freqs[freqcenter:], freqs[:freqcenter]))
576 result = np.concatenate((result[freqcenter:, :],
577 result[:freqcenter, :]), 0)
578 elif not pad_to % 2:
579 # get the last value correctly, it is negative otherwise
580 freqs[-1] *= -1
582 # we unwrap the phase here to handle the onesided vs. twosided case
583 if mode == 'phase':
584 result = np.unwrap(result, axis=0)
586 return result, freqs, t
589def _single_spectrum_helper(x, mode, Fs=None, window=None, pad_to=None,
590 sides=None):
591 '''
592 This is a helper function that implements the commonality between the
593 complex, magnitude, angle, and phase spectrums.
594 It is *NOT* meant to be used outside of mlab and may change at any time.
595 '''
596 cbook._check_in_list(['complex', 'magnitude', 'angle', 'phase'], mode=mode)
598 if pad_to is None:
599 pad_to = len(x)
601 spec, freqs, _ = _spectral_helper(x=x, y=None, NFFT=len(x), Fs=Fs,
602 detrend_func=detrend_none, window=window,
603 noverlap=0, pad_to=pad_to,
604 sides=sides,
605 scale_by_freq=False,
606 mode=mode)
607 if mode != 'complex':
608 spec = spec.real
610 if spec.ndim == 2 and spec.shape[1] == 1:
611 spec = spec[:, 0]
613 return spec, freqs
616# Split out these keyword docs so that they can be used elsewhere
617docstring.interpd.update(Spectral=inspect.cleandoc("""
618 Fs : scalar
619 The sampling frequency (samples per time unit). It is used
620 to calculate the Fourier frequencies, freqs, in cycles per time
621 unit. The default value is 2.
623 window : callable or ndarray
624 A function or a vector of length *NFFT*. To create window vectors see
625 `window_hanning`, `window_none`, `numpy.blackman`, `numpy.hamming`,
626 `numpy.bartlett`, `scipy.signal`, `scipy.signal.get_window`, etc. The
627 default is `window_hanning`. If a function is passed as the argument,
628 it must take a data segment as an argument and return the windowed
629 version of the segment.
631 sides : {'default', 'onesided', 'twosided'}
632 Specifies which sides of the spectrum to return. Default gives the
633 default behavior, which returns one-sided for real data and both
634 for complex data. 'onesided' forces the return of a one-sided
635 spectrum, while 'twosided' forces two-sided.
636"""))
639docstring.interpd.update(Single_Spectrum=inspect.cleandoc("""
640 pad_to : int
641 The number of points to which the data segment is padded when
642 performing the FFT. While not increasing the actual resolution of
643 the spectrum (the minimum distance between resolvable peaks),
644 this can give more points in the plot, allowing for more
645 detail. This corresponds to the *n* parameter in the call to fft().
646 The default is None, which sets *pad_to* equal to the length of the
647 input signal (i.e. no padding).
648"""))
651docstring.interpd.update(PSD=inspect.cleandoc("""
652 pad_to : int
653 The number of points to which the data segment is padded when
654 performing the FFT. This can be different from *NFFT*, which
655 specifies the number of data points used. While not increasing
656 the actual resolution of the spectrum (the minimum distance between
657 resolvable peaks), this can give more points in the plot,
658 allowing for more detail. This corresponds to the *n* parameter
659 in the call to fft(). The default is None, which sets *pad_to*
660 equal to *NFFT*
662 NFFT : int
663 The number of data points used in each block for the FFT.
664 A power 2 is most efficient. The default value is 256.
665 This should *NOT* be used to get zero padding, or the scaling of the
666 result will be incorrect. Use *pad_to* for this instead.
668 detrend : {'none', 'mean', 'linear'} or callable, default 'none'
669 The function applied to each segment before fft-ing, designed to
670 remove the mean or linear trend. Unlike in MATLAB, where the
671 *detrend* parameter is a vector, in Matplotlib is it a function.
672 The :mod:`~matplotlib.mlab` module defines `.detrend_none`,
673 `.detrend_mean`, and `.detrend_linear`, but you can use a custom
674 function as well. You can also use a string to choose one of the
675 functions: 'none' calls `.detrend_none`. 'mean' calls `.detrend_mean`.
676 'linear' calls `.detrend_linear`.
678 scale_by_freq : bool, optional
679 Specifies whether the resulting density values should be scaled
680 by the scaling frequency, which gives density in units of Hz^-1.
681 This allows for integration over the returned frequency values.
682 The default is True for MATLAB compatibility.
683"""))
686@docstring.dedent_interpd
687def psd(x, NFFT=None, Fs=None, detrend=None, window=None,
688 noverlap=None, pad_to=None, sides=None, scale_by_freq=None):
689 r"""
690 Compute the power spectral density.
692 The power spectral density :math:`P_{xx}` by Welch's average
693 periodogram method. The vector *x* is divided into *NFFT* length
694 segments. Each segment is detrended by function *detrend* and
695 windowed by function *window*. *noverlap* gives the length of
696 the overlap between segments. The :math:`|\mathrm{fft}(i)|^2`
697 of each segment :math:`i` are averaged to compute :math:`P_{xx}`.
699 If len(*x*) < *NFFT*, it will be zero padded to *NFFT*.
701 Parameters
702 ----------
703 x : 1-D array or sequence
704 Array or sequence containing the data
706 %(Spectral)s
708 %(PSD)s
710 noverlap : integer
711 The number of points of overlap between segments.
712 The default value is 0 (no overlap).
714 Returns
715 -------
716 Pxx : 1-D array
717 The values for the power spectrum `P_{xx}` (real valued)
719 freqs : 1-D array
720 The frequencies corresponding to the elements in *Pxx*
722 References
723 ----------
724 Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John
725 Wiley & Sons (1986)
727 See Also
728 --------
729 specgram
730 `specgram` differs in the default overlap; in not returning the mean of
731 the segment periodograms; and in returning the times of the segments.
733 magnitude_spectrum : returns the magnitude spectrum.
735 csd : returns the spectral density between two signals.
736 """
737 Pxx, freqs = csd(x=x, y=None, NFFT=NFFT, Fs=Fs, detrend=detrend,
738 window=window, noverlap=noverlap, pad_to=pad_to,
739 sides=sides, scale_by_freq=scale_by_freq)
740 return Pxx.real, freqs
743@docstring.dedent_interpd
744def csd(x, y, NFFT=None, Fs=None, detrend=None, window=None,
745 noverlap=None, pad_to=None, sides=None, scale_by_freq=None):
746 """
747 Compute the cross-spectral density.
749 The cross spectral density :math:`P_{xy}` by Welch's average
750 periodogram method. The vectors *x* and *y* are divided into
751 *NFFT* length segments. Each segment is detrended by function
752 *detrend* and windowed by function *window*. *noverlap* gives
753 the length of the overlap between segments. The product of
754 the direct FFTs of *x* and *y* are averaged over each segment
755 to compute :math:`P_{xy}`, with a scaling to correct for power
756 loss due to windowing.
758 If len(*x*) < *NFFT* or len(*y*) < *NFFT*, they will be zero
759 padded to *NFFT*.
761 Parameters
762 ----------
763 x, y : 1-D arrays or sequences
764 Arrays or sequences containing the data
766 %(Spectral)s
768 %(PSD)s
770 noverlap : integer
771 The number of points of overlap between segments.
772 The default value is 0 (no overlap).
774 Returns
775 -------
776 Pxy : 1-D array
777 The values for the cross spectrum `P_{xy}` before scaling (real valued)
779 freqs : 1-D array
780 The frequencies corresponding to the elements in *Pxy*
782 References
783 ----------
784 Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John
785 Wiley & Sons (1986)
787 See Also
788 --------
789 psd : equivalent to setting ``y = x``.
790 """
791 if NFFT is None:
792 NFFT = 256
793 Pxy, freqs, _ = _spectral_helper(x=x, y=y, NFFT=NFFT, Fs=Fs,
794 detrend_func=detrend, window=window,
795 noverlap=noverlap, pad_to=pad_to,
796 sides=sides, scale_by_freq=scale_by_freq,
797 mode='psd')
799 if Pxy.ndim == 2:
800 if Pxy.shape[1] > 1:
801 Pxy = Pxy.mean(axis=1)
802 else:
803 Pxy = Pxy[:, 0]
804 return Pxy, freqs
807@docstring.dedent_interpd
808def complex_spectrum(x, Fs=None, window=None, pad_to=None,
809 sides=None):
810 """
811 Compute the complex-valued frequency spectrum of *x*. Data is padded to a
812 length of *pad_to* and the windowing function *window* is applied to the
813 signal.
815 Parameters
816 ----------
817 x : 1-D array or sequence
818 Array or sequence containing the data
820 %(Spectral)s
822 %(Single_Spectrum)s
824 Returns
825 -------
826 spectrum : 1-D array
827 The values for the complex spectrum (complex valued)
829 freqs : 1-D array
830 The frequencies corresponding to the elements in *spectrum*
832 See Also
833 --------
834 magnitude_spectrum
835 Returns the absolute value of this function.
836 angle_spectrum
837 Returns the angle of this function.
838 phase_spectrum
839 Returns the phase (unwrapped angle) of this function.
840 specgram
841 Can return the complex spectrum of segments within the signal.
842 """
843 return _single_spectrum_helper(x=x, Fs=Fs, window=window, pad_to=pad_to,
844 sides=sides, mode='complex')
847@docstring.dedent_interpd
848def magnitude_spectrum(x, Fs=None, window=None, pad_to=None,
849 sides=None):
850 """
851 Compute the magnitude (absolute value) of the frequency spectrum of
852 *x*. Data is padded to a length of *pad_to* and the windowing function
853 *window* is applied to the signal.
855 Parameters
856 ----------
857 x : 1-D array or sequence
858 Array or sequence containing the data
860 %(Spectral)s
862 %(Single_Spectrum)s
864 Returns
865 -------
866 spectrum : 1-D array
867 The values for the magnitude spectrum (real valued)
869 freqs : 1-D array
870 The frequencies corresponding to the elements in *spectrum*
872 See Also
873 --------
874 psd
875 Returns the power spectral density.
876 complex_spectrum
877 This function returns the absolute value of `complex_spectrum`.
878 angle_spectrum
879 Returns the angles of the corresponding frequencies.
880 phase_spectrum
881 Returns the phase (unwrapped angle) of the corresponding frequencies.
882 specgram
883 Can return the complex spectrum of segments within the signal.
884 """
885 return _single_spectrum_helper(x=x, Fs=Fs, window=window, pad_to=pad_to,
886 sides=sides, mode='magnitude')
889@docstring.dedent_interpd
890def angle_spectrum(x, Fs=None, window=None, pad_to=None,
891 sides=None):
892 """
893 Compute the angle of the frequency spectrum (wrapped phase spectrum) of
894 *x*. Data is padded to a length of *pad_to* and the windowing function
895 *window* is applied to the signal.
897 Parameters
898 ----------
899 x : 1-D array or sequence
900 Array or sequence containing the data
902 %(Spectral)s
904 %(Single_Spectrum)s
906 Returns
907 -------
908 spectrum : 1-D array
909 The values for the angle spectrum in radians (real valued)
911 freqs : 1-D array
912 The frequencies corresponding to the elements in *spectrum*
914 See Also
915 --------
916 complex_spectrum
917 This function returns the angle value of `complex_spectrum`.
918 magnitude_spectrum
919 Returns the magnitudes of the corresponding frequencies.
920 phase_spectrum
921 Returns the phase (unwrapped angle) of the corresponding frequencies.
922 specgram
923 Can return the complex spectrum of segments within the signal.
924 """
925 return _single_spectrum_helper(x=x, Fs=Fs, window=window, pad_to=pad_to,
926 sides=sides, mode='angle')
929@docstring.dedent_interpd
930def phase_spectrum(x, Fs=None, window=None, pad_to=None,
931 sides=None):
932 """
933 Compute the phase of the frequency spectrum (unwrapped angle spectrum) of
934 *x*. Data is padded to a length of *pad_to* and the windowing function
935 *window* is applied to the signal.
937 Parameters
938 ----------
939 x : 1-D array or sequence
940 Array or sequence containing the data
942 %(Spectral)s
944 %(Single_Spectrum)s
946 Returns
947 -------
948 spectrum : 1-D array
949 The values for the phase spectrum in radians (real valued)
951 freqs : 1-D array
952 The frequencies corresponding to the elements in *spectrum*
954 See Also
955 --------
956 complex_spectrum
957 This function returns the phase value of `complex_spectrum`.
958 magnitude_spectrum
959 Returns the magnitudes of the corresponding frequencies.
960 angle_spectrum
961 Returns the angle (wrapped phase) of the corresponding frequencies.
962 specgram
963 Can return the complex spectrum of segments within the signal.
964 """
965 return _single_spectrum_helper(x=x, Fs=Fs, window=window, pad_to=pad_to,
966 sides=sides, mode='phase')
969@docstring.dedent_interpd
970def specgram(x, NFFT=None, Fs=None, detrend=None, window=None,
971 noverlap=None, pad_to=None, sides=None, scale_by_freq=None,
972 mode=None):
973 """
974 Compute a spectrogram.
976 Compute and plot a spectrogram of data in x. Data are split into
977 NFFT length segments and the spectrum of each section is
978 computed. The windowing function window is applied to each
979 segment, and the amount of overlap of each segment is
980 specified with noverlap.
982 Parameters
983 ----------
984 x : array-like
985 1-D array or sequence.
987 %(Spectral)s
989 %(PSD)s
991 noverlap : int, optional
992 The number of points of overlap between blocks. The default
993 value is 128.
994 mode : str, optional
995 What sort of spectrum to use, default is 'psd'.
996 'psd'
997 Returns the power spectral density.
999 'complex'
1000 Returns the complex-valued frequency spectrum.
1002 'magnitude'
1003 Returns the magnitude spectrum.
1005 'angle'
1006 Returns the phase spectrum without unwrapping.
1008 'phase'
1009 Returns the phase spectrum with unwrapping.
1011 Returns
1012 -------
1013 spectrum : array-like
1014 2-D array, columns are the periodograms of successive segments.
1016 freqs : array-like
1017 1-D array, frequencies corresponding to the rows in *spectrum*.
1019 t : array-like
1020 1-D array, the times corresponding to midpoints of segments
1021 (i.e the columns in *spectrum*).
1023 See Also
1024 --------
1025 psd : differs in the overlap and in the return values.
1026 complex_spectrum : similar, but with complex valued frequencies.
1027 magnitude_spectrum : similar single segment when mode is 'magnitude'.
1028 angle_spectrum : similar to single segment when mode is 'angle'.
1029 phase_spectrum : similar to single segment when mode is 'phase'.
1031 Notes
1032 -----
1033 detrend and scale_by_freq only apply when *mode* is set to 'psd'.
1035 """
1036 if noverlap is None:
1037 noverlap = 128 # default in _spectral_helper() is noverlap = 0
1038 if NFFT is None:
1039 NFFT = 256 # same default as in _spectral_helper()
1040 if len(x) <= NFFT:
1041 cbook._warn_external("Only one segment is calculated since parameter "
1042 "NFFT (=%d) >= signal length (=%d)." %
1043 (NFFT, len(x)))
1045 spec, freqs, t = _spectral_helper(x=x, y=None, NFFT=NFFT, Fs=Fs,
1046 detrend_func=detrend, window=window,
1047 noverlap=noverlap, pad_to=pad_to,
1048 sides=sides,
1049 scale_by_freq=scale_by_freq,
1050 mode=mode)
1052 if mode != 'complex':
1053 spec = spec.real # Needed since helper implements generically
1055 return spec, freqs, t
1058@docstring.dedent_interpd
1059def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning,
1060 noverlap=0, pad_to=None, sides='default', scale_by_freq=None):
1061 r"""
1062 The coherence between *x* and *y*. Coherence is the normalized
1063 cross spectral density:
1065 .. math::
1067 C_{xy} = \frac{|P_{xy}|^2}{P_{xx}P_{yy}}
1069 Parameters
1070 ----------
1071 x, y
1072 Array or sequence containing the data
1074 %(Spectral)s
1076 %(PSD)s
1078 noverlap : integer
1079 The number of points of overlap between blocks. The default value
1080 is 0 (no overlap).
1082 Returns
1083 -------
1084 The return value is the tuple (*Cxy*, *f*), where *f* are the
1085 frequencies of the coherence vector. For cohere, scaling the
1086 individual densities by the sampling frequency has no effect,
1087 since the factors cancel out.
1089 See Also
1090 --------
1091 :func:`psd`, :func:`csd` :
1092 For information about the methods used to compute :math:`P_{xy}`,
1093 :math:`P_{xx}` and :math:`P_{yy}`.
1094 """
1095 if len(x) < 2 * NFFT:
1096 raise ValueError(
1097 "Coherence is calculated by averaging over *NFFT* length "
1098 "segments. Your signal is too short for your choice of *NFFT*.")
1099 Pxx, f = psd(x, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
1100 scale_by_freq)
1101 Pyy, f = psd(y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
1102 scale_by_freq)
1103 Pxy, f = csd(x, y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
1104 scale_by_freq)
1105 Cxy = np.abs(Pxy) ** 2 / (Pxx * Pyy)
1106 return Cxy, f
1109def _csv2rec(fname, comments='#', skiprows=0, checkrows=0, delimiter=',',
1110 converterd=None, names=None, missing='', missingd=None,
1111 use_mrecords=False, dayfirst=False, yearfirst=False):
1112 """
1113 Load data from comma/space/tab delimited file in *fname* into a
1114 numpy record array and return the record array.
1116 If *names* is *None*, a header row is required to automatically
1117 assign the recarray names. The headers will be lower cased,
1118 spaces will be converted to underscores, and illegal attribute
1119 name characters removed. If *names* is not *None*, it is a
1120 sequence of names to use for the column names. In this case, it
1121 is assumed there is no header row.
1124 - *fname*: can be a filename or a file handle. Support for gzipped
1125 files is automatic, if the filename ends in '.gz'
1127 - *comments*: the character used to indicate the start of a comment
1128 in the file, or *None* to switch off the removal of comments
1130 - *skiprows*: is the number of rows from the top to skip
1132 - *checkrows*: is the number of rows to check to validate the column
1133 data type. When set to zero all rows are validated.
1135 - *converterd*: if not *None*, is a dictionary mapping column number or
1136 munged column name to a converter function.
1138 - *names*: if not None, is a list of header names. In this case, no
1139 header will be read from the file
1141 - *missingd* is a dictionary mapping munged column names to field values
1142 which signify that the field does not contain actual data and should
1143 be masked, e.g., '0000-00-00' or 'unused'
1145 - *missing*: a string whose value signals a missing field regardless of
1146 the column it appears in
1148 - *use_mrecords*: if True, return an mrecords.fromrecords record array if
1149 any of the data are missing
1151 - *dayfirst*: default is False so that MM-DD-YY has precedence over
1152 DD-MM-YY. See
1153 http://labix.org/python-dateutil#head-b95ce2094d189a89f80f5ae52a05b4ab7b41af47
1154 for further information.
1156 - *yearfirst*: default is False so that MM-DD-YY has precedence over
1157 YY-MM-DD. See
1158 http://labix.org/python-dateutil#head-b95ce2094d189a89f80f5ae52a05b4ab7b41af47
1159 for further information.
1161 If no rows are found, *None* is returned
1162 """
1164 if converterd is None:
1165 converterd = dict()
1167 if missingd is None:
1168 missingd = {}
1170 import dateutil.parser
1171 import datetime
1173 fh = cbook.to_filehandle(fname)
1175 delimiter = str(delimiter)
1177 class FH:
1178 """
1179 For space-delimited files, we want different behavior than
1180 comma or tab. Generally, we want multiple spaces to be
1181 treated as a single separator, whereas with comma and tab we
1182 want multiple commas to return multiple (empty) fields. The
1183 join/strip trick below effects this.
1184 """
1185 def __init__(self, fh):
1186 self.fh = fh
1188 def close(self):
1189 self.fh.close()
1191 def seek(self, arg):
1192 self.fh.seek(arg)
1194 def fix(self, s):
1195 return ' '.join(s.split())
1197 def __next__(self):
1198 return self.fix(next(self.fh))
1200 def __iter__(self):
1201 for line in self.fh:
1202 yield self.fix(line)
1204 if delimiter == ' ':
1205 fh = FH(fh)
1207 reader = csv.reader(fh, delimiter=delimiter)
1209 def process_skiprows(reader):
1210 if skiprows:
1211 for i, row in enumerate(reader):
1212 if i >= (skiprows-1):
1213 break
1215 return fh, reader
1217 process_skiprows(reader)
1219 def ismissing(name, val):
1220 "Should the value val in column name be masked?"
1221 return val == missing or val == missingd.get(name) or val == ''
1223 def with_default_value(func, default):
1224 def newfunc(name, val):
1225 if ismissing(name, val):
1226 return default
1227 else:
1228 return func(val)
1229 return newfunc
1231 def mybool(x):
1232 if x == 'True':
1233 return True
1234 elif x == 'False':
1235 return False
1236 else:
1237 raise ValueError('invalid bool')
1239 dateparser = dateutil.parser.parse
1241 def mydateparser(x):
1242 # try and return a datetime object
1243 d = dateparser(x, dayfirst=dayfirst, yearfirst=yearfirst)
1244 return d
1246 mydateparser = with_default_value(mydateparser, datetime.datetime(1, 1, 1))
1248 myfloat = with_default_value(float, np.nan)
1249 myint = with_default_value(int, -1)
1250 mystr = with_default_value(str, '')
1251 mybool = with_default_value(mybool, None)
1253 def mydate(x):
1254 # try and return a date object
1255 d = dateparser(x, dayfirst=dayfirst, yearfirst=yearfirst)
1257 if d.hour > 0 or d.minute > 0 or d.second > 0:
1258 raise ValueError('not a date')
1259 return d.date()
1260 mydate = with_default_value(mydate, datetime.date(1, 1, 1))
1262 def get_func(name, item, func):
1263 # promote functions in this order
1264 funcs = [mybool, myint, myfloat, mydate, mydateparser, mystr]
1265 for func in funcs[funcs.index(func):]:
1266 try:
1267 func(name, item)
1268 except Exception:
1269 continue
1270 return func
1271 raise ValueError('Could not find a working conversion function')
1273 # map column names that clash with builtins -- TODO - extend this list
1274 itemd = {
1275 'return': 'return_',
1276 'file': 'file_',
1277 'print': 'print_',
1278 }
1280 def get_converters(reader, comments):
1282 converters = None
1283 i = 0
1284 for row in reader:
1285 if (len(row) and comments is not None and
1286 row[0].startswith(comments)):
1287 continue
1288 if i == 0:
1289 converters = [mybool]*len(row)
1290 if checkrows and i > checkrows:
1291 break
1292 i += 1
1294 for j, (name, item) in enumerate(zip(names, row)):
1295 func = converterd.get(j)
1296 if func is None:
1297 func = converterd.get(name)
1298 if func is None:
1299 func = converters[j]
1300 if len(item.strip()):
1301 func = get_func(name, item, func)
1302 else:
1303 # how should we handle custom converters and defaults?
1304 func = with_default_value(func, None)
1305 converters[j] = func
1306 return converters
1308 # Get header and remove invalid characters
1309 needheader = names is None
1311 if needheader:
1312 for row in reader:
1313 if (len(row) and comments is not None and
1314 row[0].startswith(comments)):
1315 continue
1316 headers = row
1317 break
1319 # remove these chars
1320 delete = set(r"""~!@#$%^&*()-=+~\|}[]{';: /?.>,<""")
1321 delete.add('"')
1323 names = []
1324 seen = dict()
1325 for i, item in enumerate(headers):
1326 item = item.strip().lower().replace(' ', '_')
1327 item = ''.join([c for c in item if c not in delete])
1328 if not len(item):
1329 item = 'column%d' % i
1331 item = itemd.get(item, item)
1332 cnt = seen.get(item, 0)
1333 if cnt > 0:
1334 names.append(item + '_%d' % cnt)
1335 else:
1336 names.append(item)
1337 seen[item] = cnt+1
1339 else:
1340 if isinstance(names, str):
1341 names = [n.strip() for n in names.split(',')]
1343 # get the converter functions by inspecting checkrows
1344 converters = get_converters(reader, comments)
1345 if converters is None:
1346 raise ValueError('Could not find any valid data in CSV file')
1348 # reset the reader and start over
1349 fh.seek(0)
1350 reader = csv.reader(fh, delimiter=delimiter)
1351 process_skiprows(reader)
1353 if needheader:
1354 while True:
1355 # skip past any comments and consume one line of column header
1356 row = next(reader)
1357 if (len(row) and comments is not None and
1358 row[0].startswith(comments)):
1359 continue
1360 break
1362 # iterate over the remaining rows and convert the data to date
1363 # objects, ints, or floats as appropriate
1364 rows = []
1365 rowmasks = []
1366 for i, row in enumerate(reader):
1367 if not len(row):
1368 continue
1369 if comments is not None and row[0].startswith(comments):
1370 continue
1371 # Ensure that the row returned always has the same nr of elements
1372 row.extend([''] * (len(converters) - len(row)))
1373 rows.append([func(name, val)
1374 for func, name, val in zip(converters, names, row)])
1375 rowmasks.append([ismissing(name, val)
1376 for name, val in zip(names, row)])
1377 fh.close()
1379 if not len(rows):
1380 return None
1382 if use_mrecords and np.any(rowmasks):
1383 r = np.ma.mrecords.fromrecords(rows, names=names, mask=rowmasks)
1384 else:
1385 r = np.rec.fromrecords(rows, names=names)
1386 return r
1389class GaussianKDE:
1390 """
1391 Representation of a kernel-density estimate using Gaussian kernels.
1393 Parameters
1394 ----------
1395 dataset : array-like
1396 Datapoints to estimate from. In case of univariate data this is a 1-D
1397 array, otherwise a 2-D array with shape (# of dims, # of data).
1399 bw_method : str, scalar or callable, optional
1400 The method used to calculate the estimator bandwidth. This can be
1401 'scott', 'silverman', a scalar constant or a callable. If a
1402 scalar, this will be used directly as `kde.factor`. If a
1403 callable, it should take a `GaussianKDE` instance as only
1404 parameter and return a scalar. If None (default), 'scott' is used.
1406 Attributes
1407 ----------
1408 dataset : ndarray
1409 The dataset with which `gaussian_kde` was initialized.
1411 dim : int
1412 Number of dimensions.
1414 num_dp : int
1415 Number of datapoints.
1417 factor : float
1418 The bandwidth factor, obtained from `kde.covariance_factor`, with which
1419 the covariance matrix is multiplied.
1421 covariance : ndarray
1422 The covariance matrix of *dataset*, scaled by the calculated bandwidth
1423 (`kde.factor`).
1425 inv_cov : ndarray
1426 The inverse of *covariance*.
1428 Methods
1429 -------
1430 kde.evaluate(points) : ndarray
1431 Evaluate the estimated pdf on a provided set of points.
1433 kde(points) : ndarray
1434 Same as kde.evaluate(points)
1436 """
1438 # This implementation with minor modification was too good to pass up.
1439 # from scipy: https://github.com/scipy/scipy/blob/master/scipy/stats/kde.py
1441 def __init__(self, dataset, bw_method=None):
1442 self.dataset = np.atleast_2d(dataset)
1443 if not np.array(self.dataset).size > 1:
1444 raise ValueError("`dataset` input should have multiple elements.")
1446 self.dim, self.num_dp = np.array(self.dataset).shape
1448 if bw_method is None:
1449 pass
1450 elif cbook._str_equal(bw_method, 'scott'):
1451 self.covariance_factor = self.scotts_factor
1452 elif cbook._str_equal(bw_method, 'silverman'):
1453 self.covariance_factor = self.silverman_factor
1454 elif isinstance(bw_method, Number):
1455 self._bw_method = 'use constant'
1456 self.covariance_factor = lambda: bw_method
1457 elif callable(bw_method):
1458 self._bw_method = bw_method
1459 self.covariance_factor = lambda: self._bw_method(self)
1460 else:
1461 raise ValueError("`bw_method` should be 'scott', 'silverman', a "
1462 "scalar or a callable")
1464 # Computes the covariance matrix for each Gaussian kernel using
1465 # covariance_factor().
1467 self.factor = self.covariance_factor()
1468 # Cache covariance and inverse covariance of the data
1469 if not hasattr(self, '_data_inv_cov'):
1470 self.data_covariance = np.atleast_2d(
1471 np.cov(
1472 self.dataset,
1473 rowvar=1,
1474 bias=False))
1475 self.data_inv_cov = np.linalg.inv(self.data_covariance)
1477 self.covariance = self.data_covariance * self.factor ** 2
1478 self.inv_cov = self.data_inv_cov / self.factor ** 2
1479 self.norm_factor = (np.sqrt(np.linalg.det(2 * np.pi * self.covariance))
1480 * self.num_dp)
1482 def scotts_factor(self):
1483 return np.power(self.num_dp, -1. / (self.dim + 4))
1485 def silverman_factor(self):
1486 return np.power(
1487 self.num_dp * (self.dim + 2.0) / 4.0, -1. / (self.dim + 4))
1489 # Default method to calculate bandwidth, can be overwritten by subclass
1490 covariance_factor = scotts_factor
1492 def evaluate(self, points):
1493 """Evaluate the estimated pdf on a set of points.
1495 Parameters
1496 ----------
1497 points : (# of dimensions, # of points)-array
1498 Alternatively, a (# of dimensions,) vector can be passed in and
1499 treated as a single point.
1501 Returns
1502 -------
1503 values : (# of points,)-array
1504 The values at each point.
1506 Raises
1507 ------
1508 ValueError : if the dimensionality of the input points is different
1509 than the dimensionality of the KDE.
1511 """
1512 points = np.atleast_2d(points)
1514 dim, num_m = np.array(points).shape
1515 if dim != self.dim:
1516 raise ValueError("points have dimension {}, dataset has dimension "
1517 "{}".format(dim, self.dim))
1519 result = np.zeros(num_m)
1521 if num_m >= self.num_dp:
1522 # there are more points than data, so loop over data
1523 for i in range(self.num_dp):
1524 diff = self.dataset[:, i, np.newaxis] - points
1525 tdiff = np.dot(self.inv_cov, diff)
1526 energy = np.sum(diff * tdiff, axis=0) / 2.0
1527 result = result + np.exp(-energy)
1528 else:
1529 # loop over points
1530 for i in range(num_m):
1531 diff = self.dataset - points[:, i, np.newaxis]
1532 tdiff = np.dot(self.inv_cov, diff)
1533 energy = np.sum(diff * tdiff, axis=0) / 2.0
1534 result[i] = np.sum(np.exp(-energy), axis=0)
1536 result = result / self.norm_factor
1538 return result
1540 __call__ = evaluate