Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/mlab.py: 15%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

245 statements  

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 and kernel density estimations. 

6 

7.. _NumPy: https://numpy.org 

8.. _SciPy: https://www.scipy.org 

9 

10Spectral functions 

11------------------ 

12 

13`cohere` 

14 Coherence (normalized cross spectral density) 

15 

16`csd` 

17 Cross spectral density using Welch's average periodogram 

18 

19`detrend` 

20 Remove the mean or best fit line from an array 

21 

22`psd` 

23 Power spectral density using Welch's average periodogram 

24 

25`specgram` 

26 Spectrogram (spectrum over segments of time) 

27 

28`complex_spectrum` 

29 Return the complex-valued frequency spectrum of a signal 

30 

31`magnitude_spectrum` 

32 Return the magnitude of the frequency spectrum of a signal 

33 

34`angle_spectrum` 

35 Return the angle (wrapped phase) of the frequency spectrum of a signal 

36 

37`phase_spectrum` 

38 Return the phase (unwrapped angle) of the frequency spectrum of a signal 

39 

40`detrend_mean` 

41 Remove the mean from a line. 

42 

43`detrend_linear` 

44 Remove the best fit line from a line. 

45 

46`detrend_none` 

47 Return the original line. 

48""" 

49 

50import functools 

51from numbers import Number 

52 

53import numpy as np 

54 

55from matplotlib import _api, _docstring, cbook 

56 

57 

58def window_hanning(x): 

59 """ 

60 Return *x* times the Hanning (or Hann) window of len(*x*). 

61 

62 See Also 

63 -------- 

64 window_none : Another window algorithm. 

65 """ 

66 return np.hanning(len(x))*x 

67 

68 

69def window_none(x): 

70 """ 

71 No window function; simply return *x*. 

72 

73 See Also 

74 -------- 

75 window_hanning : Another window algorithm. 

76 """ 

77 return x 

78 

79 

80def detrend(x, key=None, axis=None): 

81 """ 

82 Return *x* with its trend removed. 

83 

84 Parameters 

85 ---------- 

86 x : array or sequence 

87 Array or sequence containing the data. 

88 

89 key : {'default', 'constant', 'mean', 'linear', 'none'} or function 

90 The detrending algorithm to use. 'default', 'mean', and 'constant' are 

91 the same as `detrend_mean`. 'linear' is the same as `detrend_linear`. 

92 'none' is the same as `detrend_none`. The default is 'mean'. See the 

93 corresponding functions for more details regarding the algorithms. Can 

94 also be a function that carries out the detrend operation. 

95 

96 axis : int 

97 The axis along which to do the detrending. 

98 

99 See Also 

100 -------- 

101 detrend_mean : Implementation of the 'mean' algorithm. 

102 detrend_linear : Implementation of the 'linear' algorithm. 

103 detrend_none : Implementation of the 'none' algorithm. 

104 """ 

105 if key is None or key in ['constant', 'mean', 'default']: 

106 return detrend(x, key=detrend_mean, axis=axis) 

107 elif key == 'linear': 

108 return detrend(x, key=detrend_linear, axis=axis) 

109 elif key == 'none': 

110 return detrend(x, key=detrend_none, axis=axis) 

111 elif callable(key): 

112 x = np.asarray(x) 

113 if axis is not None and axis + 1 > x.ndim: 

114 raise ValueError(f'axis(={axis}) out of bounds') 

115 if (axis is None and x.ndim == 0) or (not axis and x.ndim == 1): 

116 return key(x) 

117 # try to use the 'axis' argument if the function supports it, 

118 # otherwise use apply_along_axis to do it 

119 try: 

120 return key(x, axis=axis) 

121 except TypeError: 

122 return np.apply_along_axis(key, axis=axis, arr=x) 

123 else: 

124 raise ValueError( 

125 f"Unknown value for key: {key!r}, must be one of: 'default', " 

126 f"'constant', 'mean', 'linear', or a function") 

127 

128 

129def detrend_mean(x, axis=None): 

130 """ 

131 Return *x* minus the mean(*x*). 

132 

133 Parameters 

134 ---------- 

135 x : array or sequence 

136 Array or sequence containing the data 

137 Can have any dimensionality 

138 

139 axis : int 

