statistics.py 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  1. # -*- coding: utf-8 -*-
  2. """
  3. Statistical measures of spike trains (e.g., Fano factor) and functions to estimate firing rates.
  4. :copyright: Copyright 2014-2016 by the Elephant team, see AUTHORS.txt.
  5. :license: Modified BSD, see LICENSE.txt for details.
  6. """
  7. from __future__ import division, print_function
  8. import numpy as np
  9. import quantities as pq
  10. import scipy.stats
  11. import scipy.signal
  12. import neo
  13. from neo.core import SpikeTrain
  14. import elephant.conversion as conv
  15. import elephant.kernels as kernels
  16. import warnings
  17. # warnings.simplefilter('always', DeprecationWarning)
  18. def isi(spiketrain, axis=-1):
  19. """
  20. Return an array containing the inter-spike intervals of the SpikeTrain.
  21. Accepts a Neo SpikeTrain, a Quantity array, or a plain NumPy array.
  22. If either a SpikeTrain or Quantity array is provided, the return value will
  23. be a quantities array, otherwise a plain NumPy array. The units of
  24. the quantities array will be the same as spiketrain.
  25. Parameters
  26. ----------
  27. spiketrain : Neo SpikeTrain or Quantity array or NumPy ndarray
  28. The spike times.
  29. axis : int, optional
  30. The axis along which the difference is taken.
  31. Default is the last axis.
  32. Returns
  33. -------
  34. NumPy array or quantities array.
  35. """
  36. if axis is None:
  37. axis = -1
  38. if isinstance(spiketrain, neo.SpikeTrain):
  39. intervals = np.diff(
  40. np.sort(spiketrain.times.view(pq.Quantity)), axis=axis)
  41. else:
  42. intervals = np.diff(np.sort(spiketrain), axis=axis)
  43. return intervals
  44. def mean_firing_rate(spiketrain, t_start=None, t_stop=None, axis=None):
  45. """
  46. Return the firing rate of the SpikeTrain.
  47. Accepts a Neo SpikeTrain, a Quantity array, or a plain NumPy array.
  48. If either a SpikeTrain or Quantity array is provided, the return value will
  49. be a quantities array, otherwise a plain NumPy array. The units of
  50. the quantities array will be the inverse of the spiketrain.
  51. The interval over which the firing rate is calculated can be optionally
  52. controlled with `t_start` and `t_stop`
  53. Parameters
  54. ----------
  55. spiketrain : Neo SpikeTrain or Quantity array or NumPy ndarray
  56. The spike times.
  57. t_start : float or Quantity scalar, optional
  58. The start time to use for the interval.
  59. If not specified, retrieved from the``t_start`
  60. attribute of `spiketrain`. If that is not present, default to
  61. `0`. Any value from `spiketrain` below this value is ignored.
  62. t_stop : float or Quantity scalar, optional
  63. The stop time to use for the time points.
  64. If not specified, retrieved from the `t_stop`
  65. attribute of `spiketrain`. If that is not present, default to
  66. the maximum value of `spiketrain`. Any value from
  67. `spiketrain` above this value is ignored.
  68. axis : int, optional
  69. The axis over which to do the calculation.
  70. Default is `None`, do the calculation over the flattened array.
  71. Returns
  72. -------
  73. float, quantities scalar, NumPy array or quantities array.
  74. Notes
  75. -----
  76. If `spiketrain` is a Quantity or Neo SpikeTrain and `t_start` or `t_stop`
  77. are not, `t_start` and `t_stop` are assumed to have the same units as
  78. `spiketrain`.
  79. Raises
  80. ------
  81. TypeError
  82. If `spiketrain` is a NumPy array and `t_start` or `t_stop`
  83. is a quantity scalar.
  84. """
  85. if t_start is None:
  86. t_start = getattr(spiketrain, 't_start', 0)
  87. found_t_start = False
  88. if t_stop is None:
  89. if hasattr(spiketrain, 't_stop'):
  90. t_stop = spiketrain.t_stop
  91. else:
  92. t_stop = np.max(spiketrain, axis=axis)
  93. found_t_start = True
  94. # figure out what units, if any, we are dealing with
  95. if hasattr(spiketrain, 'units'):
  96. units = spiketrain.units
  97. else:
  98. units = None
  99. # convert everything to the same units
  100. if hasattr(t_start, 'units'):
  101. if units is None:
  102. raise TypeError('t_start cannot be a Quantity if '
  103. 'spiketrain is not a quantity')
  104. t_start = t_start.rescale(units)
  105. elif units is not None:
  106. t_start = pq.Quantity(t_start, units=units)
  107. if hasattr(t_stop, 'units'):
  108. if units is None:
  109. raise TypeError('t_stop cannot be a Quantity if '
  110. 'spiketrain is not a quantity')
  111. t_stop = t_stop.rescale(units)
  112. elif units is not None:
  113. t_stop = pq.Quantity(t_stop, units=units)
  114. if not axis or not found_t_start:
  115. return np.sum((spiketrain >= t_start) & (spiketrain <= t_stop),
  116. axis=axis) / (t_stop - t_start)
  117. else:
  118. # this is needed to handle broadcasting between spiketrain and t_stop
  119. t_stop_test = np.expand_dims(t_stop, axis)
  120. return np.sum((spiketrain >= t_start) & (spiketrain <= t_stop_test),
  121. axis=axis) / (t_stop - t_start)
  122. # we make `cv` an alias for scipy.stats.variation for the convenience
  123. # of former NeuroTools users
  124. cv = scipy.stats.variation
  125. def fanofactor(spiketrains):
  126. """
  127. Evaluates the empirical Fano factor F of the spike counts of
  128. a list of `neo.core.SpikeTrain` objects.
  129. Given the vector v containing the observed spike counts (one per
  130. spike train) in the time window [t0, t1], F is defined as:
  131. F := var(v)/mean(v).
  132. The Fano factor is typically computed for spike trains representing the
  133. activity of the same neuron over different trials. The higher F, the larger
  134. the cross-trial non-stationarity. In theory for a time-stationary Poisson
  135. process, F=1.
  136. Parameters
  137. ----------
  138. spiketrains : list of neo.SpikeTrain objects, quantity arrays, numpy arrays or lists
  139. Spike trains for which to compute the Fano factor of spike counts.
  140. Returns
  141. -------
  142. fano : float or nan
  143. The Fano factor of the spike counts of the input spike trains. If an
  144. empty list is specified, or if all spike trains are empty, F:=nan.
  145. """
  146. # Build array of spike counts (one per spike train)
  147. spike_counts = np.array([len(t) for t in spiketrains])
  148. # Compute FF
  149. if all([count == 0 for count in spike_counts]):
  150. fano = np.nan
  151. else:
  152. fano = spike_counts.var() / spike_counts.mean()
  153. return fano
  154. def lv(v):
  155. """
  156. Calculate the measure of local variation LV for
  157. a sequence of time intervals between events.
  158. Given a vector v containing a sequence of intervals, the LV is
  159. defined as:
  160. .math $$ LV := \\frac{1}{N}\\sum_{i=1}^{N-1}
  161. \\frac{3(isi_i-isi_{i+1})^2}
  162. {(isi_i+isi_{i+1})^2} $$
  163. The LV is typically computed as a substitute for the classical
  164. coefficient of variation for sequences of events which include
  165. some (relatively slow) rate fluctuation. As with the CV, LV=1 for
  166. a sequence of intervals generated by a Poisson process.
  167. Parameters
  168. ----------
  169. v : quantity array, numpy array or list
  170. Vector of consecutive time intervals
  171. Returns
  172. -------
  173. lvar : float
  174. The LV of the inter-spike interval of the input sequence.
  175. Raises
  176. ------
  177. AttributeError :
  178. If an empty list is specified, or if the sequence has less
  179. than two entries, an AttributeError will be raised.
  180. ValueError :
  181. Only vector inputs are supported. If a matrix is passed to the
  182. function a ValueError will be raised.
  183. References
  184. ----------
  185. ..[1] Shinomoto, S., Shima, K., & Tanji, J. (2003). Differences in spiking
  186. patterns among cortical neurons. Neural Computation, 15, 2823–2842.
  187. """
  188. # convert to array, cast to float
  189. v = np.asarray(v)
  190. # ensure we have enough entries
  191. if v.size < 2:
  192. raise AttributeError("Input size is too small. Please provide "
  193. "an input with more than 1 entry.")
  194. # calculate LV and return result
  195. # raise error if input is multi-dimensional
  196. return 3. * np.mean(np.power(np.diff(v) / (v[:-1] + v[1:]), 2))
  197. # sigma2kw and kw2sigma only needed for oldfct_instantaneous_rate!
  198. # to finally be taken out of Elephant
  199. def sigma2kw(form):
  200. warnings.simplefilter('always', DeprecationWarning)
  201. warnings.warn("deprecated", DeprecationWarning, stacklevel=2)
  202. if form.upper() == 'BOX':
  203. coeff = 2.0 * np.sqrt(3)
  204. elif form.upper() == 'TRI':
  205. coeff = 2.0 * np.sqrt(6)
  206. elif form.upper() == 'EPA':
  207. coeff = 2.0 * np.sqrt(5)
  208. elif form.upper() == 'GAU':
  209. coeff = 2.0 * 2.7 # > 99% of distribution weight
  210. elif form.upper() == 'ALP':
  211. coeff = 5.0
  212. elif form.upper() == 'EXP':
  213. coeff = 5.0
  214. return coeff
  215. def kw2sigma(form):
  216. warnings.simplefilter('always', DeprecationWarning)
  217. warnings.warn("deprecated", DeprecationWarning, stacklevel=2)
  218. return 1/sigma2kw(form)
  219. # to finally be taken out of Elephant
  220. def make_kernel(form, sigma, sampling_period, direction=1):
  221. """
  222. Creates kernel functions for convolution.
  223. Constructs a numeric linear convolution kernel of basic shape to be used
  224. for data smoothing (linear low pass filtering) and firing rate estimation
  225. from single trial or trial-averaged spike trains.
  226. Exponential and alpha kernels may also be used to represent postynaptic
  227. currents / potentials in a linear (current-based) model.
  228. Parameters
  229. ----------
  230. form : {'BOX', 'TRI', 'GAU', 'EPA', 'EXP', 'ALP'}
  231. Kernel form. Currently implemented forms are BOX (boxcar),
  232. TRI (triangle), GAU (gaussian), EPA (epanechnikov), EXP (exponential),
  233. ALP (alpha function). EXP and ALP are asymmetric kernel forms and
  234. assume optional parameter `direction`.
  235. sigma : Quantity
  236. Standard deviation of the distribution associated with kernel shape.
  237. This parameter defines the time resolution of the kernel estimate
  238. and makes different kernels comparable (cf. [1] for symmetric kernels).
  239. This is used here as an alternative definition to the cut-off
  240. frequency of the associated linear filter.
  241. sampling_period : float
  242. Temporal resolution of input and output.
  243. direction : {-1, 1}
  244. Asymmetric kernels have two possible directions.
  245. The values are -1 or 1, default is 1. The
  246. definition here is that for direction = 1 the
  247. kernel represents the impulse response function
  248. of the linear filter. Default value is 1.
  249. Returns
  250. -------
  251. kernel : numpy.ndarray
  252. Array of kernel. The length of this array is always an odd
  253. number to represent symmetric kernels such that the center bin
  254. coincides with the median of the numeric array, i.e for a
  255. triangle, the maximum will be at the center bin with equal
  256. number of bins to the right and to the left.
  257. norm : float
  258. For rate estimates. The kernel vector is normalized such that
  259. the sum of all entries equals unity sum(kernel)=1. When
  260. estimating rate functions from discrete spike data (0/1) the
  261. additional parameter `norm` allows for the normalization to
  262. rate in spikes per second.
  263. For example:
  264. ``rate = norm * scipy.signal.lfilter(kernel, 1, spike_data)``
  265. m_idx : int
  266. Index of the numerically determined median (center of gravity)
  267. of the kernel function.
  268. Examples
  269. --------
  270. To obtain single trial rate function of trial one should use::
  271. r = norm * scipy.signal.fftconvolve(sua, kernel)
  272. To obtain trial-averaged spike train one should use::
  273. r_avg = norm * scipy.signal.fftconvolve(sua, np.mean(X,1))
  274. where `X` is an array of shape `(l,n)`, `n` is the number of trials and
  275. `l` is the length of each trial.
  276. See also
  277. --------
  278. elephant.statistics.instantaneous_rate
  279. References
  280. ----------
  281. .. [1] Meier R, Egert U, Aertsen A, Nawrot MP, "FIND - a unified framework
  282. for neural data analysis"; Neural Netw. 2008 Oct; 21(8):1085-93.
  283. .. [2] Nawrot M, Aertsen A, Rotter S, "Single-trial estimation of neuronal
  284. firing rates - from single neuron spike trains to population activity";
  285. J. Neurosci Meth 94: 81-92; 1999.
  286. """
  287. warnings.simplefilter('always', DeprecationWarning)
  288. warnings.warn("deprecated", DeprecationWarning, stacklevel=2)
  289. forms_abbreviated = np.array(['BOX', 'TRI', 'GAU', 'EPA', 'EXP', 'ALP'])
  290. forms_verbose = np.array(['boxcar', 'triangle', 'gaussian', 'epanechnikov',
  291. 'exponential', 'alpha'])
  292. if form in forms_verbose:
  293. form = forms_abbreviated[forms_verbose == form][0]
  294. assert form.upper() in ('BOX', 'TRI', 'GAU', 'EPA', 'EXP', 'ALP'), \
  295. "form must be one of either 'BOX','TRI','GAU','EPA','EXP' or 'ALP'!"
  296. assert direction in (1, -1), "direction must be either 1 or -1"
  297. # conversion to SI units (s)
  298. if sigma < 0:
  299. raise ValueError('sigma must be positive!')
  300. SI_sigma = sigma.rescale('s').magnitude
  301. SI_time_stamp_resolution = sampling_period.rescale('s').magnitude
  302. norm = 1. / SI_time_stamp_resolution
  303. if form.upper() == 'BOX':
  304. w = 2.0 * SI_sigma * np.sqrt(3)
  305. # always odd number of bins
  306. width = 2 * np.floor(w / 2.0 / SI_time_stamp_resolution) + 1
  307. height = 1. / width
  308. kernel = np.ones((1, width)) * height # area = 1
  309. elif form.upper() == 'TRI':
  310. w = 2 * SI_sigma * np.sqrt(6)
  311. halfwidth = np.floor(w / 2.0 / SI_time_stamp_resolution)
  312. trileft = np.arange(1, halfwidth + 2)
  313. triright = np.arange(halfwidth, 0, -1) # odd number of bins
  314. triangle = np.append(trileft, triright)
  315. kernel = triangle / triangle.sum() # area = 1
  316. elif form.upper() == 'EPA':
  317. w = 2.0 * SI_sigma * np.sqrt(5)
  318. halfwidth = np.floor(w / 2.0 / SI_time_stamp_resolution)
  319. base = np.arange(-halfwidth, halfwidth + 1)
  320. parabula = base**2
  321. epanech = parabula.max() - parabula # inverse parabula
  322. kernel = epanech / epanech.sum() # area = 1
  323. elif form.upper() == 'GAU':
  324. w = 2.0 * SI_sigma * 2.7 # > 99% of distribution weight
  325. halfwidth = np.floor(w / 2.0 / SI_time_stamp_resolution) # always odd
  326. base = np.arange(-halfwidth, halfwidth + 1) * SI_time_stamp_resolution
  327. g = np.exp(
  328. -(base**2) / 2.0 / SI_sigma**2) / SI_sigma / np.sqrt(2.0 * np.pi)
  329. kernel = g / g.sum() # area = 1
  330. elif form.upper() == 'ALP':
  331. w = 5.0 * SI_sigma
  332. alpha = np.arange(
  333. 1, (
  334. 2.0 * np.floor(w / SI_time_stamp_resolution / 2.0) + 1) +
  335. 1) * SI_time_stamp_resolution
  336. alpha = (2.0 / SI_sigma**2) * alpha * np.exp(
  337. -alpha * np.sqrt(2) / SI_sigma)
  338. kernel = alpha / alpha.sum() # normalization
  339. if direction == -1:
  340. kernel = np.flipud(kernel)
  341. elif form.upper() == 'EXP':
  342. w = 5.0 * SI_sigma
  343. expo = np.arange(
  344. 1, (
  345. 2.0 * np.floor(w / SI_time_stamp_resolution / 2.0) + 1) +
  346. 1) * SI_time_stamp_resolution
  347. expo = np.exp(-expo / SI_sigma)
  348. kernel = expo / expo.sum()
  349. if direction == -1:
  350. kernel = np.flipud(kernel)
  351. kernel = kernel.ravel()
  352. m_idx = np.nonzero(kernel.cumsum() >= 0.5)[0].min()
  353. return kernel, norm, m_idx
  354. # to finally be taken out of Elephant
  355. def oldfct_instantaneous_rate(spiketrain, sampling_period, form,
  356. sigma='auto', t_start=None, t_stop=None,
  357. acausal=True, trim=False):
  358. """
  359. Estimate instantaneous firing rate by kernel convolution.
  360. Parameters
  361. -----------
  362. spiketrain: 'neo.SpikeTrain'
  363. Neo object that contains spike times, the unit of the time stamps
  364. and t_start and t_stop of the spike train.
  365. sampling_period : Quantity
  366. time stamp resolution of the spike times. the same resolution will
  367. be assumed for the kernel
  368. form : {'BOX', 'TRI', 'GAU', 'EPA', 'EXP', 'ALP'}
  369. Kernel form. Currently implemented forms are BOX (boxcar),
  370. TRI (triangle), GAU (gaussian), EPA (epanechnikov), EXP (exponential),
  371. ALP (alpha function). EXP and ALP are asymmetric kernel forms and
  372. assume optional parameter `direction`.
  373. sigma : string or Quantity
  374. Standard deviation of the distribution associated with kernel shape.
  375. This parameter defines the time resolution of the kernel estimate
  376. and makes different kernels comparable (cf. [1] for symmetric kernels).
  377. This is used here as an alternative definition to the cut-off
  378. frequency of the associated linear filter.
  379. Default value is 'auto'. In this case, the optimized kernel width for
  380. the rate estimation is calculated according to [1]. Note that the
  381. automatized calculation of the kernel width ONLY works for gaussian
  382. kernel shapes!
  383. t_start : Quantity (Optional)
  384. start time of the interval used to compute the firing rate, if None
  385. assumed equal to spiketrain.t_start
  386. Default:None
  387. t_stop : Qunatity
  388. End time of the interval used to compute the firing rate (included).
  389. If none assumed equal to spiketrain.t_stop
  390. Default:None
  391. acausal : bool
  392. if True, acausal filtering is used, i.e., the gravity center of the
  393. filter function is aligned with the spike to convolve
  394. Default:None
  395. m_idx : int
  396. index of the value in the kernel function vector that corresponds
  397. to its gravity center. this parameter is not mandatory for
  398. symmetrical kernels but it is required when asymmetrical kernels
  399. are to be aligned at their gravity center with the event times if None
  400. is assumed to be the median value of the kernel support
  401. Default : None
  402. trim : bool
  403. if True, only the 'valid' region of the convolved
  404. signal are returned, i.e., the points where there
  405. isn't complete overlap between kernel and spike train
  406. are discarded
  407. NOTE: if True and an asymmetrical kernel is provided
  408. the output will not be aligned with [t_start, t_stop]
  409. Returns
  410. -------
  411. rate : neo.AnalogSignal
  412. Contains the rate estimation in unit hertz (Hz).
  413. Has a property 'rate.times' which contains the time axis of the rate
  414. estimate. The unit of this property is the same as the resolution that
  415. is given as an argument to the function.
  416. Raises
  417. ------
  418. TypeError:
  419. If argument value for the parameter `sigma` is not a quantity object
  420. or string 'auto'.
  421. See also
  422. --------
  423. elephant.statistics.make_kernel
  424. References
  425. ----------
  426. ..[1] H. Shimazaki, S. Shinomoto, J Comput Neurosci (2010) 29:171–182.
  427. """
  428. warnings.simplefilter('always', DeprecationWarning)
  429. warnings.warn("deprecated", DeprecationWarning, stacklevel=2)
  430. if sigma == 'auto':
  431. form = 'GAU'
  432. unit = spiketrain.units
  433. kernel_width = sskernel(spiketrain.magnitude, tin=None,
  434. bootstrap=True)['optw']
  435. sigma = kw2sigma(form) * kernel_width * unit
  436. elif not isinstance(sigma, pq.Quantity):
  437. raise TypeError('sigma must be either a quantities object or "auto".'
  438. ' Found: %s, value %s' % (type(sigma), str(sigma)))
  439. kernel, norm, m_idx = make_kernel(form=form, sigma=sigma,
  440. sampling_period=sampling_period)
  441. units = pq.CompoundUnit(
  442. "%s*s" % str(sampling_period.rescale('s').magnitude))
  443. spiketrain = spiketrain.rescale(units)
  444. if t_start is None:
  445. t_start = spiketrain.t_start
  446. else:
  447. t_start = t_start.rescale(spiketrain.units)
  448. if t_stop is None:
  449. t_stop = spiketrain.t_stop
  450. else:
  451. t_stop = t_stop.rescale(spiketrain.units)
  452. time_vector = np.zeros(int((t_stop - t_start)) + 1)
  453. spikes_slice = spiketrain.time_slice(t_start, t_stop) \
  454. if len(spiketrain) else np.array([])
  455. for spike in spikes_slice:
  456. index = int((spike - t_start))
  457. time_vector[index] += 1
  458. r = norm * scipy.signal.fftconvolve(time_vector, kernel, 'full')
  459. if np.any(r < 0):
  460. warnings.warn('Instantaneous firing rate approximation contains '
  461. 'negative values, possibly caused due to machine '
  462. 'precision errors')
  463. if acausal:
  464. if not trim:
  465. r = r[m_idx:-(kernel.size - m_idx)]
  466. elif trim:
  467. r = r[2 * m_idx:-2 * (kernel.size - m_idx)]
  468. t_start = t_start + m_idx * spiketrain.units
  469. t_stop = t_stop - ((kernel.size) - m_idx) * spiketrain.units
  470. else:
  471. if not trim:
  472. r = r[m_idx:-(kernel.size - m_idx)]
  473. elif trim:
  474. r = r[2 * m_idx:-2 * (kernel.size - m_idx)]
  475. t_start = t_start + m_idx * spiketrain.units
  476. t_stop = t_stop - ((kernel.size) - m_idx) * spiketrain.units
  477. rate = neo.AnalogSignal(signal=r.reshape(r.size, 1),
  478. sampling_period=sampling_period,
  479. units=pq.Hz, t_start=t_start)
  480. return rate, sigma
  481. def instantaneous_rate(spiketrain, sampling_period, kernel='auto',
  482. cutoff=5.0, t_start=None, t_stop=None, trim=False):
  483. """
  484. Estimates instantaneous firing rate by kernel convolution.
  485. Parameters
  486. -----------
  487. spiketrain : 'neo.SpikeTrain'
  488. Neo object that contains spike times, the unit of the time stamps
  489. and t_start and t_stop of the spike train.
  490. sampling_period : Time Quantity
  491. Time stamp resolution of the spike times. The same resolution will
  492. be assumed for the kernel
  493. kernel : string 'auto' or callable object of :class:`Kernel` from module
  494. 'kernels.py'. Currently implemented kernel forms are rectangular,
  495. triangular, epanechnikovlike, gaussian, laplacian, exponential,
  496. and alpha function.
  497. Example: kernel = kernels.RectangularKernel(sigma=10*ms, invert=False)
  498. The kernel is used for convolution with the spike train and its
  499. standard deviation determines the time resolution of the instantaneous
  500. rate estimation.
  501. Default: 'auto'. In this case, the optimized kernel width for the
  502. rate estimation is calculated according to [1] and with this width
  503. a gaussian kernel is constructed. Automatized calculation of the
  504. kernel width is not available for other than gaussian kernel shapes.
  505. cutoff : float
  506. This factor determines the cutoff of the probability distribution of
  507. the kernel, i.e., the considered width of the kernel in terms of
  508. multiples of the standard deviation sigma.
  509. Default: 5.0
  510. t_start : Time Quantity (optional)
  511. Start time of the interval used to compute the firing rate. If None
  512. assumed equal to spiketrain.t_start
  513. Default: None
  514. t_stop : Time Quantity (optional)
  515. End time of the interval used to compute the firing rate (included).
  516. If None assumed equal to spiketrain.t_stop
  517. Default: None
  518. trim : bool
  519. if False, the output of the Fast Fourier Transformation being a longer
  520. vector than the input vector by the size of the kernel is reduced back
  521. to the original size of the considered time interval of the spiketrain
  522. using the median of the kernel.
  523. if True, only the region of the convolved signal is returned, where
  524. there is complete overlap between kernel and spike train. This is
  525. achieved by reducing the length of the output of the Fast Fourier
  526. Transformation by a total of two times the size of the kernel, and
  527. t_start and t_stop are adjusted.
  528. Default: False
  529. Returns
  530. -------
  531. rate : neo.AnalogSignal
  532. Contains the rate estimation in unit hertz (Hz).
  533. Has a property 'rate.times' which contains the time axis of the rate
  534. estimate. The unit of this property is the same as the resolution that
  535. is given via the argument 'sampling_period' to the function.
  536. Raises
  537. ------
  538. TypeError:
  539. If `spiketrain` is not an instance of :class:`SpikeTrain` of Neo.
  540. If `sampling_period` is not a time quantity.
  541. If `kernel` is neither instance of :class:`Kernel` or string 'auto'.
  542. If `cutoff` is neither float nor int.
  543. If `t_start` and `t_stop` are neither None nor a time quantity.
  544. If `trim` is not bool.
  545. ValueError:
  546. If `sampling_period` is smaller than zero.
  547. Example
  548. --------
  549. kernel = kernels.AlphaKernel(sigma = 0.05*s, invert = True)
  550. rate = instantaneous_rate(spiketrain, sampling_period = 2*ms, kernel)
  551. References
  552. ----------
  553. ..[1] H. Shimazaki, S. Shinomoto, J Comput Neurosci (2010) 29:171–182.
  554. """
  555. # Checks of input variables:
  556. if not isinstance(spiketrain, SpikeTrain):
  557. raise TypeError(
  558. "spiketrain must be instance of :class:`SpikeTrain` of Neo!\n"
  559. " Found: %s, value %s" % (type(spiketrain), str(spiketrain)))
  560. if not (isinstance(sampling_period, pq.Quantity) and
  561. sampling_period.dimensionality.simplified ==
  562. pq.Quantity(1, "s").dimensionality):
  563. raise TypeError(
  564. "The sampling period must be a time quantity!\n"
  565. " Found: %s, value %s" % (type(sampling_period), str(sampling_period)))
  566. if sampling_period.magnitude < 0:
  567. raise ValueError("The sampling period must be larger than zero.")
  568. if kernel == 'auto':
  569. kernel_width = sskernel(spiketrain.magnitude, tin=None,
  570. bootstrap=True)['optw']
  571. unit = spiketrain.units
  572. sigma = 1/(2.0 * 2.7) * kernel_width * unit
  573. # factor 2.0 connects kernel width with its half width,
  574. # factor 2.7 connects half width of Gaussian distribution with
  575. # 99% probability mass with its standard deviation.
  576. kernel = kernels.GaussianKernel(sigma)
  577. elif not isinstance(kernel, kernels.Kernel):
  578. raise TypeError(
  579. "kernel must be either instance of :class:`Kernel` "
  580. "or the string 'auto'!\n"
  581. " Found: %s, value %s" % (type(kernel), str(kernel)))
  582. if not (isinstance(cutoff, float) or isinstance(cutoff, int)):
  583. raise TypeError("cutoff must be float or integer!")
  584. if not (t_start is None or (isinstance(t_start, pq.Quantity) and
  585. t_start.dimensionality.simplified ==
  586. pq.Quantity(1, "s").dimensionality)):
  587. raise TypeError("t_start must be a time quantity!")
  588. if not (t_stop is None or (isinstance(t_stop, pq.Quantity) and
  589. t_stop.dimensionality.simplified ==
  590. pq.Quantity(1, "s").dimensionality)):
  591. raise TypeError("t_stop must be a time quantity!")
  592. if not (isinstance(trim, bool)):
  593. raise TypeError("trim must be bool!")
  594. # main function:
  595. units = pq.CompoundUnit("%s*s" % str(sampling_period.rescale('s').magnitude))
  596. spiketrain = spiketrain.rescale(units)
  597. if t_start is None:
  598. t_start = spiketrain.t_start
  599. else:
  600. t_start = t_start.rescale(spiketrain.units)
  601. if t_stop is None:
  602. t_stop = spiketrain.t_stop
  603. else:
  604. t_stop = t_stop.rescale(spiketrain.units)
  605. time_vector = np.zeros(int((t_stop - t_start)) + 1)
  606. spikes_slice = spiketrain.time_slice(t_start, t_stop) \
  607. if len(spiketrain) else np.array([])
  608. for spike in spikes_slice:
  609. index = int((spike - t_start))
  610. time_vector[index] += 1
  611. if cutoff < kernel.min_cutoff:
  612. cutoff = kernel.min_cutoff
  613. warnings.warn("The width of the kernel was adjusted to a minimally "
  614. "allowed width.")
  615. t_arr = np.arange(-cutoff * kernel.sigma.rescale(units).magnitude,
  616. cutoff * kernel.sigma.rescale(units).magnitude +
  617. sampling_period.rescale(units).magnitude,
  618. sampling_period.rescale(units).magnitude) * units
  619. r = scipy.signal.fftconvolve(time_vector,
  620. kernel(t_arr).rescale(pq.Hz).magnitude, 'full')
  621. if np.any(r < 0):
  622. warnings.warn("Instantaneous firing rate approximation contains "
  623. "negative values, possibly caused due to machine "
  624. "precision errors.")
  625. if not trim:
  626. r = r[kernel.median_index(t_arr):-(kernel(t_arr).size -
  627. kernel.median_index(t_arr))]
  628. elif trim:
  629. r = r[2 * kernel.median_index(t_arr):-2 * (kernel(t_arr).size -
  630. kernel.median_index(t_arr))]
  631. t_start += kernel.median_index(t_arr) * spiketrain.units
  632. t_stop -= (kernel(t_arr).size -
  633. kernel.median_index(t_arr)) * spiketrain.units
  634. rate = neo.AnalogSignal(signal=r.reshape(r.size, 1),
  635. sampling_period=sampling_period,
  636. units=pq.Hz, t_start=t_start, t_stop=t_stop)
  637. return rate
  638. def time_histogram(spiketrains, binsize, t_start=None, t_stop=None,
  639. output='counts', binary=False):
  640. """
  641. Time Histogram of a list of :attr:`neo.SpikeTrain` objects.
  642. Parameters
  643. ----------
  644. spiketrains : List of neo.SpikeTrain objects
  645. Spiketrains with a common time axis (same `t_start` and `t_stop`)
  646. binsize : quantities.Quantity
  647. Width of the histogram's time bins.
  648. t_start, t_stop : Quantity (optional)
  649. Start and stop time of the histogram. Only events in the input
  650. `spiketrains` falling between `t_start` and `t_stop` (both included)
  651. are considered in the histogram. If `t_start` and/or `t_stop` are not
  652. specified, the maximum `t_start` of all :attr:spiketrains is used as
  653. `t_start`, and the minimum `t_stop` is used as `t_stop`.
  654. Default: t_start = t_stop = None
  655. output : str (optional)
  656. Normalization of the histogram. Can be one of:
  657. * `counts`'`: spike counts at each bin (as integer numbers)
  658. * `mean`: mean spike counts per spike train
  659. * `rate`: mean spike rate per spike train. Like 'mean', but the
  660. counts are additionally normalized by the bin width.
  661. binary : bool (optional)
  662. If **True**, indicates whether all spiketrain objects should first
  663. binned to a binary representation (using the `BinnedSpikeTrain` class
  664. in the `conversion` module) and the calculation of the histogram is
  665. based on this representation.
  666. Note that the output is not binary, but a histogram of the converted,
  667. binary representation.
  668. Default: False
  669. Returns
  670. -------
  671. time_hist : neo.AnalogSignal
  672. A neo.AnalogSignal object containing the histogram values.
  673. `AnalogSignal[j]` is the histogram computed between
  674. `t_start + j * binsize` and `t_start + (j + 1) * binsize`.
  675. See also
  676. --------
  677. elephant.conversion.BinnedSpikeTrain
  678. """
  679. min_tstop = 0
  680. if t_start is None:
  681. # Find the internal range for t_start, where all spike trains are
  682. # defined; cut all spike trains taking that time range only
  683. max_tstart, min_tstop = conv._get_start_stop_from_input(spiketrains)
  684. t_start = max_tstart
  685. if not all([max_tstart == t.t_start for t in spiketrains]):
  686. warnings.warn(
  687. "Spiketrains have different t_start values -- "
  688. "using maximum t_start as t_start.")
  689. if t_stop is None:
  690. # Find the internal range for t_stop
  691. if min_tstop:
  692. t_stop = min_tstop
  693. if not all([min_tstop == t.t_stop for t in spiketrains]):
  694. warnings.warn(
  695. "Spiketrains have different t_stop values -- "
  696. "using minimum t_stop as t_stop.")
  697. else:
  698. min_tstop = conv._get_start_stop_from_input(spiketrains)[1]
  699. t_stop = min_tstop
  700. if not all([min_tstop == t.t_stop for t in spiketrains]):
  701. warnings.warn(
  702. "Spiketrains have different t_stop values -- "
  703. "using minimum t_stop as t_stop.")
  704. sts_cut = [st.time_slice(t_start=t_start, t_stop=t_stop) for st in
  705. spiketrains]
  706. # Bin the spike trains and sum across columns
  707. bs = conv.BinnedSpikeTrain(sts_cut, t_start=t_start, t_stop=t_stop,
  708. binsize=binsize)
  709. if binary:
  710. bin_hist = bs.to_sparse_bool_array().sum(axis=0)
  711. else:
  712. bin_hist = bs.to_sparse_array().sum(axis=0)
  713. # Flatten array
  714. bin_hist = np.ravel(bin_hist)
  715. # Renormalise the histogram
  716. if output == 'counts':
  717. # Raw
  718. bin_hist = bin_hist * pq.dimensionless
  719. elif output == 'mean':
  720. # Divide by number of input spike trains
  721. bin_hist = bin_hist * 1. / len(spiketrains) * pq.dimensionless
  722. elif output == 'rate':
  723. # Divide by number of input spike trains and bin width
  724. bin_hist = bin_hist * 1. / len(spiketrains) / binsize
  725. else:
  726. raise ValueError('Parameter output is not valid.')
  727. return neo.AnalogSignal(signal=bin_hist.reshape(bin_hist.size, 1),
  728. sampling_period=binsize, units=bin_hist.units,
  729. t_start=t_start)
  730. def complexity_pdf(spiketrains, binsize):
  731. """
  732. Complexity Distribution [1] of a list of :attr:`neo.SpikeTrain` objects.
  733. Probability density computed from the complexity histogram which is the
  734. histogram of the entries of the population histogram of clipped (binary)
  735. spike trains computed with a bin width of binsize.
  736. It provides for each complexity (== number of active neurons per bin) the
  737. number of occurrences. The normalization of that histogram to 1 is the
  738. probability density.
  739. Parameters
  740. ----------
  741. spiketrains : List of neo.SpikeTrain objects
  742. Spiketrains with a common time axis (same `t_start` and `t_stop`)
  743. binsize : quantities.Quantity
  744. Width of the histogram's time bins.
  745. Returns
  746. -------
  747. time_hist : neo.AnalogSignal
  748. A neo.AnalogSignal object containing the histogram values.
  749. `AnalogSignal[j]` is the histogram computed between .
  750. See also
  751. --------
  752. elephant.conversion.BinnedSpikeTrain
  753. References
  754. ----------
  755. [1]Gruen, S., Abeles, M., & Diesmann, M. (2008). Impact of higher-order
  756. correlations on coincidence distributions of massively parallel data.
  757. In Dynamic Brain-from Neural Spikes to Behaviors (pp. 96-114).
  758. Springer Berlin Heidelberg.
  759. """
  760. # Computing the population histogram with parameter binary=True to clip the
  761. # spike trains before summing
  762. pophist = time_histogram(spiketrains, binsize, binary=True)
  763. # Computing the histogram of the entries of pophist (=Complexity histogram)
  764. complexity_hist = np.histogram(
  765. pophist.magnitude, bins=range(0, len(spiketrains) + 2))[0]
  766. # Normalization of the Complexity Histogram to 1 (probabilty distribution)
  767. complexity_hist = complexity_hist / complexity_hist.sum()
  768. # Convert the Complexity pdf to an neo.AnalogSignal
  769. complexity_distribution = neo.AnalogSignal(
  770. np.array(complexity_hist).reshape(len(complexity_hist), 1) *
  771. pq.dimensionless, t_start=0 * pq.dimensionless,
  772. sampling_period=1 * pq.dimensionless)
  773. return complexity_distribution
  774. """Kernel Bandwidth Optimization.
  775. Python implementation by Subhasis Ray.
  776. Original matlab code (sskernel.m) here:
  777. http://2000.jukuin.keio.ac.jp/shimazaki/res/kernel.html
  778. This was translated into Python by Subhasis Ray, NCBS. Tue Jun 10
  779. 23:01:43 IST 2014
  780. """
  781. def nextpow2(x):
  782. """ Return the smallest integral power of 2 that >= x """
  783. n = 2
  784. while n < x:
  785. n = 2 * n
  786. return n
  787. def fftkernel(x, w):
  788. """
  789. y = fftkernel(x,w)
  790. Function `fftkernel' applies the Gauss kernel smoother to an input
  791. signal using FFT algorithm.
  792. Input argument
  793. x: Sample signal vector.
  794. w: Kernel bandwidth (the standard deviation) in unit of
  795. the sampling resolution of x.
  796. Output argument
  797. y: Smoothed signal.
  798. MAY 5/23, 2012 Author Hideaki Shimazaki
  799. RIKEN Brain Science Insitute
  800. http://2000.jukuin.keio.ac.jp/shimazaki
  801. Ported to Python: Subhasis Ray, NCBS. Tue Jun 10 10:42:38 IST 2014
  802. """
  803. L = len(x)
  804. Lmax = L + 3 * w
  805. n = nextpow2(Lmax)
  806. X = np.fft.fft(x, n)
  807. f = np.arange(0, n, 1.0) / n
  808. f = np.concatenate((-f[:int(n / 2)], f[int(n / 2):0:-1]))
  809. K = np.exp(-0.5 * (w * 2 * np.pi * f)**2)
  810. y = np.fft.ifft(X * K, n)
  811. y = y[:L].copy()
  812. return y
  813. def logexp(x):
  814. if x < 1e2:
  815. y = np.log(1 + np.exp(x))
  816. else:
  817. y = x
  818. return y
  819. def ilogexp(x):
  820. if x < 1e2:
  821. y = np.log(np.exp(x) - 1)
  822. else:
  823. y = x
  824. return y
  825. def cost_function(x, N, w, dt):
  826. """
  827. The cost function
  828. Cn(w) = sum_{i,j} int k(x - x_i) k(x - x_j) dx - 2 sum_{i~=j} k(x_i - x_j)
  829. """
  830. yh = np.abs(fftkernel(x, w / dt)) # density
  831. # formula for density
  832. C = np.sum(yh ** 2) * dt - 2 * np.sum(yh * x) * \
  833. dt + 2 / np.sqrt(2 * np.pi) / w / N
  834. C = C * N * N
  835. # formula for rate
  836. # C = dt*sum( yh.^2 - 2*yh.*y_hist + 2/sqrt(2*pi)/w*y_hist )
  837. return C, yh
  838. def sskernel(spiketimes, tin=None, w=None, bootstrap=False):
  839. """
  840. Calculates optimal fixed kernel bandwidth.
  841. spiketimes: sequence of spike times (sorted to be ascending).
  842. tin: (optional) time points at which the kernel bandwidth is to be estimated.
  843. w: (optional) vector of kernel bandwidths. If specified, optimal
  844. bandwidth is selected from this.
  845. bootstrap (optional): whether to calculate the 95% confidence
  846. interval. (default False)
  847. Returns
  848. A dictionary containing the following key value pairs:
  849. 'y': estimated density,
  850. 't': points at which estimation was computed,
  851. 'optw': optimal kernel bandwidth,
  852. 'w': kernel bandwidths examined,
  853. 'C': cost functions of w,
  854. 'confb95': (lower bootstrap confidence level, upper bootstrap confidence level),
  855. 'yb': bootstrap samples.
  856. Ref: Shimazaki, Hideaki, and Shigeru Shinomoto. 2010. Kernel
  857. Bandwidth Optimization in Spike Rate Estimation. Journal of
  858. Computational Neuroscience 29 (1-2):
  859. 171-82. doi:10.1007/s10827-009-0180-4.
  860. """
  861. if tin is None:
  862. time = np.max(spiketimes) - np.min(spiketimes)
  863. isi = np.diff(spiketimes)
  864. isi = isi[isi > 0].copy()
  865. dt = np.min(isi)
  866. tin = np.linspace(np.min(spiketimes),
  867. np.max(spiketimes),
  868. min(int(time / dt + 0.5), 1000)) # The 1000 seems somewhat arbitrary
  869. t = tin
  870. else:
  871. time = np.max(tin) - np.min(tin)
  872. spiketimes = spiketimes[(spiketimes >= np.min(tin)) &
  873. (spiketimes <= np.max(tin))].copy()
  874. isi = np.diff(spiketimes)
  875. isi = isi[isi > 0].copy()
  876. dt = np.min(isi)
  877. if dt > np.min(np.diff(tin)):
  878. t = np.linspace(np.min(tin), np.max(tin),
  879. min(int(time / dt + 0.5), 1000))
  880. else:
  881. t = tin
  882. dt = np.min(np.diff(tin))
  883. yhist, bins = np.histogram(spiketimes, np.r_[t - dt / 2, t[-1] + dt / 2])
  884. N = np.sum(yhist)
  885. yhist = yhist / (N * dt) # density
  886. optw = None
  887. y = None
  888. if w is not None:
  889. C = np.zeros(len(w))
  890. Cmin = np.inf
  891. for k, w_ in enumerate(w):
  892. C[k], yh = cost_function(yhist, N, w_, dt)
  893. if C[k] < Cmin:
  894. Cmin = C[k]
  895. optw = w_
  896. y = yh
  897. else:
  898. # Golden section search on a log-exp scale
  899. wmin = 2 * dt
  900. wmax = max(spiketimes) - min(spiketimes)
  901. imax = 20 # max iterations
  902. w = np.zeros(imax)
  903. C = np.zeros(imax)
  904. tolerance = 1e-5
  905. phi = 0.5 * (np.sqrt(5) + 1) # The Golden ratio
  906. a = ilogexp(wmin)
  907. b = ilogexp(wmax)
  908. c1 = (phi - 1) * a + (2 - phi) * b
  909. c2 = (2 - phi) * a + (phi - 1) * b
  910. f1, y1 = cost_function(yhist, N, logexp(c1), dt)
  911. f2, y2 = cost_function(yhist, N, logexp(c2), dt)
  912. k = 0
  913. while (np.abs(b - a) > (tolerance * (np.abs(c1) + np.abs(c2))))\
  914. and (k < imax):
  915. if f1 < f2:
  916. b = c2
  917. c2 = c1
  918. c1 = (phi - 1) * a + (2 - phi) * b
  919. f2 = f1
  920. f1, y1 = cost_function(yhist, N, logexp(c1), dt)
  921. w[k] = logexp(c1)
  922. C[k] = f1
  923. optw = logexp(c1)
  924. y = y1 / (np.sum(y1 * dt))
  925. else:
  926. a = c1
  927. c1 = c2
  928. c2 = (2 - phi) * a + (phi - 1) * b
  929. f1 = f2
  930. f2, y2 = cost_function(yhist, N, logexp(c2), dt)
  931. w[k] = logexp(c2)
  932. C[k] = f2
  933. optw = logexp(c2)
  934. y = y2 / np.sum(y2 * dt)
  935. k = k + 1
  936. # Bootstrap confidence intervals
  937. confb95 = None
  938. yb = None
  939. if bootstrap:
  940. nbs = 1000
  941. yb = np.zeros((nbs, len(tin)))
  942. for ii in range(nbs):
  943. idx = np.floor(np.random.rand(N) * N).astype(int)
  944. xb = spiketimes[idx]
  945. y_histb, bins = np.histogram(
  946. xb, np.r_[t - dt / 2, t[-1] + dt / 2]) / dt / N
  947. yb_buf = fftkernel(y_histb, optw / dt).real
  948. yb_buf = yb_buf / np.sum(yb_buf * dt)
  949. yb[ii, :] = np.interp(tin, t, yb_buf)
  950. ybsort = np.sort(yb, axis=0)
  951. y95b = ybsort[np.floor(0.05 * nbs).astype(int), :]
  952. y95u = ybsort[np.floor(0.95 * nbs).astype(int), :]
  953. confb95 = (y95b, y95u)
  954. ret = np.interp(tin, t, y)
  955. return {'y': ret,
  956. 't': tin,
  957. 'optw': optw,
  958. 'w': w,
  959. 'C': C,
  960. 'confb95': confb95,
  961. 'yb': yb}