test_mlab.py 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  1. from numpy.testing import (assert_allclose, assert_almost_equal,
  2. assert_array_equal, assert_array_almost_equal_nulp)
  3. import numpy as np
  4. import pytest
  5. from matplotlib import mlab
  6. def test_window():
  7. np.random.seed(0)
  8. n = 1000
  9. rand = np.random.standard_normal(n) + 100
  10. ones = np.ones(n)
  11. assert_array_equal(mlab.window_none(ones), ones)
  12. assert_array_equal(mlab.window_none(rand), rand)
  13. assert_array_equal(np.hanning(len(rand)) * rand, mlab.window_hanning(rand))
  14. assert_array_equal(np.hanning(len(ones)), mlab.window_hanning(ones))
  15. class TestDetrend:
  16. def setup_method(self):
  17. np.random.seed(0)
  18. n = 1000
  19. x = np.linspace(0., 100, n)
  20. self.sig_zeros = np.zeros(n)
  21. self.sig_off = self.sig_zeros + 100.
  22. self.sig_slope = np.linspace(-10., 90., n)
  23. self.sig_slope_mean = x - x.mean()
  24. self.sig_base = (
  25. np.random.standard_normal(n) + np.sin(x*2*np.pi/(n/100)))
  26. self.sig_base -= self.sig_base.mean()
  27. def allclose(self, *args):
  28. assert_allclose(*args, atol=1e-8)
  29. def test_detrend_none(self):
  30. assert mlab.detrend_none(0.) == 0.
  31. assert mlab.detrend_none(0., axis=1) == 0.
  32. assert mlab.detrend(0., key="none") == 0.
  33. assert mlab.detrend(0., key=mlab.detrend_none) == 0.
  34. for sig in [
  35. 5.5, self.sig_off, self.sig_slope, self.sig_base,
  36. (self.sig_base + self.sig_slope + self.sig_off).tolist(),
  37. np.vstack([self.sig_base, # 2D case.
  38. self.sig_base + self.sig_off,
  39. self.sig_base + self.sig_slope,
  40. self.sig_base + self.sig_off + self.sig_slope]),
  41. np.vstack([self.sig_base, # 2D transposed case.
  42. self.sig_base + self.sig_off,
  43. self.sig_base + self.sig_slope,
  44. self.sig_base + self.sig_off + self.sig_slope]).T,
  45. ]:
  46. if isinstance(sig, np.ndarray):
  47. assert_array_equal(mlab.detrend_none(sig), sig)
  48. else:
  49. assert mlab.detrend_none(sig) == sig
  50. def test_detrend_mean(self):
  51. for sig in [0., 5.5]: # 0D.
  52. assert mlab.detrend_mean(sig) == 0.
  53. assert mlab.detrend(sig, key="mean") == 0.
  54. assert mlab.detrend(sig, key=mlab.detrend_mean) == 0.
  55. # 1D.
  56. self.allclose(mlab.detrend_mean(self.sig_zeros), self.sig_zeros)
  57. self.allclose(mlab.detrend_mean(self.sig_base), self.sig_base)
  58. self.allclose(mlab.detrend_mean(self.sig_base + self.sig_off),
  59. self.sig_base)
  60. self.allclose(mlab.detrend_mean(self.sig_base + self.sig_slope),
  61. self.sig_base + self.sig_slope_mean)
  62. self.allclose(
  63. mlab.detrend_mean(self.sig_base + self.sig_slope + self.sig_off),
  64. self.sig_base + self.sig_slope_mean)
  65. def test_detrend_mean_1d_base_slope_off_list_andor_axis0(self):
  66. input = self.sig_base + self.sig_slope + self.sig_off
  67. target = self.sig_base + self.sig_slope_mean
  68. self.allclose(mlab.detrend_mean(input, axis=0), target)
  69. self.allclose(mlab.detrend_mean(input.tolist()), target)
  70. self.allclose(mlab.detrend_mean(input.tolist(), axis=0), target)
  71. def test_detrend_mean_2d(self):
  72. input = np.vstack([self.sig_off,
  73. self.sig_base + self.sig_off])
  74. target = np.vstack([self.sig_zeros,
  75. self.sig_base])
  76. self.allclose(mlab.detrend_mean(input), target)
  77. self.allclose(mlab.detrend_mean(input, axis=None), target)
  78. self.allclose(mlab.detrend_mean(input.T, axis=None).T, target)
  79. self.allclose(mlab.detrend(input), target)
  80. self.allclose(mlab.detrend(input, axis=None), target)
  81. self.allclose(
  82. mlab.detrend(input.T, key="constant", axis=None), target.T)
  83. input = np.vstack([self.sig_base,
  84. self.sig_base + self.sig_off,
  85. self.sig_base + self.sig_slope,
  86. self.sig_base + self.sig_off + self.sig_slope])
  87. target = np.vstack([self.sig_base,
  88. self.sig_base,
  89. self.sig_base + self.sig_slope_mean,
  90. self.sig_base + self.sig_slope_mean])
  91. self.allclose(mlab.detrend_mean(input.T, axis=0), target.T)
  92. self.allclose(mlab.detrend_mean(input, axis=1), target)
  93. self.allclose(mlab.detrend_mean(input, axis=-1), target)
  94. self.allclose(mlab.detrend(input, key="default", axis=1), target)
  95. self.allclose(mlab.detrend(input.T, key="mean", axis=0), target.T)
  96. self.allclose(
  97. mlab.detrend(input.T, key=mlab.detrend_mean, axis=0), target.T)
  98. def test_detrend_ValueError(self):
  99. for signal, kwargs in [
  100. (self.sig_slope[np.newaxis], {"key": "spam"}),
  101. (self.sig_slope[np.newaxis], {"key": 5}),
  102. (5.5, {"axis": 0}),
  103. (self.sig_slope, {"axis": 1}),
  104. (self.sig_slope[np.newaxis], {"axis": 2}),
  105. ]:
  106. with pytest.raises(ValueError):
  107. mlab.detrend(signal, **kwargs)
  108. def test_detrend_mean_ValueError(self):
  109. for signal, kwargs in [
  110. (5.5, {"axis": 0}),
  111. (self.sig_slope, {"axis": 1}),
  112. (self.sig_slope[np.newaxis], {"axis": 2}),
  113. ]:
  114. with pytest.raises(ValueError):
  115. mlab.detrend_mean(signal, **kwargs)
  116. def test_detrend_linear(self):
  117. # 0D.
  118. assert mlab.detrend_linear(0.) == 0.
  119. assert mlab.detrend_linear(5.5) == 0.
  120. assert mlab.detrend(5.5, key="linear") == 0.
  121. assert mlab.detrend(5.5, key=mlab.detrend_linear) == 0.
  122. for sig in [ # 1D.
  123. self.sig_off,
  124. self.sig_slope,
  125. self.sig_slope + self.sig_off,
  126. ]:
  127. self.allclose(mlab.detrend_linear(sig), self.sig_zeros)
  128. def test_detrend_str_linear_1d(self):
  129. input = self.sig_slope + self.sig_off
  130. target = self.sig_zeros
  131. self.allclose(mlab.detrend(input, key="linear"), target)
  132. self.allclose(mlab.detrend(input, key=mlab.detrend_linear), target)
  133. self.allclose(mlab.detrend_linear(input.tolist()), target)
  134. def test_detrend_linear_2d(self):
  135. input = np.vstack([self.sig_off,
  136. self.sig_slope,
  137. self.sig_slope + self.sig_off])
  138. target = np.vstack([self.sig_zeros,
  139. self.sig_zeros,
  140. self.sig_zeros])
  141. self.allclose(
  142. mlab.detrend(input.T, key="linear", axis=0), target.T)
  143. self.allclose(
  144. mlab.detrend(input.T, key=mlab.detrend_linear, axis=0), target.T)
  145. self.allclose(
  146. mlab.detrend(input, key="linear", axis=1), target)
  147. self.allclose(
  148. mlab.detrend(input, key=mlab.detrend_linear, axis=1), target)
  149. with pytest.raises(ValueError):
  150. mlab.detrend_linear(self.sig_slope[np.newaxis])
  151. @pytest.mark.parametrize('iscomplex', [False, True],
  152. ids=['real', 'complex'], scope='class')
  153. @pytest.mark.parametrize('sides', ['onesided', 'twosided', 'default'],
  154. scope='class')
  155. @pytest.mark.parametrize(
  156. 'fstims,len_x,NFFT_density,nover_density,pad_to_density,pad_to_spectrum',
  157. [
  158. ([], None, -1, -1, -1, -1),
  159. ([4], None, -1, -1, -1, -1),
  160. ([4, 5, 10], None, -1, -1, -1, -1),
  161. ([], None, None, -1, -1, None),
  162. ([], None, -1, -1, None, None),
  163. ([], None, None, -1, None, None),
  164. ([], 1024, 512, -1, -1, 128),
  165. ([], 256, -1, -1, 33, 257),
  166. ([], 255, 33, -1, -1, None),
  167. ([], 256, 128, -1, 256, 256),
  168. ([], None, -1, 32, -1, -1),
  169. ],
  170. ids=[
  171. 'nosig',
  172. 'Fs4',
  173. 'FsAll',
  174. 'nosig_noNFFT',
  175. 'nosig_nopad_to',
  176. 'nosig_noNFFT_no_pad_to',
  177. 'nosig_trim',
  178. 'nosig_odd',
  179. 'nosig_oddlen',
  180. 'nosig_stretch',
  181. 'nosig_overlap',
  182. ],
  183. scope='class')
  184. class TestSpectral:
  185. @pytest.fixture(scope='class', autouse=True)
  186. def stim(self, request, fstims, iscomplex, sides, len_x, NFFT_density,
  187. nover_density, pad_to_density, pad_to_spectrum):
  188. Fs = 100.
  189. x = np.arange(0, 10, 1 / Fs)
  190. if len_x is not None:
  191. x = x[:len_x]
  192. # get the stimulus frequencies, defaulting to None
  193. fstims = [Fs / fstim for fstim in fstims]
  194. # get the constants, default to calculated values
  195. if NFFT_density is None:
  196. NFFT_density_real = 256
  197. elif NFFT_density < 0:
  198. NFFT_density_real = NFFT_density = 100
  199. else:
  200. NFFT_density_real = NFFT_density
  201. if nover_density is None:
  202. nover_density_real = 0
  203. elif nover_density < 0:
  204. nover_density_real = nover_density = NFFT_density_real // 2
  205. else:
  206. nover_density_real = nover_density
  207. if pad_to_density is None:
  208. pad_to_density_real = NFFT_density_real
  209. elif pad_to_density < 0:
  210. pad_to_density = int(2**np.ceil(np.log2(NFFT_density_real)))
  211. pad_to_density_real = pad_to_density
  212. else:
  213. pad_to_density_real = pad_to_density
  214. if pad_to_spectrum is None:
  215. pad_to_spectrum_real = len(x)
  216. elif pad_to_spectrum < 0:
  217. pad_to_spectrum_real = pad_to_spectrum = len(x)
  218. else:
  219. pad_to_spectrum_real = pad_to_spectrum
  220. if pad_to_spectrum is None:
  221. NFFT_spectrum_real = NFFT_spectrum = pad_to_spectrum_real
  222. else:
  223. NFFT_spectrum_real = NFFT_spectrum = len(x)
  224. nover_spectrum = 0
  225. NFFT_specgram = NFFT_density
  226. nover_specgram = nover_density
  227. pad_to_specgram = pad_to_density
  228. NFFT_specgram_real = NFFT_density_real
  229. nover_specgram_real = nover_density_real
  230. if sides == 'onesided' or (sides == 'default' and not iscomplex):
  231. # frequencies for specgram, psd, and csd
  232. # need to handle even and odd differently
  233. if pad_to_density_real % 2:
  234. freqs_density = np.linspace(0, Fs / 2,
  235. num=pad_to_density_real,
  236. endpoint=False)[::2]
  237. else:
  238. freqs_density = np.linspace(0, Fs / 2,
  239. num=pad_to_density_real // 2 + 1)
  240. # frequencies for complex, magnitude, angle, and phase spectrums
  241. # need to handle even and odd differently
  242. if pad_to_spectrum_real % 2:
  243. freqs_spectrum = np.linspace(0, Fs / 2,
  244. num=pad_to_spectrum_real,
  245. endpoint=False)[::2]
  246. else:
  247. freqs_spectrum = np.linspace(0, Fs / 2,
  248. num=pad_to_spectrum_real // 2 + 1)
  249. else:
  250. # frequencies for specgram, psd, and csd
  251. # need to handle even and odd differently
  252. if pad_to_density_real % 2:
  253. freqs_density = np.linspace(-Fs / 2, Fs / 2,
  254. num=2 * pad_to_density_real,
  255. endpoint=False)[1::2]
  256. else:
  257. freqs_density = np.linspace(-Fs / 2, Fs / 2,
  258. num=pad_to_density_real,
  259. endpoint=False)
  260. # frequencies for complex, magnitude, angle, and phase spectrums
  261. # need to handle even and odd differently
  262. if pad_to_spectrum_real % 2:
  263. freqs_spectrum = np.linspace(-Fs / 2, Fs / 2,
  264. num=2 * pad_to_spectrum_real,
  265. endpoint=False)[1::2]
  266. else:
  267. freqs_spectrum = np.linspace(-Fs / 2, Fs / 2,
  268. num=pad_to_spectrum_real,
  269. endpoint=False)
  270. freqs_specgram = freqs_density
  271. # time points for specgram
  272. t_start = NFFT_specgram_real // 2
  273. t_stop = len(x) - NFFT_specgram_real // 2 + 1
  274. t_step = NFFT_specgram_real - nover_specgram_real
  275. t_specgram = x[t_start:t_stop:t_step]
  276. if NFFT_specgram_real % 2:
  277. t_specgram += 1 / Fs / 2
  278. if len(t_specgram) == 0:
  279. t_specgram = np.array([NFFT_specgram_real / (2 * Fs)])
  280. t_spectrum = np.array([NFFT_spectrum_real / (2 * Fs)])
  281. t_density = t_specgram
  282. y = np.zeros_like(x)
  283. for i, fstim in enumerate(fstims):
  284. y += np.sin(fstim * x * np.pi * 2) * 10**i
  285. if iscomplex:
  286. y = y.astype('complex')
  287. # Interestingly, the instance on which this fixture is called is not
  288. # the same as the one on which a test is run. So we need to modify the
  289. # class itself when using a class-scoped fixture.
  290. cls = request.cls
  291. cls.Fs = Fs
  292. cls.sides = sides
  293. cls.fstims = fstims
  294. cls.NFFT_density = NFFT_density
  295. cls.nover_density = nover_density
  296. cls.pad_to_density = pad_to_density
  297. cls.NFFT_spectrum = NFFT_spectrum
  298. cls.nover_spectrum = nover_spectrum
  299. cls.pad_to_spectrum = pad_to_spectrum
  300. cls.NFFT_specgram = NFFT_specgram
  301. cls.nover_specgram = nover_specgram
  302. cls.pad_to_specgram = pad_to_specgram
  303. cls.t_specgram = t_specgram
  304. cls.t_density = t_density
  305. cls.t_spectrum = t_spectrum
  306. cls.y = y
  307. cls.freqs_density = freqs_density
  308. cls.freqs_spectrum = freqs_spectrum
  309. cls.freqs_specgram = freqs_specgram
  310. cls.NFFT_density_real = NFFT_density_real
  311. def check_freqs(self, vals, targfreqs, resfreqs, fstims):
  312. assert resfreqs.argmin() == 0
  313. assert resfreqs.argmax() == len(resfreqs)-1
  314. assert_allclose(resfreqs, targfreqs, atol=1e-06)
  315. for fstim in fstims:
  316. i = np.abs(resfreqs - fstim).argmin()
  317. assert vals[i] > vals[i+2]
  318. assert vals[i] > vals[i-2]
  319. def check_maxfreq(self, spec, fsp, fstims):
  320. # skip the test if there are no frequencies
  321. if len(fstims) == 0:
  322. return
  323. # if twosided, do the test for each side
  324. if fsp.min() < 0:
  325. fspa = np.abs(fsp)
  326. zeroind = fspa.argmin()
  327. self.check_maxfreq(spec[:zeroind], fspa[:zeroind], fstims)
  328. self.check_maxfreq(spec[zeroind:], fspa[zeroind:], fstims)
  329. return
  330. fstimst = fstims[:]
  331. spect = spec.copy()
  332. # go through each peak and make sure it is correctly the maximum peak
  333. while fstimst:
  334. maxind = spect.argmax()
  335. maxfreq = fsp[maxind]
  336. assert_almost_equal(maxfreq, fstimst[-1])
  337. del fstimst[-1]
  338. spect[maxind-5:maxind+5] = 0
  339. def test_spectral_helper_raises(self):
  340. # We don't use parametrize here to handle ``y = self.y``.
  341. for kwargs in [ # Various error conditions:
  342. {"y": self.y+1, "mode": "complex"}, # Modes requiring ``x is y``.
  343. {"y": self.y+1, "mode": "magnitude"},
  344. {"y": self.y+1, "mode": "angle"},
  345. {"y": self.y+1, "mode": "phase"},
  346. {"mode": "spam"}, # Bad mode.
  347. {"y": self.y, "sides": "eggs"}, # Bad sides.
  348. {"y": self.y, "NFFT": 10, "noverlap": 20}, # noverlap > NFFT.
  349. {"NFFT": 10, "noverlap": 10}, # noverlap == NFFT.
  350. {"y": self.y, "NFFT": 10,
  351. "window": np.ones(9)}, # len(win) != NFFT.
  352. ]:
  353. with pytest.raises(ValueError):
  354. mlab._spectral_helper(x=self.y, **kwargs)
  355. @pytest.mark.parametrize('mode', ['default', 'psd'])
  356. def test_single_spectrum_helper_unsupported_modes(self, mode):
  357. with pytest.raises(ValueError):
  358. mlab._single_spectrum_helper(x=self.y, mode=mode)
  359. @pytest.mark.parametrize("mode, case", [
  360. ("psd", "density"),
  361. ("magnitude", "specgram"),
  362. ("magnitude", "spectrum"),
  363. ])
  364. def test_spectral_helper_psd(self, mode, case):
  365. freqs = getattr(self, f"freqs_{case}")
  366. spec, fsp, t = mlab._spectral_helper(
  367. x=self.y, y=self.y,
  368. NFFT=getattr(self, f"NFFT_{case}"),
  369. Fs=self.Fs,
  370. noverlap=getattr(self, f"nover_{case}"),
  371. pad_to=getattr(self, f"pad_to_{case}"),
  372. sides=self.sides,
  373. mode=mode)
  374. assert_allclose(fsp, freqs, atol=1e-06)
  375. assert_allclose(t, getattr(self, f"t_{case}"), atol=1e-06)
  376. assert spec.shape[0] == freqs.shape[0]
  377. assert spec.shape[1] == getattr(self, f"t_{case}").shape[0]
  378. def test_csd(self):
  379. freqs = self.freqs_density
  380. spec, fsp = mlab.csd(x=self.y, y=self.y+1,
  381. NFFT=self.NFFT_density,
  382. Fs=self.Fs,
  383. noverlap=self.nover_density,
  384. pad_to=self.pad_to_density,
  385. sides=self.sides)
  386. assert_allclose(fsp, freqs, atol=1e-06)
  387. assert spec.shape == freqs.shape
  388. def test_csd_padding(self):
  389. """Test zero padding of csd()."""
  390. if self.NFFT_density is None: # for derived classes
  391. return
  392. sargs = dict(x=self.y, y=self.y+1, Fs=self.Fs, window=mlab.window_none,
  393. sides=self.sides)
  394. spec0, _ = mlab.csd(NFFT=self.NFFT_density, **sargs)
  395. spec1, _ = mlab.csd(NFFT=self.NFFT_density*2, **sargs)
  396. assert_almost_equal(np.sum(np.conjugate(spec0)*spec0).real,
  397. np.sum(np.conjugate(spec1/2)*spec1/2).real)
  398. def test_psd(self):
  399. freqs = self.freqs_density
  400. spec, fsp = mlab.psd(x=self.y,
  401. NFFT=self.NFFT_density,
  402. Fs=self.Fs,
  403. noverlap=self.nover_density,
  404. pad_to=self.pad_to_density,
  405. sides=self.sides)
  406. assert spec.shape == freqs.shape
  407. self.check_freqs(spec, freqs, fsp, self.fstims)
  408. @pytest.mark.parametrize(
  409. 'make_data, detrend',
  410. [(np.zeros, mlab.detrend_mean), (np.zeros, 'mean'),
  411. (np.arange, mlab.detrend_linear), (np.arange, 'linear')])
  412. def test_psd_detrend(self, make_data, detrend):
  413. if self.NFFT_density is None:
  414. return
  415. ydata = make_data(self.NFFT_density)
  416. ydata1 = ydata+5
  417. ydata2 = ydata+3.3
  418. ydata = np.vstack([ydata1, ydata2])
  419. ydata = np.tile(ydata, (20, 1))
  420. ydatab = ydata.T.flatten()
  421. ydata = ydata.flatten()
  422. ycontrol = np.zeros_like(ydata)
  423. spec_g, fsp_g = mlab.psd(x=ydata,
  424. NFFT=self.NFFT_density,
  425. Fs=self.Fs,
  426. noverlap=0,
  427. sides=self.sides,
  428. detrend=detrend)
  429. spec_b, fsp_b = mlab.psd(x=ydatab,
  430. NFFT=self.NFFT_density,
  431. Fs=self.Fs,
  432. noverlap=0,
  433. sides=self.sides,
  434. detrend=detrend)
  435. spec_c, fsp_c = mlab.psd(x=ycontrol,
  436. NFFT=self.NFFT_density,
  437. Fs=self.Fs,
  438. noverlap=0,
  439. sides=self.sides)
  440. assert_array_equal(fsp_g, fsp_c)
  441. assert_array_equal(fsp_b, fsp_c)
  442. assert_allclose(spec_g, spec_c, atol=1e-08)
  443. # these should not be almost equal
  444. with pytest.raises(AssertionError):
  445. assert_allclose(spec_b, spec_c, atol=1e-08)
  446. def test_psd_window_hanning(self):
  447. if self.NFFT_density is None:
  448. return
  449. ydata = np.arange(self.NFFT_density)
  450. ydata1 = ydata+5
  451. ydata2 = ydata+3.3
  452. windowVals = mlab.window_hanning(np.ones_like(ydata1))
  453. ycontrol1 = ydata1 * windowVals
  454. ycontrol2 = mlab.window_hanning(ydata2)
  455. ydata = np.vstack([ydata1, ydata2])
  456. ycontrol = np.vstack([ycontrol1, ycontrol2])
  457. ydata = np.tile(ydata, (20, 1))
  458. ycontrol = np.tile(ycontrol, (20, 1))
  459. ydatab = ydata.T.flatten()
  460. ydataf = ydata.flatten()
  461. ycontrol = ycontrol.flatten()
  462. spec_g, fsp_g = mlab.psd(x=ydataf,
  463. NFFT=self.NFFT_density,
  464. Fs=self.Fs,
  465. noverlap=0,
  466. sides=self.sides,
  467. window=mlab.window_hanning)
  468. spec_b, fsp_b = mlab.psd(x=ydatab,
  469. NFFT=self.NFFT_density,
  470. Fs=self.Fs,
  471. noverlap=0,
  472. sides=self.sides,
  473. window=mlab.window_hanning)
  474. spec_c, fsp_c = mlab.psd(x=ycontrol,
  475. NFFT=self.NFFT_density,
  476. Fs=self.Fs,
  477. noverlap=0,
  478. sides=self.sides,
  479. window=mlab.window_none)
  480. spec_c *= len(ycontrol1)/(windowVals**2).sum()
  481. assert_array_equal(fsp_g, fsp_c)
  482. assert_array_equal(fsp_b, fsp_c)
  483. assert_allclose(spec_g, spec_c, atol=1e-08)
  484. # these should not be almost equal
  485. with pytest.raises(AssertionError):
  486. assert_allclose(spec_b, spec_c, atol=1e-08)
  487. def test_psd_window_hanning_detrend_linear(self):
  488. if self.NFFT_density is None:
  489. return
  490. ydata = np.arange(self.NFFT_density)
  491. ycontrol = np.zeros(self.NFFT_density)
  492. ydata1 = ydata+5
  493. ydata2 = ydata+3.3
  494. ycontrol1 = ycontrol
  495. ycontrol2 = ycontrol
  496. windowVals = mlab.window_hanning(np.ones_like(ycontrol1))
  497. ycontrol1 = ycontrol1 * windowVals
  498. ycontrol2 = mlab.window_hanning(ycontrol2)
  499. ydata = np.vstack([ydata1, ydata2])
  500. ycontrol = np.vstack([ycontrol1, ycontrol2])
  501. ydata = np.tile(ydata, (20, 1))
  502. ycontrol = np.tile(ycontrol, (20, 1))
  503. ydatab = ydata.T.flatten()
  504. ydataf = ydata.flatten()
  505. ycontrol = ycontrol.flatten()
  506. spec_g, fsp_g = mlab.psd(x=ydataf,
  507. NFFT=self.NFFT_density,
  508. Fs=self.Fs,
  509. noverlap=0,
  510. sides=self.sides,
  511. detrend=mlab.detrend_linear,
  512. window=mlab.window_hanning)
  513. spec_b, fsp_b = mlab.psd(x=ydatab,
  514. NFFT=self.NFFT_density,
  515. Fs=self.Fs,
  516. noverlap=0,
  517. sides=self.sides,
  518. detrend=mlab.detrend_linear,
  519. window=mlab.window_hanning)
  520. spec_c, fsp_c = mlab.psd(x=ycontrol,
  521. NFFT=self.NFFT_density,
  522. Fs=self.Fs,
  523. noverlap=0,
  524. sides=self.sides,
  525. window=mlab.window_none)
  526. spec_c *= len(ycontrol1)/(windowVals**2).sum()
  527. assert_array_equal(fsp_g, fsp_c)
  528. assert_array_equal(fsp_b, fsp_c)
  529. assert_allclose(spec_g, spec_c, atol=1e-08)
  530. # these should not be almost equal
  531. with pytest.raises(AssertionError):
  532. assert_allclose(spec_b, spec_c, atol=1e-08)
  533. def test_psd_window_flattop(self):
  534. # flattop window
  535. # adaption from https://github.com/scipy/scipy/blob\
  536. # /v1.10.0/scipy/signal/windows/_windows.py#L562-L622
  537. a = [0.21557895, 0.41663158, 0.277263158, 0.083578947, 0.006947368]
  538. fac = np.linspace(-np.pi, np.pi, self.NFFT_density_real)
  539. win = np.zeros(self.NFFT_density_real)
  540. for k in range(len(a)):
  541. win += a[k] * np.cos(k * fac)
  542. spec, fsp = mlab.psd(x=self.y,
  543. NFFT=self.NFFT_density,
  544. Fs=self.Fs,
  545. noverlap=0,
  546. sides=self.sides,
  547. window=win,
  548. scale_by_freq=False)
  549. spec_a, fsp_a = mlab.psd(x=self.y,
  550. NFFT=self.NFFT_density,
  551. Fs=self.Fs,
  552. noverlap=0,
  553. sides=self.sides,
  554. window=win)
  555. assert_allclose(spec*win.sum()**2,
  556. spec_a*self.Fs*(win**2).sum(),
  557. atol=1e-08)
  558. def test_psd_windowarray(self):
  559. freqs = self.freqs_density
  560. spec, fsp = mlab.psd(x=self.y,
  561. NFFT=self.NFFT_density,
  562. Fs=self.Fs,
  563. noverlap=self.nover_density,
  564. pad_to=self.pad_to_density,
  565. sides=self.sides,
  566. window=np.ones(self.NFFT_density_real))
  567. assert_allclose(fsp, freqs, atol=1e-06)
  568. assert spec.shape == freqs.shape
  569. def test_psd_windowarray_scale_by_freq(self):
  570. win = mlab.window_hanning(np.ones(self.NFFT_density_real))
  571. spec, fsp = mlab.psd(x=self.y,
  572. NFFT=self.NFFT_density,
  573. Fs=self.Fs,
  574. noverlap=self.nover_density,
  575. pad_to=self.pad_to_density,
  576. sides=self.sides,
  577. window=mlab.window_hanning)
  578. spec_s, fsp_s = mlab.psd(x=self.y,
  579. NFFT=self.NFFT_density,
  580. Fs=self.Fs,
  581. noverlap=self.nover_density,
  582. pad_to=self.pad_to_density,
  583. sides=self.sides,
  584. window=mlab.window_hanning,
  585. scale_by_freq=True)
  586. spec_n, fsp_n = mlab.psd(x=self.y,
  587. NFFT=self.NFFT_density,
  588. Fs=self.Fs,
  589. noverlap=self.nover_density,
  590. pad_to=self.pad_to_density,
  591. sides=self.sides,
  592. window=mlab.window_hanning,
  593. scale_by_freq=False)
  594. assert_array_equal(fsp, fsp_s)
  595. assert_array_equal(fsp, fsp_n)
  596. assert_array_equal(spec, spec_s)
  597. assert_allclose(spec_s*(win**2).sum(),
  598. spec_n/self.Fs*win.sum()**2,
  599. atol=1e-08)
  600. @pytest.mark.parametrize(
  601. "kind", ["complex", "magnitude", "angle", "phase"])
  602. def test_spectrum(self, kind):
  603. freqs = self.freqs_spectrum
  604. spec, fsp = getattr(mlab, f"{kind}_spectrum")(
  605. x=self.y,
  606. Fs=self.Fs, sides=self.sides, pad_to=self.pad_to_spectrum)
  607. assert_allclose(fsp, freqs, atol=1e-06)
  608. assert spec.shape == freqs.shape
  609. if kind == "magnitude":
  610. self.check_maxfreq(spec, fsp, self.fstims)
  611. self.check_freqs(spec, freqs, fsp, self.fstims)
  612. @pytest.mark.parametrize(
  613. 'kwargs',
  614. [{}, {'mode': 'default'}, {'mode': 'psd'}, {'mode': 'magnitude'},
  615. {'mode': 'complex'}, {'mode': 'angle'}, {'mode': 'phase'}])
  616. def test_specgram(self, kwargs):
  617. freqs = self.freqs_specgram
  618. spec, fsp, t = mlab.specgram(x=self.y,
  619. NFFT=self.NFFT_specgram,
  620. Fs=self.Fs,
  621. noverlap=self.nover_specgram,
  622. pad_to=self.pad_to_specgram,
  623. sides=self.sides,
  624. **kwargs)
  625. if kwargs.get('mode') == 'complex':
  626. spec = np.abs(spec)
  627. specm = np.mean(spec, axis=1)
  628. assert_allclose(fsp, freqs, atol=1e-06)
  629. assert_allclose(t, self.t_specgram, atol=1e-06)
  630. assert spec.shape[0] == freqs.shape[0]
  631. assert spec.shape[1] == self.t_specgram.shape[0]
  632. if kwargs.get('mode') not in ['complex', 'angle', 'phase']:
  633. # using a single freq, so all time slices should be about the same
  634. if np.abs(spec.max()) != 0:
  635. assert_allclose(
  636. np.diff(spec, axis=1).max() / np.abs(spec.max()), 0,
  637. atol=1e-02)
  638. if kwargs.get('mode') not in ['angle', 'phase']:
  639. self.check_freqs(specm, freqs, fsp, self.fstims)
  640. def test_specgram_warn_only1seg(self):
  641. """Warning should be raised if len(x) <= NFFT."""
  642. with pytest.warns(UserWarning, match="Only one segment is calculated"):
  643. mlab.specgram(x=self.y, NFFT=len(self.y), Fs=self.Fs)
  644. def test_psd_csd_equal(self):
  645. Pxx, freqsxx = mlab.psd(x=self.y,
  646. NFFT=self.NFFT_density,
  647. Fs=self.Fs,
  648. noverlap=self.nover_density,
  649. pad_to=self.pad_to_density,
  650. sides=self.sides)
  651. Pxy, freqsxy = mlab.csd(x=self.y, y=self.y,
  652. NFFT=self.NFFT_density,
  653. Fs=self.Fs,
  654. noverlap=self.nover_density,
  655. pad_to=self.pad_to_density,
  656. sides=self.sides)
  657. assert_array_almost_equal_nulp(Pxx, Pxy)
  658. assert_array_equal(freqsxx, freqsxy)
  659. @pytest.mark.parametrize("mode", ["default", "psd"])
  660. def test_specgram_auto_default_psd_equal(self, mode):
  661. """
  662. Test that mlab.specgram without mode and with mode 'default' and 'psd'
  663. are all the same.
  664. """
  665. speca, freqspeca, ta = mlab.specgram(x=self.y,
  666. NFFT=self.NFFT_specgram,
  667. Fs=self.Fs,
  668. noverlap=self.nover_specgram,
  669. pad_to=self.pad_to_specgram,
  670. sides=self.sides)
  671. specb, freqspecb, tb = mlab.specgram(x=self.y,
  672. NFFT=self.NFFT_specgram,
  673. Fs=self.Fs,
  674. noverlap=self.nover_specgram,
  675. pad_to=self.pad_to_specgram,
  676. sides=self.sides,
  677. mode=mode)
  678. assert_array_equal(speca, specb)
  679. assert_array_equal(freqspeca, freqspecb)
  680. assert_array_equal(ta, tb)
  681. @pytest.mark.parametrize(
  682. "mode, conv", [
  683. ("magnitude", np.abs),
  684. ("angle", np.angle),
  685. ("phase", lambda x: np.unwrap(np.angle(x), axis=0))
  686. ])
  687. def test_specgram_complex_equivalent(self, mode, conv):
  688. specc, freqspecc, tc = mlab.specgram(x=self.y,
  689. NFFT=self.NFFT_specgram,
  690. Fs=self.Fs,
  691. noverlap=self.nover_specgram,
  692. pad_to=self.pad_to_specgram,
  693. sides=self.sides,
  694. mode='complex')
  695. specm, freqspecm, tm = mlab.specgram(x=self.y,
  696. NFFT=self.NFFT_specgram,
  697. Fs=self.Fs,
  698. noverlap=self.nover_specgram,
  699. pad_to=self.pad_to_specgram,
  700. sides=self.sides,
  701. mode=mode)
  702. assert_array_equal(freqspecc, freqspecm)
  703. assert_array_equal(tc, tm)
  704. assert_allclose(conv(specc), specm, atol=1e-06)
  705. def test_psd_windowarray_equal(self):
  706. win = mlab.window_hanning(np.ones(self.NFFT_density_real))
  707. speca, fspa = mlab.psd(x=self.y,
  708. NFFT=self.NFFT_density,
  709. Fs=self.Fs,
  710. noverlap=self.nover_density,
  711. pad_to=self.pad_to_density,
  712. sides=self.sides,
  713. window=win)
  714. specb, fspb = mlab.psd(x=self.y,
  715. NFFT=self.NFFT_density,
  716. Fs=self.Fs,
  717. noverlap=self.nover_density,
  718. pad_to=self.pad_to_density,
  719. sides=self.sides)
  720. assert_array_equal(fspa, fspb)
  721. assert_allclose(speca, specb, atol=1e-08)
  722. # extra test for cohere...
  723. def test_cohere():
  724. N = 1024
  725. np.random.seed(19680801)
  726. x = np.random.randn(N)
  727. # phase offset
  728. y = np.roll(x, 20)
  729. # high-freq roll-off
  730. y = np.convolve(y, np.ones(20) / 20., mode='same')
  731. cohsq, f = mlab.cohere(x, y, NFFT=256, Fs=2, noverlap=128)
  732. assert_allclose(np.mean(cohsq), 0.837, atol=1.e-3)
  733. assert np.isreal(np.mean(cohsq))
  734. # *****************************************************************
  735. # These Tests were taken from SCIPY with some minor modifications
  736. # this can be retrieved from:
  737. # https://github.com/scipy/scipy/blob/master/scipy/stats/tests/test_kdeoth.py
  738. # *****************************************************************
  739. class TestGaussianKDE:
  740. def test_kde_integer_input(self):
  741. """Regression test for #1181."""
  742. x1 = np.arange(5)
  743. kde = mlab.GaussianKDE(x1)
  744. y_expected = [0.13480721, 0.18222869, 0.19514935, 0.18222869,
  745. 0.13480721]
  746. np.testing.assert_array_almost_equal(kde(x1), y_expected, decimal=6)
  747. def test_gaussian_kde_covariance_caching(self):
  748. x1 = np.array([-7, -5, 1, 4, 5], dtype=float)
  749. xs = np.linspace(-10, 10, num=5)
  750. # These expected values are from scipy 0.10, before some changes to
  751. # gaussian_kde. They were not compared with any external reference.
  752. y_expected = [0.02463386, 0.04689208, 0.05395444, 0.05337754,
  753. 0.01664475]
  754. # set it to the default bandwidth.
  755. kde2 = mlab.GaussianKDE(x1, 'scott')
  756. y2 = kde2(xs)
  757. np.testing.assert_array_almost_equal(y_expected, y2, decimal=7)
  758. def test_kde_bandwidth_method(self):
  759. np.random.seed(8765678)
  760. n_basesample = 50
  761. xn = np.random.randn(n_basesample)
  762. # Default
  763. gkde = mlab.GaussianKDE(xn)
  764. # Supply a callable
  765. gkde2 = mlab.GaussianKDE(xn, 'scott')
  766. # Supply a scalar
  767. gkde3 = mlab.GaussianKDE(xn, bw_method=gkde.factor)
  768. xs = np.linspace(-7, 7, 51)
  769. kdepdf = gkde.evaluate(xs)
  770. kdepdf2 = gkde2.evaluate(xs)
  771. assert kdepdf.all() == kdepdf2.all()
  772. kdepdf3 = gkde3.evaluate(xs)
  773. assert kdepdf.all() == kdepdf3.all()
  774. class TestGaussianKDECustom:
  775. def test_no_data(self):
  776. """Pass no data into the GaussianKDE class."""
  777. with pytest.raises(ValueError):
  778. mlab.GaussianKDE([])
  779. def test_single_dataset_element(self):
  780. """Pass a single dataset element into the GaussianKDE class."""
  781. with pytest.raises(ValueError):
  782. mlab.GaussianKDE([42])
  783. def test_silverman_multidim_dataset(self):
  784. """Test silverman's for a multi-dimensional array."""
  785. x1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
  786. with pytest.raises(np.linalg.LinAlgError):
  787. mlab.GaussianKDE(x1, "silverman")
  788. def test_silverman_singledim_dataset(self):
  789. """Test silverman's output for a single dimension list."""
  790. x1 = np.array([-7, -5, 1, 4, 5])
  791. mygauss = mlab.GaussianKDE(x1, "silverman")
  792. y_expected = 0.76770389927475502
  793. assert_almost_equal(mygauss.covariance_factor(), y_expected, 7)
  794. def test_scott_multidim_dataset(self):
  795. """Test scott's output for a multi-dimensional array."""
  796. x1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
  797. with pytest.raises(np.linalg.LinAlgError):
  798. mlab.GaussianKDE(x1, "scott")
  799. def test_scott_singledim_dataset(self):
  800. """Test scott's output a single-dimensional array."""
  801. x1 = np.array([-7, -5, 1, 4, 5])
  802. mygauss = mlab.GaussianKDE(x1, "scott")
  803. y_expected = 0.72477966367769553
  804. assert_almost_equal(mygauss.covariance_factor(), y_expected, 7)
  805. def test_scalar_empty_dataset(self):
  806. """Test the scalar's cov factor for an empty array."""
  807. with pytest.raises(ValueError):
  808. mlab.GaussianKDE([], bw_method=5)
  809. def test_scalar_covariance_dataset(self):
  810. """Test a scalar's cov factor."""
  811. np.random.seed(8765678)
  812. n_basesample = 50
  813. multidim_data = [np.random.randn(n_basesample) for i in range(5)]
  814. kde = mlab.GaussianKDE(multidim_data, bw_method=0.5)
  815. assert kde.covariance_factor() == 0.5
  816. def test_callable_covariance_dataset(self):
  817. """Test the callable's cov factor for a multi-dimensional array."""
  818. np.random.seed(8765678)
  819. n_basesample = 50
  820. multidim_data = [np.random.randn(n_basesample) for i in range(5)]
  821. def callable_fun(x):
  822. return 0.55
  823. kde = mlab.GaussianKDE(multidim_data, bw_method=callable_fun)
  824. assert kde.covariance_factor() == 0.55
  825. def test_callable_singledim_dataset(self):
  826. """Test the callable's cov factor for a single-dimensional array."""
  827. np.random.seed(8765678)
  828. n_basesample = 50
  829. multidim_data = np.random.randn(n_basesample)
  830. kde = mlab.GaussianKDE(multidim_data, bw_method='silverman')
  831. y_expected = 0.48438841363348911
  832. assert_almost_equal(kde.covariance_factor(), y_expected, 7)
  833. def test_wrong_bw_method(self):
  834. """Test the error message that should be called when bw is invalid."""
  835. np.random.seed(8765678)
  836. n_basesample = 50
  837. data = np.random.randn(n_basesample)
  838. with pytest.raises(ValueError):
  839. mlab.GaussianKDE(data, bw_method="invalid")
  840. class TestGaussianKDEEvaluate:
  841. def test_evaluate_diff_dim(self):
  842. """
  843. Test the evaluate method when the dim's of dataset and points have
  844. different dimensions.
  845. """
  846. x1 = np.arange(3, 10, 2)
  847. kde = mlab.GaussianKDE(x1)
  848. x2 = np.arange(3, 12, 2)
  849. y_expected = [
  850. 0.08797252, 0.11774109, 0.11774109, 0.08797252, 0.0370153
  851. ]
  852. y = kde.evaluate(x2)
  853. np.testing.assert_array_almost_equal(y, y_expected, 7)
  854. def test_evaluate_inv_dim(self):
  855. """
  856. Invert the dimensions; i.e., for a dataset of dimension 1 [3, 2, 4],
  857. the points should have a dimension of 3 [[3], [2], [4]].
  858. """
  859. np.random.seed(8765678)
  860. n_basesample = 50
  861. multidim_data = np.random.randn(n_basesample)
  862. kde = mlab.GaussianKDE(multidim_data)
  863. x2 = [[1], [2], [3]]
  864. with pytest.raises(ValueError):
  865. kde.evaluate(x2)
  866. def test_evaluate_dim_and_num(self):
  867. """Tests if evaluated against a one by one array"""
  868. x1 = np.arange(3, 10, 2)
  869. x2 = np.array([3])
  870. kde = mlab.GaussianKDE(x1)
  871. y_expected = [0.08797252]
  872. y = kde.evaluate(x2)
  873. np.testing.assert_array_almost_equal(y, y_expected, 7)
  874. def test_evaluate_point_dim_not_one(self):
  875. x1 = np.arange(3, 10, 2)
  876. x2 = [np.arange(3, 10, 2), np.arange(3, 10, 2)]
  877. kde = mlab.GaussianKDE(x1)
  878. with pytest.raises(ValueError):
  879. kde.evaluate(x2)
  880. def test_evaluate_equal_dim_and_num_lt(self):
  881. x1 = np.arange(3, 10, 2)
  882. x2 = np.arange(3, 8, 2)
  883. kde = mlab.GaussianKDE(x1)
  884. y_expected = [0.08797252, 0.11774109, 0.11774109]
  885. y = kde.evaluate(x2)
  886. np.testing.assert_array_almost_equal(y, y_expected, 7)
  887. def test_psd_onesided_norm():
  888. u = np.array([0, 1, 2, 3, 1, 2, 1])
  889. dt = 1.0
  890. Su = np.abs(np.fft.fft(u) * dt)**2 / (dt * u.size)
  891. P, f = mlab.psd(u, NFFT=u.size, Fs=1/dt, window=mlab.window_none,
  892. detrend=mlab.detrend_none, noverlap=0, pad_to=None,
  893. scale_by_freq=None,
  894. sides='onesided')
  895. Su_1side = np.append([Su[0]], Su[1:4] + Su[4:][::-1])
  896. assert_allclose(P, Su_1side, atol=1e-06)
  897. def test_psd_oversampling():
  898. """Test the case len(x) < NFFT for psd()."""
  899. u = np.array([0, 1, 2, 3, 1, 2, 1])
  900. dt = 1.0
  901. Su = np.abs(np.fft.fft(u) * dt)**2 / (dt * u.size)
  902. P, f = mlab.psd(u, NFFT=u.size*2, Fs=1/dt, window=mlab.window_none,
  903. detrend=mlab.detrend_none, noverlap=0, pad_to=None,
  904. scale_by_freq=None,
  905. sides='onesided')
  906. Su_1side = np.append([Su[0]], Su[1:4] + Su[4:][::-1])
  907. assert_almost_equal(np.sum(P), np.sum(Su_1side)) # same energy