140 The axis along which to take the mean. See `numpy.mean` for a 

141 description of this argument. 

142 

143 See Also 

144 -------- 

145 detrend_linear : Another detrend algorithm. 

146 detrend_none : Another detrend algorithm. 

147 detrend : A wrapper around all the detrend algorithms. 

148 """ 

149 x = np.asarray(x) 

150 

151 if axis is not None and axis+1 > x.ndim: 

152 raise ValueError('axis(=%s) out of bounds' % axis) 

153 

154 return x - x.mean(axis, keepdims=True) 

155 

156 

157def detrend_none(x, axis=None): 

158 """ 

159 Return *x*: no detrending. 

160 

161 Parameters 

162 ---------- 

163 x : any object 

164 An object containing the data 

165 

166 axis : int 

167 This parameter is ignored. 

168 It is included for compatibility with detrend_mean 

169 

170 See Also 

171 -------- 

172 detrend_mean : Another detrend algorithm. 

173 detrend_linear : Another detrend algorithm. 

174 detrend : A wrapper around all the detrend algorithms. 

175 """ 

176 return x 

177 

178 

179def detrend_linear(y): 

180 """ 

181 Return *x* minus best fit line; 'linear' detrending. 

182 

183 Parameters 

184 ---------- 

185 y : 0-D or 1-D array or sequence 

186 Array or sequence containing the data 

187 

188 See Also 

189 -------- 

190 detrend_mean : Another detrend algorithm. 

191 detrend_none : Another detrend algorithm. 

192 detrend : A wrapper around all the detrend algorithms. 

193 """ 

194 # This is faster than an algorithm based on linalg.lstsq. 

195 y = np.asarray(y) 

196 

197 if y.ndim > 1: 

198 raise ValueError('y cannot have ndim > 1') 

199 

200 # short-circuit 0-D array. 

201 if not y.ndim: 

202 return np.array(0., dtype=y.dtype) 

203 

204 x = np.arange(y.size, dtype=float) 

205 

206 C = np.cov(x, y, bias=1) 

207 b = C[0, 1]/C[0, 0] 

208 

209 a = y.mean() - b*x.mean() 

210 return y - (b*x + a) 

211 

212 

213def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, 

214 window=None, noverlap=None, pad_to=None, 

215 sides=None, scale_by_freq=None, mode=None): 

216 """ 

217 Private helper implementing the common parts between the psd, csd, 

218 spectrogram and complex, magnitude, angle, and phase spectrums. 

