Hide keyboard shortcuts

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. 

6 

7Spectral functions 

8------------------- 

9 

10`cohere` 

11 Coherence (normalized cross spectral density) 

12 

13`csd` 

14 Cross spectral density using Welch's average periodogram 

15 

16`detrend` 

17 Remove the mean or best fit line from an array 

18 

19`psd` 

20 Power spectral density using Welch's average periodogram 

21 

22`specgram` 

23 Spectrogram (spectrum over segments of time) 

24 

25`complex_spectrum` 

26 Return the complex-valued frequency spectrum of a signal 

27 

28`magnitude_spectrum` 

29 Return the magnitude of the frequency spectrum of a signal 

30 

31`angle_spectrum` 

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

33 

34`phase_spectrum` 

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

36 

37`detrend_mean` 

38 Remove the mean from a line. 

39 

40`detrend_linear` 

41 Remove the best fit line from a line. 

42 

43`detrend_none` 

44 Return the original line. 

45 

46`stride_windows` 

47 Get all windows in an array in a memory-efficient manner 

48 

49`stride_repeat` 

50 Repeat an array in a memory-efficient manner 

51 

52`apply_window` 

53 Apply a window along a given axis 

54""" 

55 

56import csv 

57import inspect 

58from numbers import Number 

59 

60import numpy as np 

61 

62import matplotlib.cbook as cbook 

63from matplotlib import docstring 

64 

65 

66def window_hanning(x): 

67 ''' 

68 Return x times the hanning window of len(x). 

69 

70 See Also 

71 -------- 

72 window_none : Another window algorithm. 

73 ''' 

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

75 

76 

77def window_none(x): 

78 ''' 

79 No window function; simply return x. 

80 

81 See Also 

82 -------- 

83 window_hanning : Another window algorithm. 

84 ''' 

85 return x 

86 

87 

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. 

92 

93 Parameters 

94 ---------- 

95 x : 1D or 2D array or sequence 

96 Array or sequence containing the data. 

97 

98 window : function or array. 

99 Either a function to generate a window or an array with length 

100 *x*.shape[*axis*] 

101 

102 axis : integer 

103 The axis over which to do the repetition. 

104 Must be 0 or 1. The default is 0 

105 

106 return_window : bool 

107 If true, also return the 1D values of the window that was applied 

108 ''' 

109 x = np.asarray(x) 

110 

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) 

115 

116 xshape = list(x.shape) 

117 xshapetarg = xshape.pop(axis) 

118 

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)) 

126 

127 if x.ndim == 1: 

128 if return_window: 

129 return windowVals * x, windowVals 

130 else: 

131 return windowVals * x 

132 

133 xshapeother = xshape.pop() 

134 

135 otheraxis = (axis+1) % 2 

136 

137 windowValsRep = stride_repeat(windowVals, xshapeother, axis=otheraxis) 

138 

139 if return_window: 

140 return windowValsRep * x, windowVals 

141 else: 

142 return windowValsRep * x 

143 

144 

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

146 ''' 

147 Return x with its trend removed. 

148 

149 Parameters 

150 ---------- 

151 x : array or sequence 

152 Array or sequence containing the data. 

153 

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. 

161 

162 axis : integer 

163 The axis along which to do the detrending. 

164 

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") 

193 

194 

195@cbook.deprecated("3.1", alternative="detrend_mean") 

196def demean(x, axis=0): 

197 ''' 

198 Return x minus its mean along the specified axis. 

199 

200 Parameters 

201 ---------- 

202 x : array or sequence 

203 Array or sequence containing the data 

204 Can have any dimensionality 

205 

206 axis : integer 

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

208 description of this argument. 

209 

210 See Also 

211 -------- 

212 detrend_mean : Same as `demean` except for the default *axis*. 

213 ''' 

214 return detrend_mean(x, axis=axis) 

215 

216 

217def detrend_mean(x, axis=None): 

218 ''' 

219 Return x minus the mean(x). 

220 

221 Parameters 

222 ---------- 

223 x : array or sequence 

224 Array or sequence containing the data 

225 Can have any dimensionality 

226 

227 axis : integer 

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

229 description of this argument. 

230 

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) 

238 

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

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

241 

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

243 

244 

245def detrend_none(x, axis=None): 

246 ''' 

247 Return x: no detrending. 

248 

249 Parameters 

250 ---------- 

251 x : any object 

252 An object containing the data 

253 

254 axis : integer 

255 This parameter is ignored. 

256 It is included for compatibility with detrend_mean 

257 

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 

265 

266 

267def detrend_linear(y): 

268 ''' 

269 Return x minus best fit line; 'linear' detrending. 

270 

271 Parameters 

272 ---------- 

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

274 Array or sequence containing the data 

275 

276 axis : integer 

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

278 description of this argument. 

279 

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) 

288 

289 if y.ndim > 1: 

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

291 

292 # short-circuit 0-D array. 

293 if not y.ndim: 

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

295 

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

297 

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

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

300 

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

302 return y - (b*x + a) 

303 

304 

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. 

309 

310 .. warning:: 

311 

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. 

315 

316 Parameters 

317 ---------- 

318 x : 1D array or sequence 

319 Array or sequence containing the data. 

320 

321 n : integer 

322 The number of data points in each window. 

323 

324 noverlap : integer 

325 The overlap between adjacent windows. 

326 Default is 0 (no overlap) 

327 

328 axis : integer 

329 The axis along which the windows will run. 

330 

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 

340 

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') 

345 

346 x = np.asarray(x) 

347 

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') 

357 

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) 

362 

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) 

371 

372 

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. 

378 

379 .. warning:: 

380 

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. 

384 

385 Parameters 

386 ---------- 

387 x : 1D array or sequence 

388 Array or sequence containing the data. 

389 

390 n : integer 

391 The number of time to repeat the array. 

392 

393 axis : integer 

394 The axis along which the data will run. 

395 

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') 

406 

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') 

414 

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) 

418 

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) 

425 

426 return np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides) 

427 

428 

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 

445 

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 

454 

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

456 if NFFT is None: 

457 NFFT = 256 

458 

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) 

464 

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

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

467 

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) 

473 

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) 

480 

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 

486 

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

488 n = len(y) 

489 y = np.resize(y, NFFT) 

490 y[n:] = 0 

491 

492 if pad_to is None: 

493 pad_to = NFFT 

494 

495 if mode != 'psd': 

496 scale_by_freq = False 

497 elif scale_by_freq is None: 

498 scale_by_freq = True 

499 

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. 

514 

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") 

520 

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] 

526 

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() 

543 

544 if mode == 'psd': 

545 

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: 

549 

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) 

556 

557 result[slc] *= scaling_factor 

558 

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 

570 

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

572 

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 

581 

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

583 if mode == 'phase': 

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

585 

586 return result, freqs, t 

587 

588 

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) 

597 

598 if pad_to is None: 

599 pad_to = len(x) 

600 

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 

609 

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

611 spec = spec[:, 0] 

612 

613 return spec, freqs 

614 

615 

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. 

622 

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. 

630 

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""")) 

