granger.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. # -*- coding: utf-8 -*-
  2. """
  3. .. current_module elephant.causality
  4. Overview
  5. --------
  6. This module provides function to estimate causal influences of signals on each
  7. other.
  8. Granger causality
  9. ~~~~~~~~~~~~~~~~~
  10. Granger causality is a method to determine causal influence of one signal on
  11. another based on autoregressive modelling. It was developed by Nobel prize
  12. laureate Clive Granger and has been adopted in various numerical fields ever
  13. since :cite:`granger-Granger69_424`. In its simplest form, the
  14. method tests whether the past values of one signal help to reduce the
  15. prediction error of another signal, compared to the past values of the latter
  16. signal alone. If it does reduce the prediction error, the first signal is said
  17. to Granger cause the other signal.
  18. Limitations
  19. +++++++++++
  20. The user must be mindful of the method's limitations, which are assumptions of
  21. covariance stationary data, linearity imposed by the underlying autoregressive
  22. modelling as well as the fact that the variables not included in the model will
  23. not be accounted for :cite:`granger-Seth07_1667`.
  24. Implementation
  25. ++++++++++++++
  26. The mathematical implementation of Granger causality methods in this module
  27. closely follows :cite:`granger-Ding06_0608035`.
  28. Overview of Functions
  29. ---------------------
  30. Various formulations of Granger causality have been developed. In this module
  31. you will find function for time-series data to test pairwise Granger causality
  32. (`pairwise_granger`).
  33. Time-series Granger causality
  34. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  35. .. autosummary::
  36. :toctree: toctree/causality/
  37. pairwise_granger
  38. conditional_granger
  39. References
  40. ----------
  41. .. bibliography:: ../bib/elephant.bib
  42. :labelprefix: gr
  43. :keyprefix: granger-
  44. :style: unsrt
  45. :copyright: Copyright 2014-2020 by the Elephant team, see `doc/authors.rst`.
  46. :license: Modified BSD, see LICENSE.txt for details.
  47. """
  48. from __future__ import division, print_function, unicode_literals
  49. import warnings
  50. from collections import namedtuple
  51. import numpy as np
  52. from neo.core import AnalogSignal
  53. __all__ = (
  54. "Causality",
  55. "pairwise_granger",
  56. "conditional_granger"
  57. )
  58. # the return type of pairwise_granger() function
  59. Causality = namedtuple('Causality',
  60. ['directional_causality_x_y',
  61. 'directional_causality_y_x',
  62. 'instantaneous_causality',
  63. 'total_interdependence'])
  64. def _bic(cov, order, dimension, length):
  65. """
  66. Calculate Bayesian Information Criterion
  67. Parameters
  68. ----------
  69. cov : np.ndarray
  70. covariance matrix of auto regressive model
  71. order : int
  72. order of autoregressive model
  73. dimension : int
  74. dimensionality of the data
  75. length : int
  76. number of time samples
  77. Returns
  78. -------
  79. criterion : float
  80. Bayesian Information Criterion
  81. """
  82. sign, log_det_cov = np.linalg.slogdet(cov)
  83. criterion = 2 * log_det_cov \
  84. + 2*(dimension**2)*order*np.log(length)/length
  85. return criterion
  86. def _aic(cov, order, dimension, length):
  87. """
  88. Calculate Akaike Information Criterion
  89. Parameters
  90. ----------
  91. cov : np.ndarray
  92. covariance matrix of auto regressive model
  93. order : int
  94. order of autoregressive model
  95. dimension : int
  96. dimensionality of the data
  97. length : int
  98. number of time samples
  99. Returns
  100. -------
  101. criterion : float
  102. Akaike Information Criterion
  103. """
  104. sign, log_det_cov = np.linalg.slogdet(cov)
  105. criterion = 2 * log_det_cov \
  106. + 2*(dimension**2)*order/length
  107. return criterion
  108. def _lag_covariances(signals, dimension, max_lag):
  109. r"""
  110. Determine covariances of time series and time shift of itself up to a
  111. maximal lag
  112. Parameters
  113. ----------
  114. signals: np.ndarray
  115. time series data
  116. dimension : int
  117. number of time series
  118. max_lag: int
  119. maximal time lag to be considered
  120. Returns
  121. -------
  122. lag_corr : np.ndarray
  123. correlations matrices of lagged signals
  124. Covariance of shifted signals calculated according to the following
  125. formula:
  126. x: d-dimensional signal
  127. x^T: transpose of d-dimensional signal
  128. N: number of time points
  129. \tau: lag
  130. C(\tau) = \sum_{i=0}^{N-\tau} x[i]*x^T[\tau+i]
  131. """
  132. length = np.size(signals[0])
  133. if length < max_lag:
  134. raise ValueError("Maximum lag larger than size of data")
  135. # centralize time series
  136. signals_mean = (signals - np.mean(signals, keepdims=True)).T
  137. lag_covariances = np.zeros((max_lag+1, dimension, dimension))
  138. # determine lagged covariance for different time lags
  139. for lag in range(0, max_lag+1):
  140. lag_covariances[lag] = \
  141. np.mean(np.einsum('ij,ik -> ijk', signals_mean[:length-lag],
  142. signals_mean[lag:]), axis=0)
  143. return lag_covariances
  144. def _yule_walker_matrix(data, dimension, order):
  145. r"""
  146. Generate matrix for Yule-Walker equation
  147. Parameters
  148. ----------
  149. data : np.ndarray
  150. correlation of data shifted with lags up to order
  151. dimension : int
  152. dimensionality of data (e.g. number of channels)
  153. order : int
  154. order of the autoregressive model
  155. Returns
  156. -------
  157. yule_walker_matrix : np.ndarray
  158. matrix in Yule-Walker equation
  159. Yule-Walker Matrix M is a block-structured symmetric matrix with
  160. dimension (d \cdot p)\times(d \cdot p)
  161. where
  162. d: dimension of signal
  163. p: order of autoregressive model
  164. C(\tau): time-shifted covariances \tau -> d \times d matrix
  165. The blocks of size (d \times d) are set as follows:
  166. M_ij = C(j-i)^T
  167. where 1 \leq i \leq j \leq p. The other entries are determined by
  168. symmetry.
  169. lag_covariances : np.ndarray
  170. """
  171. lag_covariances = _lag_covariances(data, dimension, order)
  172. yule_walker_matrix = np.zeros((dimension*order, dimension*order))
  173. for block_row in range(order):
  174. for block_column in range(block_row, order):
  175. yule_walker_matrix[block_row*dimension: (block_row+1)*dimension,
  176. block_column*dimension:
  177. (block_column+1)*dimension] = \
  178. lag_covariances[block_column-block_row].T
  179. yule_walker_matrix[block_column*dimension:
  180. (block_column+1)*dimension,
  181. block_row*dimension:
  182. (block_row+1)*dimension] = \
  183. lag_covariances[block_column-block_row]
  184. return yule_walker_matrix, lag_covariances
  185. def _vector_arm(signals, dimension, order):
  186. r"""
  187. Determine coefficients of autoregressive model from time series data.
  188. Coefficients of autoregressive model calculated via solving the linear
  189. equation
  190. M A = C
  191. where
  192. M: Yule-Waler Matrix
  193. A: Coefficients of autoregressive model
  194. C: Time-shifted covariances with positive lags
  195. Covariance matrix C_0 is then given by
  196. C_0 = C[0] - \sum_{i=0}^{p-1} A[i]C[i+1]
  197. where p is the orde of the autoregressive model.
  198. Parameters
  199. ----------
  200. signals : np.ndarray
  201. time series data
  202. order : int
  203. order of the autoregressive model
  204. Returns
  205. -------
  206. coeffs: np.ndarray
  207. coefficients of the autoregressive model
  208. ry
  209. covar_mat : np.ndarray
  210. covariance matrix of
  211. """
  212. yule_walker_matrix, lag_covariances = \
  213. _yule_walker_matrix(signals, dimension, order)
  214. positive_lag_covariances = np.reshape(lag_covariances[1:],
  215. (dimension*order, dimension))
  216. lstsq_coeffs = \
  217. np.linalg.lstsq(yule_walker_matrix, positive_lag_covariances)[0]
  218. coeffs = []
  219. for index in range(order):
  220. coeffs.append(lstsq_coeffs[index*dimension:(index+1)*dimension, ].T)
  221. coeffs = np.stack(coeffs)
  222. cov_matrix = np.copy(lag_covariances[0])
  223. for i in range(order):
  224. cov_matrix -= np.matmul(coeffs[i], lag_covariances[i+1])
  225. return coeffs, cov_matrix
  226. def _optimal_vector_arm(signals, dimension, max_order,
  227. information_criterion='aic'):
  228. """
  229. Determine optimal auto regressive model by choosing optimal order via
  230. Information Criterion
  231. Parameters
  232. ----------
  233. signals : np.ndarray
  234. time series data
  235. dimension : int
  236. dimensionality of the data
  237. max_order : int
  238. maximal order to consider
  239. information_criterion : str
  240. A function to compute the information criterion:
  241. `bic` for Bayesian information_criterion,
  242. `aic` for Akaike information criterion
  243. Default: aic
  244. Returns
  245. -------
  246. optimal_coeffs: np.ndarray
  247. coefficients of the autoregressive model
  248. optimal_cov_mat : np.ndarray
  249. covariance matrix of
  250. optimal_order : int
  251. optimal order
  252. """
  253. length = np.size(signals[0])
  254. optimal_ic = np.infty
  255. optimal_order = 1
  256. optimal_coeffs = np.zeros((dimension, dimension, optimal_order))
  257. optimal_cov_matrix = np.zeros((dimension, dimension))
  258. for order in range(1, max_order + 1):
  259. coeffs, cov_matrix = _vector_arm(signals, dimension, order)
  260. if information_criterion == 'aic':
  261. temp_ic = _aic(cov_matrix, order, dimension, length)
  262. elif information_criterion == 'bic':
  263. temp_ic = _bic(cov_matrix, order, dimension, length)
  264. else:
  265. raise ValueError("The specified information criterion is not"
  266. "available. Please use 'aic' or 'bic'.")
  267. if temp_ic < optimal_ic:
  268. optimal_ic = temp_ic
  269. optimal_order = order
  270. optimal_coeffs = coeffs
  271. optimal_cov_matrix = cov_matrix
  272. return optimal_coeffs, optimal_cov_matrix, optimal_order
  273. def pairwise_granger(signals, max_order, information_criterion='aic'):
  274. r"""
  275. Determine Granger Causality of two time series
  276. Parameters
  277. ----------
  278. signals : (N, 2) np.ndarray or neo.AnalogSignal
  279. A matrix with two time series (second dimension) that have N time
  280. points (first dimension).
  281. max_order : int
  282. Maximal order of autoregressive model.
  283. information_criterion : {'aic', 'bic'}, optional
  284. A function to compute the information criterion:
  285. `bic` for Bayesian information_criterion,
  286. `aic` for Akaike information criterion,
  287. Default: 'aic'.
  288. Returns
  289. -------
  290. Causality
  291. A `namedtuple` with the following attributes:
  292. directional_causality_x_y : float
  293. The Granger causality value for X influence onto Y.
  294. directional_causality_y_x : float
  295. The Granger causality value for Y influence onto X.
  296. instantaneous_causality : float
  297. The remaining channel interdependence not accounted for by
  298. the directional causalities (e.g. shared input to X and Y).
  299. total_interdependence : float
  300. The sum of the former three metrics. It measures the dependence
  301. of X and Y. If the total interdependence is positive, X and Y
  302. are not independent.
  303. Denote covariance matrix of signals
  304. X by C|X - a real number
  305. Y by C|Y - a real number
  306. (X,Y) by C|XY - a (2 \times 2) matrix
  307. directional causality X -> Y given by
  308. log(C|X / C|XY_00)
  309. directional causality Y -> X given by
  310. log(C|Y / C|XY_11)
  311. instantaneous causality of X,Y given by
  312. log(C|XY_00 / C|XY_11)
  313. total interdependence of X,Y given by
  314. log( {C|X \cdot C|Y} / det{C|XY} )
  315. Raises
  316. ------
  317. ValueError
  318. If the provided signal does not have a shape of Nx2.
  319. If the determinant of the prediction error covariance matrix is not
  320. positive.
  321. Warns
  322. -----
  323. UserWarning
  324. If the log determinant of the prediction error covariance matrix is
  325. below the tolerance level of 1e-7.
  326. Notes
  327. -----
  328. The formulas used in this implementation follows
  329. :cite:`granger-Ding06_0608035`. The only difference being that we change
  330. the equation 47 in the following way:
  331. -R(k) - A(1)R(k - 1) - ... - A(m)R(k - m) = 0.
  332. This forumlation allows for the usage of R values without transposition
  333. (i.e. directly) in equation 48.
  334. Examples
  335. --------
  336. Example 1. Independent variables.
  337. >>> import numpy as np
  338. >>> from elephant.causality.granger import pairwise_granger
  339. >>> pairwise_granger(np.random.uniform(size=(1000, 2)), max_order=2)
  340. Causality(directional_causality_x_y=0.0,
  341. directional_causality_y_x=-0.0,
  342. instantaneous_causality=0.0,
  343. total_interdependence=0.0)
  344. Example 2. Dependent variables. Y depends on X but not vice versa.
  345. .. math::
  346. \begin{array}{ll}
  347. X_t \sim \mathcal{N}(0, 1) \\
  348. Y_t = 3.5 \cdot X_{t-1} + \epsilon, \;
  349. \epsilon \sim\mathcal{N}(0, 1)
  350. \end{array}
  351. In this case, the directional causality is non-zero.
  352. >>> x = np.random.randn(1001)
  353. >>> y = 3.5 * x[:-1] + np.random.randn(1000)
  354. >>> signals = np.array([x[1:], y]).T # N x 2 matrix
  355. >>> pairwise_granger(signals, max_order=1)
  356. Causality(directional_causality_x_y=2.64,
  357. directional_causality_y_x=0.0,
  358. instantaneous_causality=0.0,
  359. total_interdependence=2.64)
  360. """
  361. if isinstance(signals, AnalogSignal):
  362. signals = signals.magnitude
  363. if not (signals.ndim == 2 and signals.shape[1] == 2):
  364. raise ValueError("The input 'signals' must be of dimensions Nx2.")
  365. # transpose (N,2) -> (2,N) for mathematical convenience
  366. signals = signals.T
  367. # signal_x and signal_y are (1, N) arrays
  368. signal_x, signal_y = np.expand_dims(signals, axis=1)
  369. coeffs_x, var_x, p_1 = _optimal_vector_arm(signal_x, 1, max_order,
  370. information_criterion)
  371. coeffs_y, var_y, p_2 = _optimal_vector_arm(signal_y, 1, max_order,
  372. information_criterion)
  373. coeffs_xy, cov_xy, p_3 = _optimal_vector_arm(signals, 2, max_order,
  374. information_criterion)
  375. sign, log_det_cov = np.linalg.slogdet(cov_xy)
  376. tolerance = 1e-7
  377. if sign <= 0:
  378. raise ValueError(
  379. "Determinant of covariance matrix must be always positive: "
  380. "In this case its sign is {}".format(sign))
  381. if log_det_cov <= tolerance:
  382. warnings.warn("The value of the log determinant is at or below the "
  383. "tolerance level. Proceeding with computation.",
  384. UserWarning)
  385. directional_causality_y_x = np.log(var_x[0]) - np.log(cov_xy[0, 0])
  386. directional_causality_x_y = np.log(var_y[0]) - np.log(cov_xy[1, 1])
  387. instantaneous_causality = \
  388. np.log(cov_xy[0, 0]) + np.log(cov_xy[1, 1]) - log_det_cov
  389. instantaneous_causality = np.asarray(instantaneous_causality)
  390. total_interdependence = np.log(var_x[0]) + np.log(var_y[0]) - log_det_cov
  391. # Round GC according to following scheme:
  392. # Note that standard error scales as 1/sqrt(sample_size)
  393. # Calculate significant figures according to standard error
  394. length = np.size(signal_x)
  395. asymptotic_std_error = 1/np.sqrt(length)
  396. est_sig_figures = int((-1)*np.around(np.log10(asymptotic_std_error)))
  397. directional_causality_x_y_round = np.around(directional_causality_x_y,
  398. est_sig_figures)
  399. directional_causality_y_x_round = np.around(directional_causality_y_x,
  400. est_sig_figures)
  401. instantaneous_causality_round = np.around(instantaneous_causality,
  402. est_sig_figures)
  403. total_interdependence_round = np.around(total_interdependence,
  404. est_sig_figures)
  405. return Causality(
  406. directional_causality_x_y=directional_causality_x_y_round.item(),
  407. directional_causality_y_x=directional_causality_y_x_round.item(),
  408. instantaneous_causality=instantaneous_causality_round.item(),
  409. total_interdependence=total_interdependence_round.item())
  410. def conditional_granger(signals, max_order, information_criterion='aic'):
  411. r"""
  412. Determine conditional Granger Causality of the second time series on the
  413. first time series, given the third time series. In other words, for time
  414. series X_t, Y_t and Z_t, this function tests if Y_t influences X_t via Z_t.
  415. Parameters
  416. ----------
  417. signals : (N, 3) np.ndarray or neo.AnalogSignal
  418. A matrix with three time series (second dimension) that have N time
  419. points (first dimension). The time series to be conditioned on is the
  420. third.
  421. max_order : int
  422. Maximal order of autoregressive model.
  423. information_criterion : {'aic', 'bic'}, optional
  424. A function to compute the information criterion:
  425. `bic` for Bayesian information_criterion,
  426. `aic` for Akaike information criterion,
  427. Default: 'aic'.
  428. Returns
  429. -------
  430. conditional_causality_xy_z_round : float
  431. The value of conditional causality of Y_t on X_t given Z_t. Zero value
  432. indicates that causality of Y_t on X_t is solely dependent on Z_t.
  433. Raises
  434. ------
  435. ValueError
  436. If the provided signal does not have a shape of Nx3.
  437. Notes
  438. -----
  439. The formulas used in this implementation follows
  440. :cite:`granger-Ding06_0608035`. Specifically, the Eq 35.
  441. """
  442. if isinstance(signals, AnalogSignal):
  443. signals = signals.magnitude
  444. if not (signals.ndim == 2 and signals.shape[1] == 3):
  445. raise ValueError("The input 'signals' must be of dimensions Nx3.")
  446. # transpose (N,3) -> (3,N) for mathematical convenience
  447. signals = signals.T
  448. # signal_x, signal_y and signal_z are (1, N) arrays
  449. signal_x, signal_y, signal_z = np.expand_dims(signals, axis=1)
  450. signals_xz = np.vstack([signal_x, signal_z])
  451. coeffs_xz, cov_xz, p_1 = _optimal_vector_arm(
  452. signals_xz, dimension=2, max_order=max_order,
  453. information_criterion=information_criterion)
  454. coeffs_xyz, cov_xyz, p_2 = _optimal_vector_arm(
  455. signals, dimension=3, max_order=max_order,
  456. information_criterion=information_criterion)
  457. conditional_causality_xy_z = np.log(cov_xz[0, 0]) - np.log(cov_xyz[0, 0])
  458. # Round conditional GC according to following scheme:
  459. # Note that standard error scales as 1/sqrt(sample_size)
  460. # Calculate significant figures according to standard error
  461. length = np.size(signal_x)
  462. asymptotic_std_error = 1/np.sqrt(length)
  463. est_sig_figures = int((-1)*np.around(np.log10(asymptotic_std_error)))
  464. conditional_causality_xy_z_round = np.around(conditional_causality_xy_z,
  465. est_sig_figures)
  466. return conditional_causality_xy_z_round