219 """ 

220 if y is None: 

221 # if y is None use x for y 

222 same_data = True 

223 else: 

224 # The checks for if y is x are so that we can use the same function to 

225 # implement the core of psd(), csd(), and spectrogram() without doing 

226 # extra calculations. We return the unaveraged Pxy, freqs, and t. 

227 same_data = y is x 

228 

229 if Fs is None: 

230 Fs = 2 

231 if noverlap is None: 

232 noverlap = 0 

233 if detrend_func is None: 

234 detrend_func = detrend_none 

235 if window is None: 

236 window = window_hanning 

237 

238 # if NFFT is set to None use the whole signal 

239 if NFFT is None: 

240 NFFT = 256 

241 

242 if noverlap >= NFFT: 

243 raise ValueError('noverlap must be less than NFFT') 

244 

245 if mode is None or mode == 'default': 

246 mode = 'psd' 

247 _api.check_in_list( 

248 ['default', 'psd', 'complex', 'magnitude', 'angle', 'phase'], 

249 mode=mode) 

250 

251 if not same_data and mode != 'psd': 

252 raise ValueError("x and y must be equal if mode is not 'psd'") 

253 

254 # Make sure we're dealing with a numpy array. If y and x were the same 

255 # object to start with, keep them that way 

256 x = np.asarray(x) 

257 if not same_data: 

258 y = np.asarray(y) 

259 

260 if sides is None or sides == 'default': 

261 if np.iscomplexobj(x): 

262 sides = 'twosided' 

263 else: 

264 sides = 'onesided' 

265 _api.check_in_list(['default', 'onesided', 'twosided'], sides=sides) 

266 

267 # zero pad x and y up to NFFT if they are shorter than NFFT 

268 if len(x) < NFFT: 

269 n = len(x) 

270 x = np.resize(x, NFFT) 

271 x[n:] = 0 

272 

273 if not same_data and len(y) < NFFT: 

274 n = len(y) 

275 y = np.resize(y, NFFT) 

276 y[n:] = 0 

277 

278 if pad_to is None: 

279 pad_to = NFFT 

280 

281 if mode != 'psd': 

282 scale_by_freq = False 

283 elif scale_by_freq is None: 

284 scale_by_freq = True 

285 

286 # For real x, ignore the negative frequencies unless told otherwise 

287 if sides == 'twosided': 

288 numFreqs = pad_to 

289 if pad_to % 2: 

290 freqcenter = (pad_to - 1)//2 + 1 

291 else: 

292 freqcenter = pad_to//2 

293 scaling_factor = 1. 

294 elif sides == 'onesided': 

295 if pad_to % 2: 

296 numFreqs = (pad_to + 1)//2 

297 else: 

298 numFreqs = pad_to//2 + 1 

299 scaling_factor = 2. 

300 

301 if not np.iterable(window): 

302 window = window(np.ones(NFFT, x.dtype)) 

303 if len(window) != NFFT: 

304 raise ValueError( 

305 "The window length must match the data's first dimension") 

306 

307 result = np.lib.stride_tricks.sliding_window_view( 

308 x, NFFT, axis=0)[::NFFT - noverlap].T 

309 result = detrend(result, detrend_func, axis=0) 

310 result = result * window.reshape((-1, 1)) 

311 result = np.fft.fft(result, n=pad_to, axis=0)[:numFreqs, :] 

312 freqs = np.fft.fftfreq(pad_to, 1/Fs)[:numFreqs] 

313 

314 if not same_data: 

315 # if same_data is False, mode must be 'psd' 

316 resultY = np.lib.stride_tricks.sliding_window_view( 

317 y, NFFT, axis=0)[::NFFT - noverlap].T 

318 resultY = detrend(resultY, detrend_func, axis=0) 

319 resultY = resultY * window.reshape((-1, 1)) 

320 resultY = np.fft.fft(resultY, n=pad_to, axis=0)[:numFreqs, :] 

321 result = np.conj(result) * resultY 

322 elif mode == 'psd': 

323 result = np.conj(result) * result 

324 elif mode == 'magnitude': 

325 result = np.abs(result) / window.sum() 

326 elif mode == 'angle' or mode == 'phase': 

327 # we unwrap the phase later to handle the onesided vs. twosided case 

328 result = np.angle(result) 

329 elif mode == 'complex': 

330 result /= window.sum() 

331 

332 if mode == 'psd': 

333 

334 # Also include scaling factors for one-sided densities and dividing by 

335 # the sampling frequency, if desired. Scale everything, except the DC 

336 # component and the NFFT/2 component: 

337 

338 # if we have a even number of frequencies, don't scale NFFT/2 

339 if not NFFT % 2: 

340 slc = slice(1, -1, None) 

341 # if we have an odd number, just don't scale DC 

342 else: 

343 slc = slice(1, None, None) 

344 

345 result[slc] *= scaling_factor 

346 

347 # MATLAB divides by the sampling frequency so that density function 

348 # has units of dB/Hz and can be integrated by the plotted frequency 

349 # values. Perform the same scaling here. 

350 if scale_by_freq: 

351 result /= Fs 

352 # Scale the spectrum by the norm of the window to compensate for 

353 # windowing loss; see Bendat & Piersol Sec 11.5.2. 

354 result /= (window**2).sum() 

355 else: 

356 # In this case, preserve power in the segment, not amplitude 

357 result /= window.sum()**2 

358 

359 t = np.arange(NFFT/2, len(x) - NFFT/2 + 1, NFFT - noverlap)/Fs 

360 

361 if sides == 'twosided': 

362 # center the frequency range at zero 

363 freqs = np.roll(freqs, -freqcenter, axis=0) 

364 result = np.roll(result, -freqcenter, axis=0) 

365 elif not pad_to % 2: 

366 # get the last value correctly, it is negative otherwise 

367 freqs[-1] *= -1 

368 

369 # we unwrap the phase here to handle the onesided vs. twosided case 

370 if mode == 'phase': 

371 result = np.unwrap(result, axis=0) 

372 

373 return result, freqs, t 

374 

375 

376def _single_spectrum_helper( 

377 mode, x, Fs=None, window=None, pad_to=None, sides=None): 

378 """ 