637 

638 

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""")) 

649 

650 

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* 

661 

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. 

667 

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`. 

677 

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""")) 

684 

685 

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. 

691 

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}`. 

698 

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

700 

701 Parameters 

702 ---------- 

703 x : 1-D array or sequence 

704 Array or sequence containing the data 

705 

706 %(Spectral)s 

707 

708 %(PSD)s 

709 

710 noverlap : integer 

711 The number of points of overlap between segments. 

712 The default value is 0 (no overlap). 

713 

714 Returns 

715 ------- 

716 Pxx : 1-D array 

717 The values for the power spectrum `P_{xx}` (real valued) 

718 

719 freqs : 1-D array 

720 The frequencies corresponding to the elements in *Pxx* 

721 

722 References 

723 ---------- 

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

725 Wiley & Sons (1986) 

726 

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. 

732 

733 magnitude_spectrum : returns the magnitude spectrum. 

734 

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 

741 

742 

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. 

748 

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. 

757 

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

759 padded to *NFFT*. 

760 

761 Parameters 

762 ---------- 

763 x, y : 1-D arrays or sequences 

764 Arrays or sequences containing the data 

765 

766 %(Spectral)s 

767 

768 %(PSD)s 

769 

770 noverlap : integer 

771 The number of points of overlap between segments. 

772 The default value is 0 (no overlap). 

773 

774 Returns 

775 ------- 

776 Pxy : 1-D array 

777 The values for the cross spectrum `P_{xy}` before scaling (real valued) 

778 

779 freqs : 1-D array 

780 The frequencies corresponding to the elements in *Pxy* 

781 

782 References 

783 ---------- 

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

785 Wiley & Sons (1986) 

786 

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') 

798 

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 

805 

806 

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. 

814 

815 Parameters 

816 ---------- 

817 x : 1-D array or sequence 

818 Array or sequence containing the data 

819 

820 %(Spectral)s 

821 

822 %(Single_Spectrum)s 

823 

824 Returns 

825 ------- 

826 spectrum : 1-D array 

827 The values for the complex spectrum (complex valued) 

828 

829 freqs : 1-D array 

830 The frequencies corresponding to the elements in *spectrum* 

831 

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') 

845 

846 

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. 

854 

855 Parameters 

856 ---------- 

857 x : 1-D array or sequence 

858 Array or sequence containing the data 

859 

860 %(Spectral)s 

861 

862 %(Single_Spectrum)s 

863 

864 Returns 

865 ------- 

866 spectrum : 1-D array 

867 The values for the magnitude spectrum (real valued) 

868 

869 freqs : 1-D array 

870 The frequencies corresponding to the elements in *spectrum* 

871 

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') 

887 

888 

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. 

896 

897 Parameters 

898 ---------- 

899 x : 1-D array or sequence 

900 Array or sequence containing the data 

901 

902 %(Spectral)s 

903 

904 %(Single_Spectrum)s 

905 

906 Returns 

907 ------- 

908 spectrum : 1-D array 

909 The values for the angle spectrum in radians (real valued) 

910 

911 freqs : 1-D array 

912 The frequencies corresponding to the elements in *spectrum* 

913 

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') 

927 

928 

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. 

936 

937 Parameters 

938 ---------- 

939 x : 1-D array or sequence 

940 Array or sequence containing the data 

941 

942 %(Spectral)s 

943 

944 %(Single_Spectrum)s 

945 

946 Returns 

947 ------- 

948 spectrum : 1-D array 

949 The values for the phase spectrum in radians (real valued) 

950 

951 freqs : 1-D array 

952 The frequencies corresponding to the elements in *spectrum* 

953 

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') 

967 

968 

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. 

975 

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. 

981 

982 Parameters 

983 ---------- 

984 x : array-like 

985 1-D array or sequence. 

986 

987 %(Spectral)s 

988 

989 %(PSD)s 

990 

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. 

998 

999 'complex' 

1000 Returns the complex-valued frequency spectrum. 

1001 

1002 'magnitude' 

1003 Returns the magnitude spectrum. 

1004 

1005 'angle' 

1006 Returns the phase spectrum without unwrapping. 

1007 

1008 'phase' 

1009 Returns the phase spectrum with unwrapping. 

1010 

1011 Returns 

1012 ------- 

1013 spectrum : array-like 

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

1015 

1016 freqs : array-like 

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

1018 

1019 t : array-like 

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

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

1022 

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'. 

1030 

1031 Notes 

1032 ----- 

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

1034 

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))) 

1044 

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) 

1051 

1052 if mode != 'complex': 

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

1054 

1055 return spec, freqs, t 

1056 

1057 

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: 

1064 

1065 .. math:: 

1066 

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

1068 

1069 Parameters 

1070 ---------- 

1071 x, y 

1072 Array or sequence containing the data 

1073 

1074 %(Spectral)s 

1075 

1076 %(PSD)s 

1077 

1078 noverlap : integer 

1079 The number of points of overlap between blocks. The default value 

1080 is 0 (no overlap). 

1081 

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. 

1088 

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 

1107 

1108 

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. 

1115 

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. 

1122 

1123 

1124 - *fname*: can be a filename or a file handle. Support for gzipped 

1125 files is automatic, if the filename ends in '.gz' 

1126 

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 

1129 

1130 - *skiprows*: is the number of rows from the top to skip 

1131 

1132 - *checkrows*: is the number of rows to check to validate the column 

1133 data type. When set to zero all rows are validated. 

1134 

1135 - *converterd*: if not *None*, is a dictionary mapping column number or 

1136 munged column name to a converter function. 

1137 

1138 - *names*: if not None, is a list of header names. In this case, no 

1139 header will be read from the file 

1140 

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' 

1144 

1145 - *missing*: a string whose value signals a missing field regardless of 

1146 the column it appears in 

1147 

1148 - *use_mrecords*: if True, return an mrecords.fromrecords record array if 

1149 any of the data are missing 

1150 

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. 

1155 

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. 

1160 

1161 If no rows are found, *None* is returned 

1162 """ 

1163 

1164 if converterd is None: 

1165 converterd = dict() 

1166 

1167 if missingd is None: 

1168 missingd = {} 

1169 

1170 import dateutil.parser 

1171 import datetime 

1172 

1173 fh = cbook.to_filehandle(fname) 

1174 

1175 delimiter = str(delimiter) 

1176 

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 

1187 

1188 def close(self): 

1189 self.fh.close() 

1190 

1191 def seek(self, arg): 

1192 self.fh.seek(arg) 

1193 

1194 def fix(self, s): 

1195 return ' '.join(s.split()) 

1196 

1197 def __next__(self): 

1198 return self.fix(next(self.fh)) 

1199 

1200 def __iter__(self): 

1201 for line in self.fh: 

1202 yield self.fix(line) 

1203 

1204 if delimiter == ' ': 

1205 fh = FH(fh) 

1206 

1207 reader = csv.reader(fh, delimiter=delimiter) 

1208 

1209 def process_skiprows(reader): 

1210 if skiprows: 

1211 for i, row in enumerate(reader): 

1212 if i >= (skiprows-1): 

1213 break 

1214 

1215 return fh, reader 

1216 

1217 process_skiprows(reader) 

1218 

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 == '' 

1222 

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 

1230 

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') 

1238 

1239 dateparser = dateutil.parser.parse 

1240 

1241 def mydateparser(x): 

1242 # try and return a datetime object 

1243 d = dateparser(x, dayfirst=dayfirst, yearfirst=yearfirst) 

1244 return d 

1245 

1246 mydateparser = with_default_value(mydateparser, datetime.datetime(1, 1, 1)) 

1247 

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) 

1252 

1253 def mydate(x): 

1254 # try and return a date object 

1255 d = dateparser(x, dayfirst=dayfirst, yearfirst=yearfirst) 

1256 

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)) 

1261 

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') 

1272 

1273 # map column names that clash with builtins -- TODO - extend this list 

1274 itemd = { 

1275 'return': 'return_', 

1276 'file': 'file_', 

1277 'print': 'print_', 

1278 } 

1279 

1280 def get_converters(reader, comments): 

1281 

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 

1293 

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 

1307 

1308 # Get header and remove invalid characters 

1309 needheader = names is None 

1310 

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 

1318 

1319 # remove these chars 

1320 delete = set(r"""~!@#$%^&*()-=+~\|}[]{';: /?.>,<""") 

1321 delete.add('"') 

1322 

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 

1330 

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 

1338 

1339 else: 

1340 if isinstance(names, str): 

1341 names = [n.strip() for n in names.split(',')] 

1342 

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') 

1347 

1348 # reset the reader and start over 

1349 fh.seek(0) 

1350 reader = csv.reader(fh, delimiter=delimiter) 

1351 process_skiprows(reader) 

1352 

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 

1361 

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() 

1378 

1379 if not len(rows): 

1380 return None 

1381 

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 

1387 

1388 

1389class GaussianKDE: 

1390 """ 

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

1392 

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). 

1398 

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. 

1405 

1406 Attributes 

1407 ---------- 

1408 dataset : ndarray 

1409 The dataset with which `gaussian_kde` was initialized. 

1410 

1411 dim : int 

1412 Number of dimensions. 

1413 

1414 num_dp : int 

1415 Number of datapoints. 

1416 

1417 factor : float 

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

1419 the covariance matrix is multiplied. 

1420 

1421 covariance : ndarray 

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

1423 (`kde.factor`). 

1424 

1425 inv_cov : ndarray 

1426 The inverse of *covariance*. 

1427 

1428 Methods 

1429 ------- 

1430 kde.evaluate(points) : ndarray 

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

1432 

1433 kde(points) : ndarray 

1434 Same as kde.evaluate(points) 

1435 

1436 """ 

1437 

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 

1440 

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.") 

1445 

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

1447 

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") 

1463 

1464 # Computes the covariance matrix for each Gaussian kernel using 

1465 # covariance_factor(). 

1466 

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) 

1476 

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) 

1481 

1482 def scotts_factor(self): 

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

1484 

1485 def silverman_factor(self): 

1486 return np.power( 

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

1488 

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

1490 covariance_factor = scotts_factor 

1491 

1492 def evaluate(self, points): 

1493 """Evaluate the estimated pdf on a set of points. 

1494 

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. 

1500 

1501 Returns 

1502 ------- 

1503 values : (# of points,)-array 

1504 The values at each point. 

1505 

1506 Raises 

1507 ------ 

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

1509 than the dimensionality of the KDE. 

1510 

1511 """ 

1512 points = np.atleast_2d(points) 

1513 

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)) 

1518 

1519 result = np.zeros(num_m) 

1520 

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) 

1535 

1536 result = result / self.norm_factor 

1537 

1538 return result 

1539 

1540 __call__ = evaluate