379 Private helper implementing the commonality between the complex, magnitude, 

380 angle, and phase spectrums. 

381 """ 

382 _api.check_in_list(['complex', 'magnitude', 'angle', 'phase'], mode=mode) 

383 

384 if pad_to is None: 

385 pad_to = len(x) 

386 

387 spec, freqs, _ = _spectral_helper(x=x, y=None, NFFT=len(x), Fs=Fs, 

388 detrend_func=detrend_none, window=window, 

389 noverlap=0, pad_to=pad_to, 

390 sides=sides, 

391 scale_by_freq=False, 

392 mode=mode) 

393 if mode != 'complex': 

394 spec = spec.real 

395 

396 if spec.ndim == 2 and spec.shape[1] == 1: 

397 spec = spec[:, 0] 

398 

399 return spec, freqs 

400 

401 

402# Split out these keyword docs so that they can be used elsewhere 

403_docstring.interpd.update( 

404 Spectral="""\ 

405Fs : float, default: 2 

406 The sampling frequency (samples per time unit). It is used to calculate 

407 the Fourier frequencies, *freqs*, in cycles per time unit. 

408 

409window : callable or ndarray, default: `.window_hanning` 

410 A function or a vector of length *NFFT*. To create window vectors see 

411 `.window_hanning`, `.window_none`, `numpy.blackman`, `numpy.hamming`, 

412 `numpy.bartlett`, `scipy.signal`, `scipy.signal.get_window`, etc. If a 

413 function is passed as the argument, it must take a data segment as an 

414 argument and return the windowed version of the segment. 

415 

416sides : {'default', 'onesided', 'twosided'}, optional 

417 Which sides of the spectrum to return. 'default' is one-sided for real 

418 data and two-sided for complex data. 'onesided' forces the return of a 

419 one-sided spectrum, while 'twosided' forces two-sided.""", 

420 

421 Single_Spectrum="""\ 

422pad_to : int, optional 

423 The number of points to which the data segment is padded when performing 

424 the FFT. While not increasing the actual resolution of the spectrum (the 

425 minimum distance between resolvable peaks), this can give more points in 

426 the plot, allowing for more detail. This corresponds to the *n* parameter 

427 in the call to `~numpy.fft.fft`. The default is None, which sets *pad_to* 

428 equal to the length of the input signal (i.e. no padding).""", 

429 

430 PSD="""\ 

431pad_to : int, optional 

432 The number of points to which the data segment is padded when performing 

433 the FFT. This can be different from *NFFT*, which specifies the number 

434 of data points used. While not increasing the actual resolution of the 

435 spectrum (the minimum distance between resolvable peaks), this can give 

436 more points in the plot, allowing for more detail. This corresponds to 

437 the *n* parameter in the call to `~numpy.fft.fft`. The default is None, 

438 which sets *pad_to* equal to *NFFT* 

439 

440NFFT : int, default: 256 

441 The number of data points used in each block for the FFT. A power 2 is 

442 most efficient. This should *NOT* be used to get zero padding, or the 

443 scaling of the result will be incorrect; use *pad_to* for this instead. 

444 

445detrend : {'none', 'mean', 'linear'} or callable, default: 'none' 

446 The function applied to each segment before fft-ing, designed to remove 

447 the mean or linear trend. Unlike in MATLAB, where the *detrend* parameter 

448 is a vector, in Matplotlib it is a function. The :mod:`~matplotlib.mlab` 

449 module defines `.detrend_none`, `.detrend_mean`, and `.detrend_linear`, 

450 but you can use a custom function as well. You can also use a string to 

451 choose one of the functions: 'none' calls `.detrend_none`. 'mean' calls 

452 `.detrend_mean`. 'linear' calls `.detrend_linear`. 

453 

454scale_by_freq : bool, default: True 

455 Whether the resulting density values should be scaled by the scaling 

456 frequency, which gives density in units of 1/Hz. This allows for 

457 integration over the returned frequency values. The default is True for 

458 MATLAB compatibility.""") 

459 

460 

461@_docstring.dedent_interpd 

462def psd(x, NFFT=None, Fs=None, detrend=None, window=None, 

463 noverlap=None, pad_to=None, sides=None, scale_by_freq=None): 

464 r""" 

465 Compute the power spectral density. 

466 

467 The power spectral density :math:`P_{xx}` by Welch's average 

468 periodogram method. The vector *x* is divided into *NFFT* length 

469 segments. Each segment is detrended by function *detrend* and 

470 windowed by function *window*. *noverlap* gives the length of 

471 the overlap between segments. The :math:`|\mathrm{fft}(i)|^2` 

472 of each segment :math:`i` are averaged to compute :math:`P_{xx}`. 

473 

474 If len(*x*) < *NFFT*, it will be zero padded to *NFFT*. 

475 

476 Parameters 

477 ---------- 

478 x : 1-D array or sequence 

479 Array or sequence containing the data 

480 

481 %(Spectral)s 

482 

483 %(PSD)s 

484 

485 noverlap : int, default: 0 (no overlap) 

486 The number of points of overlap between segments. 

487 

488 Returns 

489 ------- 

490 Pxx : 1-D array 

491 The values for the power spectrum :math:`P_{xx}` (real valued) 

492 

493 freqs : 1-D array 

494 The frequencies corresponding to the elements in *Pxx* 

495 

496 References 

497 ---------- 

498 Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John 

499 Wiley & Sons (1986) 

500 

501 See Also 

502 -------- 

503 specgram 

504 `specgram` differs in the default overlap; in not returning the mean of 

505 the segment periodograms; and in returning the times of the segments. 

506 

507 magnitude_spectrum : returns the magnitude spectrum. 

508 

509 csd : returns the spectral density between two signals. 

510 """ 

511 Pxx, freqs = csd(x=x, y=None, NFFT=NFFT, Fs=Fs, detrend=detrend, 

512 window=window, noverlap=noverlap, pad_to=pad_to, 

513 sides=sides, scale_by_freq=scale_by_freq) 

514 return Pxx.real, freqs 

515 

516 

517@_docstring.dedent_interpd 

518def csd(x, y, NFFT=None, Fs=None, detrend=None, window=None, 

519 noverlap=None, pad_to=None, sides=None, scale_by_freq=None): 

520 """ 

521 Compute the cross-spectral density. 

522 

523 The cross spectral density :math:`P_{xy}` by Welch's average 

524 periodogram method. The vectors *x* and *y* are divided into 

525 *NFFT* length segments. Each segment is detrended by function 

526 *detrend* and windowed by function *window*. *noverlap* gives 

527 the length of the overlap between segments. The product of 

528 the direct FFTs of *x* and *y* are averaged over each segment 

529 to compute :math:`P_{xy}`, with a scaling to correct for power 

530 loss due to windowing. 

531 

532 If len(*x*) < *NFFT* or len(*y*) < *NFFT*, they will be zero 

533 padded to *NFFT*. 

534 

535 Parameters 

536 ---------- 

537 x, y : 1-D arrays or sequences 

538 Arrays or sequences containing the data 

539 

540 %(Spectral)s 

541 

542 %(PSD)s 

543 

544 noverlap : int, default: 0 (no overlap) 

545 The number of points of overlap between segments. 

546 

547 Returns 

548 ------- 

549 Pxy : 1-D array 

550 The values for the cross spectrum :math:`P_{xy}` before scaling (real 

551 valued) 

552 

553 freqs : 1-D array 

554 The frequencies corresponding to the elements in *Pxy* 

555 

556 References 

557 ---------- 

558 Bendat & Piersol -- Random Data: Analysis and Measurement Procedures, John 

559 Wiley & Sons (1986) 

560 

561 See Also 

562 -------- 

563 psd : equivalent to setting ``y = x``. 

564 """ 

565 if NFFT is None: 

566 NFFT = 256 

567 Pxy, freqs, _ = _spectral_helper(x=x, y=y, NFFT=NFFT, Fs=Fs, 

568 detrend_func=detrend, window=window, 

569 noverlap=noverlap, pad_to=pad_to, 

570 sides=sides, scale_by_freq=scale_by_freq, 

571 mode='psd') 

572 

573 if Pxy.ndim == 2: 

574 if Pxy.shape[1] > 1: 

575 Pxy = Pxy.mean(axis=1) 

576 else: 

577 Pxy = Pxy[:, 0] 

578 return Pxy, freqs 

579 

580 

581_single_spectrum_docs = """\ 

582Compute the {quantity} of *x*. 

583Data is padded to a length of *pad_to* and the windowing function *window* is 

584applied to the signal. 

585 

586Parameters 

587---------- 

588x : 1-D array or sequence 

589 Array or sequence containing the data 

590 

591{Spectral} 

592 

593{Single_Spectrum} 

594 

595Returns 

596------- 

597spectrum : 1-D array 

598 The {quantity}. 

599freqs : 1-D array 

600 The frequencies corresponding to the elements in *spectrum*. 

601 

602See Also 

603-------- 

604psd 

605 Returns the power spectral density. 

606complex_spectrum 

607 Returns the complex-valued frequency spectrum. 

608magnitude_spectrum 

609 Returns the absolute value of the `complex_spectrum`. 

610angle_spectrum 

611 Returns the angle of the `complex_spectrum`. 

612phase_spectrum 

613 Returns the phase (unwrapped angle) of the `complex_spectrum`. 

614specgram 

615 Can return the complex spectrum of segments within the signal. 

616""" 

617 

618 

619complex_spectrum = functools.partial(_single_spectrum_helper, "complex") 

620complex_spectrum.__doc__ = _single_spectrum_docs.format( 

621 quantity="complex-valued frequency spectrum", 

622 **_docstring.interpd.params) 

623magnitude_spectrum = functools.partial(_single_spectrum_helper, "magnitude") 

624magnitude_spectrum.__doc__ = _single_spectrum_docs.format( 

625 quantity="magnitude (absolute value) of the frequency spectrum", 

626 **_docstring.interpd.params) 

627angle_spectrum = functools.partial(_single_spectrum_helper, "angle") 

628angle_spectrum.__doc__ = _single_spectrum_docs.format( 

629 quantity="angle of the frequency spectrum (wrapped phase spectrum)", 

630 **_docstring.interpd.params) 

631phase_spectrum = functools.partial(_single_spectrum_helper, "phase") 

632phase_spectrum.__doc__ = _single_spectrum_docs.format( 

633 quantity="phase of the frequency spectrum (unwrapped phase spectrum)", 

634 **_docstring.interpd.params) 

635 

636 

637@_docstring.dedent_interpd 

638def specgram(x, NFFT=None, Fs=None, detrend=None, window=None, 

639 noverlap=None, pad_to=None, sides=None, scale_by_freq=None, 

640 mode=None): 

641 """ 

642 Compute a spectrogram. 

643 

644 Compute and plot a spectrogram of data in *x*. Data are split into 

645 *NFFT* length segments and the spectrum of each section is 

646 computed. The windowing function *window* is applied to each 

647 segment, and the amount of overlap of each segment is 

648 specified with *noverlap*. 

649 

650 Parameters 

651 ---------- 

652 x : array-like 

653 1-D array or sequence. 

654 

655 %(Spectral)s 

656 

657 %(PSD)s 

658 

659 noverlap : int, default: 128 

660 The number of points of overlap between blocks. 

661 mode : str, default: 'psd' 

662 What sort of spectrum to use: 

663 'psd' 

664 Returns the power spectral density. 

665 'complex' 

666 Returns the complex-valued frequency spectrum. 

667 'magnitude' 

668 Returns the magnitude spectrum. 

669 'angle' 

670 Returns the phase spectrum without unwrapping. 

671 'phase' 

672 Returns the phase spectrum with unwrapping. 

673 

674 Returns 

675 ------- 

676 spectrum : array-like 

677 2D array, columns are the periodograms of successive segments. 

678 

679 freqs : array-like 

680 1-D array, frequencies corresponding to the rows in *spectrum*. 

681 

682 t : array-like 

683 1-D array, the times corresponding to midpoints of segments 

684 (i.e the columns in *spectrum*). 

685 

686 See Also 

687 -------- 

688 psd : differs in the overlap and in the return values. 

689 complex_spectrum : similar, but with complex valued frequencies. 

690 magnitude_spectrum : similar single segment when *mode* is 'magnitude'. 

691 angle_spectrum : similar to single segment when *mode* is 'angle'. 

692 phase_spectrum : similar to single segment when *mode* is 'phase'. 

693 

694 Notes 

695 ----- 

696 *detrend* and *scale_by_freq* only apply when *mode* is set to 'psd'. 

697 

698 """ 

699 if noverlap is None: 

700 noverlap = 128 # default in _spectral_helper() is noverlap = 0 

701 if NFFT is None: 

702 NFFT = 256 # same default as in _spectral_helper() 

703 if len(x) <= NFFT: 

704 _api.warn_external("Only one segment is calculated since parameter " 

705 f"NFFT (={NFFT}) >= signal length (={len(x)}).") 

706 

707 spec, freqs, t = _spectral_helper(x=x, y=None, NFFT=NFFT, Fs=Fs, 

708 detrend_func=detrend, window=window, 

709 noverlap=noverlap, pad_to=pad_to, 

710 sides=sides, 

711 scale_by_freq=scale_by_freq, 

712 mode=mode) 

713 

714 if mode != 'complex': 

715 spec = spec.real # Needed since helper implements generically 

716 

717 return spec, freqs, t 

718 

719 

720@_docstring.dedent_interpd 

721def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning, 

722 noverlap=0, pad_to=None, sides='default', scale_by_freq=None): 

723 r""" 

724 The coherence between *x* and *y*. Coherence is the normalized 

725 cross spectral density: 

726 

727 .. math:: 

728 

729 C_{xy} = \frac{|P_{xy}|^2}{P_{xx}P_{yy}} 

730 

731 Parameters 

732 ---------- 

733 x, y 

734 Array or sequence containing the data 

735 

736 %(Spectral)s 

737 

738 %(PSD)s 

739 

740 noverlap : int, default: 0 (no overlap) 

741 The number of points of overlap between segments. 

742 

743 Returns 

744 ------- 

745 Cxy : 1-D array 

746 The coherence vector. 

747 freqs : 1-D array 

748 The frequencies for the elements in *Cxy*. 

749 

750 See Also 

751 -------- 

752 :func:`psd`, :func:`csd` : 

753 For information about the methods used to compute :math:`P_{xy}`, 

754 :math:`P_{xx}` and :math:`P_{yy}`. 

755 """ 

756 if len(x) < 2 * NFFT: 

757 raise ValueError( 

758 "Coherence is calculated by averaging over *NFFT* length " 

759 "segments. Your signal is too short for your choice of *NFFT*.") 

760 Pxx, f = psd(x, NFFT, Fs, detrend, window, noverlap, pad_to, sides, 

761 scale_by_freq) 

762 Pyy, f = psd(y, NFFT, Fs, detrend, window, noverlap, pad_to, sides, 

763 scale_by_freq) 

764 Pxy, f = csd(x, y, NFFT, Fs, detrend, window, noverlap, pad_to, sides, 

765 scale_by_freq) 

766 Cxy = np.abs(Pxy) ** 2 / (Pxx * Pyy) 

767 return Cxy, f 

768 

769 

770class GaussianKDE: 

771 """ 

772 Representation of a kernel-density estimate using Gaussian kernels. 

773 

774 Parameters 

775 ---------- 

776 dataset : array-like 

777 Datapoints to estimate from. In case of univariate data this is a 1-D 

778 array, otherwise a 2D array with shape (# of dims, # of data). 

779 bw_method : {'scott', 'silverman'} or float or callable, optional 

780 The method used to calculate the estimator bandwidth. If a 

781 float, this will be used directly as `kde.factor`. If a 

782 callable, it should take a `GaussianKDE` instance as only 

783 parameter and return a float. If None (default), 'scott' is used. 

784 

785 Attributes 

786 ---------- 

787 dataset : ndarray 

788 The dataset passed to the constructor. 

789 dim : int 

790 Number of dimensions. 

791 num_dp : int 

792 Number of datapoints. 

793 factor : float 

794 The bandwidth factor, obtained from `kde.covariance_factor`, with which 

795 the covariance matrix is multiplied. 

796 covariance : ndarray 

797 The covariance matrix of *dataset*, scaled by the calculated bandwidth 

798 (`kde.factor`). 

799 inv_cov : ndarray 

800 The inverse of *covariance*. 

801 

802 Methods 

803 ------- 

804 kde.evaluate(points) : ndarray 

805 Evaluate the estimated pdf on a provided set of points. 

806 kde(points) : ndarray 

807 Same as kde.evaluate(points) 

808 """ 

809 

810 # This implementation with minor modification was too good to pass up. 

811 # from scipy: https://github.com/scipy/scipy/blob/master/scipy/stats/kde.py 

812 

813 def __init__(self, dataset, bw_method=None): 

814 self.dataset = np.atleast_2d(dataset) 

815 if not np.array(self.dataset).size > 1: 

816 raise ValueError("`dataset` input should have multiple elements.") 

817 

818 self.dim, self.num_dp = np.array(self.dataset).shape 

819 

820 if bw_method is None: 

821 pass 

822 elif cbook._str_equal(bw_method, 'scott'): 

823 self.covariance_factor = self.scotts_factor 

824 elif cbook._str_equal(bw_method, 'silverman'): 

825 self.covariance_factor = self.silverman_factor 

826 elif isinstance(bw_method, Number): 

827 self._bw_method = 'use constant' 

828 self.covariance_factor = lambda: bw_method 

829 elif callable(bw_method): 

830 self._bw_method = bw_method 

831 self.covariance_factor = lambda: self._bw_method(self) 

832 else: 

833 raise ValueError("`bw_method` should be 'scott', 'silverman', a " 

834 "scalar or a callable") 

835 

836 # Computes the covariance matrix for each Gaussian kernel using 

837 # covariance_factor(). 

838 

839 self.factor = self.covariance_factor() 

840 # Cache covariance and inverse covariance of the data 

841 if not hasattr(self, '_data_inv_cov'): 

842 self.data_covariance = np.atleast_2d( 

843 np.cov( 

844 self.dataset, 

845 rowvar=1, 

846 bias=False)) 

847 self.data_inv_cov = np.linalg.inv(self.data_covariance) 

848 

849 self.covariance = self.data_covariance * self.factor ** 2 

850 self.inv_cov = self.data_inv_cov / self.factor ** 2 

851 self.norm_factor = (np.sqrt(np.linalg.det(2 * np.pi * self.covariance)) 

852 * self.num_dp) 

853 

854 def scotts_factor(self): 

855 return np.power(self.num_dp, -1. / (self.dim + 4)) 

856 

857 def silverman_factor(self): 

858 return np.power( 

859 self.num_dp * (self.dim + 2.0) / 4.0, -1. / (self.dim + 4)) 

860 

861 # Default method to calculate bandwidth, can be overwritten by subclass 

862 covariance_factor = scotts_factor 

863 

864 def evaluate(self, points): 

865 """ 

866 Evaluate the estimated pdf on a set of points. 

867 

868 Parameters 

869 ---------- 

870 points : (# of dimensions, # of points)-array 

871 Alternatively, a (# of dimensions,) vector can be passed in and 

872 treated as a single point. 

873 

874 Returns 

875 ------- 

876 (# of points,)-array 

877 The values at each point. 

878 

879 Raises 

880 ------ 

881 ValueError : if the dimensionality of the input points is different 

882 than the dimensionality of the KDE. 

883 

884 """ 

885 points = np.atleast_2d(points) 

886 

887 dim, num_m = np.array(points).shape 

888 if dim != self.dim: 

889 raise ValueError(f"points have dimension {dim}, dataset has " 

890 f"dimension {self.dim}") 

891 

892 result = np.zeros(num_m) 

893 

894 if num_m >= self.num_dp: 

895 # there are more points than data, so loop over data 

896 for i in range(self.num_dp): 

897 diff = self.dataset[:, i, np.newaxis] - points 

898 tdiff = np.dot(self.inv_cov, diff) 

899 energy = np.sum(diff * tdiff, axis=0) / 2.0 

900 result = result + np.exp(-energy) 

901 else: 

902 # loop over points 

903 for i in range(num_m): 

904 diff = self.dataset - points[:, i, np.newaxis] 

905 tdiff = np.dot(self.inv_cov, diff) 

906 energy = np.sum(diff * tdiff, axis=0) / 2.0 

907 result[i] = np.sum(np.exp(-energy), axis=0) 

908 

909 result = result / self.norm_factor 

910 

911 return result 

912 

913 __call__ = evaluate