tigramite-fast 5.2.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. tigramite/__init__.py +0 -0
  2. tigramite/causal_effects.py +1525 -0
  3. tigramite/causal_mediation.py +1592 -0
  4. tigramite/data_processing.py +1574 -0
  5. tigramite/graphs.py +1509 -0
  6. tigramite/independence_tests/LBFGS.py +1114 -0
  7. tigramite/independence_tests/__init__.py +0 -0
  8. tigramite/independence_tests/cmiknn.py +661 -0
  9. tigramite/independence_tests/cmiknn_mixed.py +1397 -0
  10. tigramite/independence_tests/cmisymb.py +286 -0
  11. tigramite/independence_tests/gpdc.py +664 -0
  12. tigramite/independence_tests/gpdc_torch.py +820 -0
  13. tigramite/independence_tests/gsquared.py +190 -0
  14. tigramite/independence_tests/independence_tests_base.py +1310 -0
  15. tigramite/independence_tests/oracle_conditional_independence.py +1582 -0
  16. tigramite/independence_tests/pairwise_CI.py +383 -0
  17. tigramite/independence_tests/parcorr.py +369 -0
  18. tigramite/independence_tests/parcorr_mult.py +485 -0
  19. tigramite/independence_tests/parcorr_wls.py +451 -0
  20. tigramite/independence_tests/regressionCI.py +403 -0
  21. tigramite/independence_tests/robust_parcorr.py +403 -0
  22. tigramite/jpcmciplus.py +966 -0
  23. tigramite/lpcmci.py +3649 -0
  24. tigramite/models.py +2257 -0
  25. tigramite/pcmci.py +3935 -0
  26. tigramite/pcmci_base.py +1218 -0
  27. tigramite/plotting.py +4735 -0
  28. tigramite/rpcmci.py +467 -0
  29. tigramite/toymodels/__init__.py +0 -0
  30. tigramite/toymodels/context_model.py +261 -0
  31. tigramite/toymodels/non_additive.py +1231 -0
  32. tigramite/toymodels/structural_causal_processes.py +1201 -0
  33. tigramite/toymodels/surrogate_generator.py +319 -0
  34. tigramite_fast-5.2.10.1.dist-info/METADATA +182 -0
  35. tigramite_fast-5.2.10.1.dist-info/RECORD +38 -0
  36. tigramite_fast-5.2.10.1.dist-info/WHEEL +5 -0
  37. tigramite_fast-5.2.10.1.dist-info/licenses/license.txt +621 -0
  38. tigramite_fast-5.2.10.1.dist-info/top_level.txt +1 -0
tigramite/models.py ADDED
@@ -0,0 +1,2257 @@
1
+ """Tigramite causal inference for time series."""
2
+
3
+ # Author: Jakob Runge <jakob@jakob-runge.com>
4
+ #
5
+ # License: GNU General Public License v3.0
6
+
7
+ from __future__ import print_function
8
+ from copy import deepcopy
9
+ import json, warnings, os, pathlib
10
+ import numpy as np
11
+ import sklearn
12
+ import sklearn.linear_model
13
+ import networkx
14
+ from tigramite.data_processing import DataFrame
15
+ from tigramite.pcmci import PCMCI
16
+
17
+ class Models():
18
+ """Base class for time series models.
19
+
20
+ Allows to fit any model from sklearn to the parents of a target variable.
21
+ Also takes care of missing values, masking and preprocessing. If the
22
+ target variable is multivariate, a model that supports multi-output
23
+ regression must be used. Note that
24
+ sklearn.multioutput.MultiOutputRegressor allows to extend single-output
25
+ models.
26
+
27
+ Parameters
28
+ ----------
29
+ dataframe : data object
30
+ Tigramite dataframe object. It must have the attributes dataframe.values
31
+ yielding a numpy array of shape (observations T, variables N) and
32
+ optionally a mask of the same shape and a missing values flag.
33
+ model : sklearn model object
34
+ For example, sklearn.linear_model.LinearRegression() for a linear
35
+ regression model.
36
+ conditional_model : sklearn model object, optional (default: None)
37
+ Used to fit conditional causal effects in nested regression.
38
+ If None, model is used.
39
+ data_transform : sklearn preprocessing object, optional (default: None)
40
+ Used to transform data prior to fitting. For example,
41
+ sklearn.preprocessing.StandardScaler for simple standardization. The
42
+ fitted parameters are stored. Note that the inverse_transform is then
43
+ applied to the predicted data.
44
+ mask_type : {None, 'y','x','z','xy','xz','yz','xyz'}
45
+ Masking mode: Indicators for which variables in the dependence
46
+ measure I(X; Y | Z) the samples should be masked. If None, the mask
47
+ is not used. Explained in tutorial on masking and missing values.
48
+ verbosity : int, optional (default: 0)
49
+ Level of verbosity.
50
+ """
51
+
52
+ def __init__(self,
53
+ dataframe,
54
+ model,
55
+ conditional_model=None,
56
+ data_transform=None,
57
+ mask_type=None,
58
+ verbosity=0):
59
+ # Set the mask type and dataframe object
60
+ self.mask_type = mask_type
61
+ self.dataframe = dataframe
62
+ # Get the number of nodes and length for this dataset
63
+ self.N = self.dataframe.N
64
+ self.T = self.dataframe.T
65
+ # Set the model to be used
66
+ self.model = model
67
+ if conditional_model is None:
68
+ self.conditional_model = model
69
+ else:
70
+ self.conditional_model = conditional_model
71
+ # Set the data_transform object and verbosity
72
+ self.data_transform = data_transform
73
+ self.verbosity = verbosity
74
+ # Initialize the object that will be set later
75
+ self.all_parents = None
76
+ self.selected_variables = None
77
+ self.tau_max = None
78
+ self.fit_results = None
79
+
80
+ # @profile
81
+ def get_general_fitted_model(self,
82
+ Y, X, Z=None,
83
+ conditions=None,
84
+ tau_max=None,
85
+ cut_off='max_lag_or_tau_max',
86
+ empty_predictors_function=np.mean,
87
+ return_data=False):
88
+ """Fit time series model.
89
+
90
+ For each variable in selected_variables, the sklearn model is fitted
91
+ with :math:`y` given by the target variable(s), and :math:`X` given by its
92
+ parents. The fitted model class is returned for later use.
93
+
94
+ Parameters
95
+ ----------
96
+ X, Y, Z : lists of tuples
97
+ List of variables for estimating model Y = f(X,Z)
98
+ conditions : list of tuples.
99
+ Conditions for estimating conditional causal effects.
100
+ tau_max : int, optional (default: None)
101
+ Maximum time lag. If None, the maximum lag in all_parents is used.
102
+ cut_off : {'max_lag_or_tau_max', '2xtau_max', 'max_lag'}
103
+ How many samples to cutoff at the beginning. The default is
104
+ 'max_lag_or_tau_max', which uses the maximum of tau_max and the
105
+ conditions. This is useful to compare multiple models on the same
106
+ sample. Other options are '2xtau_max', which guarantees that MCI
107
+ tests are all conducted on the same samples. Last, 'max_lag' uses
108
+ as much samples as possible.
109
+ empty_predictors_function : function
110
+ Function to apply to y if no predictors are given.
111
+ return_data : bool, optional (default: False)
112
+ Whether to save the data array.
113
+
114
+ Returns
115
+ -------
116
+ fit_results : dictionary of sklearn model objects
117
+ Returns the sklearn model after fitting. Also returns the data
118
+ transformation parameters.
119
+ """
120
+
121
+ def get_vectorized_length(W):
122
+ return sum([len(self.dataframe.vector_vars[w[0]]) for w in W])
123
+
124
+ self.X = X
125
+ self.Y = Y
126
+
127
+ if conditions is None:
128
+ conditions = []
129
+ self.conditions = conditions
130
+
131
+ if Z is not None:
132
+ Z = [z for z in Z if z not in conditions]
133
+
134
+ self.Z = Z
135
+
136
+ # lenX = len(self.X)
137
+ # lenS = len(self.conditions)
138
+ self.lenX = get_vectorized_length(self.X)
139
+ self.lenS = get_vectorized_length(self.conditions)
140
+
141
+ self.cut_off = cut_off
142
+
143
+ # Find the maximal conditions lag
144
+ max_lag = 0
145
+ for y in self.Y:
146
+ this_lag = np.abs(np.array(self.X + self.Z + self.conditions)[:, 1]).max()
147
+ max_lag = max(max_lag, this_lag)
148
+ # Set the default tau max and check if it should be overwritten
149
+ if tau_max is None:
150
+ self.tau_max = max_lag
151
+ else:
152
+ self.tau_max = tau_max
153
+ if self.tau_max < max_lag:
154
+ raise ValueError("tau_max = %d, but must be at least "
155
+ " max_lag = %d"
156
+ "" % (self.tau_max, max_lag))
157
+
158
+ # Construct array of shape (var, time)
159
+ array, xyz, _ = \
160
+ self.dataframe.construct_array(X=self.X, Y=self.Y,
161
+ Z=self.conditions,
162
+ extraZ=self.Z,
163
+ tau_max=self.tau_max,
164
+ mask_type=self.mask_type,
165
+ cut_off=self.cut_off,
166
+ remove_overlaps=True,
167
+ verbosity=self.verbosity)
168
+
169
+ # Transform the data if needed
170
+ self.fitted_data_transform = None
171
+ if self.data_transform is not None:
172
+ # Fit only X, Y, and S for later use in transforming input
173
+ X_transform = deepcopy(self.data_transform)
174
+ x_indices = list(np.where(xyz==0)[0])
175
+ X_transform.fit(array[x_indices, :].T)
176
+ self.fitted_data_transform = {'X': X_transform}
177
+ Y_transform = deepcopy(self.data_transform)
178
+ y_indices = list(np.where(xyz==1)[0])
179
+ Y_transform.fit(array[y_indices, :].T)
180
+ self.fitted_data_transform['Y'] = Y_transform
181
+ if len(self.conditions) > 0:
182
+ S_transform = deepcopy(self.data_transform)
183
+ s_indices = list(np.where(xyz==2)[0])
184
+ S_transform.fit(array[s_indices, :].T)
185
+ self.fitted_data_transform['S'] = S_transform
186
+
187
+ # Now transform whole array
188
+ # TODO: Rather concatenate transformed arrays
189
+ all_transform = deepcopy(self.data_transform)
190
+ array = all_transform.fit_transform(X=array.T).T
191
+
192
+ # Fit the model
193
+ # Copy and fit the model
194
+ a_model = deepcopy(self.model)
195
+
196
+ predictor_indices = list(np.where(xyz==0)[0]) \
197
+ + list(np.where(xyz==3)[0]) \
198
+ + list(np.where(xyz==2)[0])
199
+ predictor_array = array[predictor_indices, :].T
200
+ target_array = array[np.where(xyz==1)[0], :].T
201
+
202
+ if predictor_array.size == 0:
203
+ # Just fit default (eg, mean)
204
+ class EmptyPredictorModel:
205
+ def fit(self, X, y):
206
+ if y.ndim == 1:
207
+ self.result = empty_predictors_function(y)
208
+ else:
209
+ self.result = empty_predictors_function(y, axis=0)
210
+ def predict(self, X):
211
+ return self.result
212
+ a_model = EmptyPredictorModel()
213
+
214
+ a_model.fit(X=predictor_array, y=target_array)
215
+
216
+ # Cache the results
217
+ fit_results = {}
218
+ fit_results['observation_array'] = array
219
+ fit_results['xyz'] = xyz
220
+ fit_results['model'] = a_model
221
+ # Cache the data transform
222
+ fit_results['fitted_data_transform'] = self.fitted_data_transform
223
+
224
+ # Cache and return the fit results
225
+ self.fit_results = fit_results
226
+ return fit_results
227
+
228
+ # @profile
229
+ def get_general_prediction(self,
230
+ intervention_data,
231
+ conditions_data=None,
232
+ pred_params=None,
233
+ transform_interventions_and_prediction=False,
234
+ return_further_pred_results=False,
235
+ aggregation_func=np.mean,
236
+ intervention_type='hard',
237
+ ):
238
+ r"""Predict effect of intervention with fitted model.
239
+
240
+ Uses the model.predict() function of the sklearn model.
241
+
242
+ Parameters
243
+ ----------
244
+ intervention_data : numpy array
245
+ Numpy array of shape (n_interventions, len(X)) that contains the do(X) values.
246
+ conditions_data : data object, optional
247
+ Numpy array of shape (n_interventions, len(S)) that contains the S=s values.
248
+ pred_params : dict, optional
249
+ Optional parameters passed on to sklearn prediction function (model and
250
+ conditional_model).
251
+ transform_interventions_and_prediction : bool (default: False)
252
+ Whether to perform the inverse data_transform on prediction results.
253
+ return_further_pred_results : bool, optional (default: False)
254
+ In case the predictor class returns more than just the expected value,
255
+ the entire results can be returned.
256
+ aggregation_func : callable
257
+ Callable applied to output of 'predict'. Default is 'np.mean'.
258
+ intervention_type : {'hard', 'soft'}
259
+ Specify whether intervention is 'hard' (set value) or 'soft'
260
+ (add value to observed data).
261
+
262
+ Returns
263
+ -------
264
+ Results from prediction.
265
+ """
266
+
267
+ n_interventions, _ = intervention_data.shape
268
+
269
+ if intervention_data.shape[1] != self.lenX:
270
+ raise ValueError("intervention_data.shape[1] must be len(X).")
271
+
272
+ if conditions_data is not None:
273
+ if conditions_data.shape[1] != len(self.conditions):
274
+ raise ValueError("conditions_data.shape[1] must be len(S).")
275
+ if conditions_data.shape[0] != intervention_data.shape[0]:
276
+ raise ValueError("conditions_data.shape[0] must match intervention_data.shape[0].")
277
+
278
+ # Print message
279
+ if self.verbosity > 1:
280
+ print("\n## Predicting target %s" % str(self.Y))
281
+ if pred_params is not None:
282
+ for key in list(pred_params):
283
+ print("%s = %s" % (key, pred_params[key]))
284
+
285
+ # Default value for pred_params
286
+ if pred_params is None:
287
+ pred_params = {}
288
+
289
+ # Check the model is fitted.
290
+ if self.fit_results is None:
291
+ raise ValueError("Model not yet fitted.")
292
+
293
+ # Transform the data if needed
294
+ fitted_data_transform = self.fit_results['fitted_data_transform']
295
+ if transform_interventions_and_prediction and fitted_data_transform is not None:
296
+ intervention_data = fitted_data_transform['X'].transform(X=intervention_data)
297
+ if self.conditions is not None and conditions_data is not None:
298
+ conditions_data = fitted_data_transform['S'].transform(X=conditions_data)
299
+
300
+ # Extract observational Z from stored array
301
+ z_indices = list(np.where(self.fit_results['xyz']==3)[0])
302
+ z_array = self.fit_results['observation_array'][z_indices, :].T
303
+ Tobs = len(self.fit_results['observation_array'].T)
304
+
305
+ if intervention_type == 'soft':
306
+ x_indices = list(np.where(self.fit_results['xyz']==0)[0])
307
+ x_array = self.fit_results['observation_array'][x_indices, :].T
308
+
309
+ if self.conditions is not None and conditions_data is not None:
310
+ s_indices = list(np.where(self.fit_results['xyz']==2)[0])
311
+ s_array = self.fit_results['observation_array'][s_indices, :].T
312
+
313
+ pred_dict = {}
314
+
315
+ # Now iterate through interventions (and potentially S)
316
+ for index, dox_vals in enumerate(intervention_data):
317
+ # Construct XZS-array
318
+ intervention_array = dox_vals.reshape(1, self.lenX) * np.ones((Tobs, self.lenX))
319
+ if intervention_type == 'soft':
320
+ intervention_array += x_array
321
+
322
+ predictor_array = intervention_array
323
+
324
+ if len(self.Z) > 0:
325
+ predictor_array = np.hstack((predictor_array, z_array))
326
+
327
+ if self.conditions is not None and conditions_data is not None:
328
+ conditions_array = conditions_data[index].reshape(1, self.lenS) * np.ones((Tobs, self.lenS))
329
+ predictor_array = np.hstack((predictor_array, conditions_array))
330
+
331
+ predicted_vals = self.fit_results['model'].predict(
332
+ X=predictor_array, **pred_params)
333
+
334
+ if self.conditions is not None and conditions_data is not None:
335
+
336
+ a_conditional_model = deepcopy(self.conditional_model)
337
+
338
+ if type(predicted_vals) is tuple:
339
+ predicted_vals_here = predicted_vals[0]
340
+ else:
341
+ predicted_vals_here = predicted_vals
342
+
343
+ a_conditional_model.fit(X=s_array, y=predicted_vals_here)
344
+ self.fit_results['conditional_model'] = a_conditional_model
345
+
346
+ predicted_vals = a_conditional_model.predict(
347
+ X=conditions_data[index].reshape(1, self.lenS), **pred_params) # was conditions_data before
348
+
349
+ if transform_interventions_and_prediction and fitted_data_transform is not None:
350
+ predicted_vals = fitted_data_transform['Y'].inverse_transform(X=predicted_vals).squeeze()
351
+
352
+ pred_dict[index] = predicted_vals
353
+
354
+ # Apply aggregation function
355
+ if type(predicted_vals) is tuple:
356
+ aggregated_pred = aggregation_func(predicted_vals[0], axis=0)
357
+ else:
358
+ aggregated_pred = aggregation_func(predicted_vals, axis=0)
359
+
360
+ aggregated_pred = aggregated_pred.squeeze()
361
+
362
+ if index == 0:
363
+ predicted_array = np.zeros((n_interventions, ) + aggregated_pred.shape,
364
+ dtype=aggregated_pred.dtype)
365
+
366
+ predicted_array[index] = aggregated_pred
367
+
368
+ # if fitted_data_transform is not None:
369
+ # rescaled = fitted_data_transform['Y'].inverse_transform(X=predicted_array[index, iy].reshape(-1, 1))
370
+ # predicted_array[index, iy] = rescaled.squeeze()
371
+
372
+ if return_further_pred_results:
373
+ return predicted_array, pred_dict
374
+ else:
375
+ return predicted_array
376
+
377
+
378
+ def fit_full_model(self, all_parents,
379
+ selected_variables=None,
380
+ tau_max=None,
381
+ cut_off='max_lag_or_tau_max',
382
+ empty_predictors_function=np.mean,
383
+ return_data=False):
384
+ """Fit time series model.
385
+
386
+ For each variable in selected_variables, the sklearn model is fitted
387
+ with :math:`y` given by the target variable, and :math:`X` given by its
388
+ parents. The fitted model class is returned for later use.
389
+
390
+ Parameters
391
+ ----------
392
+ all_parents : dictionary
393
+ Dictionary of form {0:[(0, -1), (3, 0), ...], 1:[], ...} containing
394
+ the parents estimated with PCMCI.
395
+ selected_variables : list of integers, optional (default: range(N))
396
+ Specify to estimate parents only for selected variables. If None is
397
+ passed, parents are estimated for all variables.
398
+ tau_max : int, optional (default: None)
399
+ Maximum time lag. If None, the maximum lag in all_parents is used.
400
+ cut_off : {'max_lag_or_tau_max', '2xtau_max', 'max_lag'}
401
+ How many samples to cutoff at the beginning. The default is
402
+ 'max_lag_or_tau_max', which uses the maximum of tau_max and the
403
+ conditions. This is useful to compare multiple models on the same
404
+ sample. Other options are '2xtau_max', which guarantees that MCI
405
+ tests are all conducted on the same samples. Last, 'max_lag' uses
406
+ as much samples as possible.
407
+ empty_predictors_function : function
408
+ Function to apply to y if no predictors are given.
409
+ return_data : bool, optional (default: False)
410
+ Whether to save the data array.
411
+
412
+ Returns
413
+ -------
414
+ fit_results : dictionary of sklearn model objects for each variable
415
+ Returns the sklearn model after fitting. Also returns the data
416
+ transformation parameters.
417
+ """
418
+ # Initialize the fit by setting the instance's all_parents attribute
419
+ self.all_parents = all_parents
420
+ # Set the default selected variables to all variables and check if this
421
+ # should be overwritten
422
+ self.selected_variables = range(self.N)
423
+ if selected_variables is not None:
424
+ self.selected_variables = selected_variables
425
+ # Find the maximal parents lag
426
+ max_parents_lag = 0
427
+ for j in self.selected_variables:
428
+ if all_parents[j]:
429
+ this_parent_lag = np.abs(np.array(all_parents[j])[:, 1]).max()
430
+ max_parents_lag = max(max_parents_lag, this_parent_lag)
431
+ # Set the default tau_max and check if it should be overwritten
432
+ self.tau_max = max_parents_lag
433
+ if tau_max is not None:
434
+ self.tau_max = tau_max
435
+ if self.tau_max < max_parents_lag:
436
+ raise ValueError("tau_max = %d, but must be at least "
437
+ " max_parents_lag = %d"
438
+ "" % (self.tau_max, max_parents_lag))
439
+ # Initialize the fit results
440
+ fit_results = {}
441
+ for j in self.selected_variables:
442
+ Y = [(j, 0)]
443
+ X = [(j, 0)] # dummy
444
+ Z = self.all_parents[j]
445
+ array, xyz, _ = \
446
+ self.dataframe.construct_array(X, Y, Z,
447
+ tau_max=self.tau_max,
448
+ mask_type=self.mask_type,
449
+ cut_off=cut_off,
450
+ remove_overlaps=True,
451
+ verbosity=self.verbosity)
452
+ # Get the dimensions out of the constructed array
453
+ dim, T = array.shape
454
+ dim_z = dim - 2
455
+ # Transform the data if needed
456
+ if self.data_transform is not None:
457
+ array = self.data_transform.fit_transform(X=array.T).T
458
+ # Cache the results
459
+ fit_results[j] = {}
460
+ # Cache the data transform
461
+ fit_results[j]['data_transform'] = deepcopy(self.data_transform)
462
+
463
+ if return_data:
464
+ # Cache the data if needed
465
+ fit_results[j]['data'] = array
466
+ fit_results[j]['used_indices'] = self.dataframe.use_indices_dataset_dict
467
+ # Copy and fit the model if there are any parents for this variable to fit
468
+ a_model = deepcopy(self.model)
469
+ if dim_z > 0:
470
+ a_model.fit(X=array[2:].T, y=array[1])
471
+ else:
472
+ # Just fit default (eg, mean)
473
+ class EmptyPredictorModel:
474
+ def fit(self, X, y):
475
+ self.result = empty_predictors_function(y)
476
+ def predict(self, X):
477
+ return self.result
478
+ a_model = EmptyPredictorModel()
479
+ # a_model = empty_predictors_model(array[1])
480
+ a_model.fit(X=array[2:].T, y=array[1])
481
+
482
+ fit_results[j]['model'] = a_model
483
+
484
+ # Cache and return the fit results
485
+ self.fit_results = fit_results
486
+ return fit_results
487
+
488
+ def get_coefs(self):
489
+ """Returns dictionary of coefficients for linear models.
490
+
491
+ Only for models from sklearn.linear_model
492
+
493
+ Returns
494
+ -------
495
+ coeffs : dictionary
496
+ Dictionary of dictionaries for each variable with keys given by the
497
+ parents and the regression coefficients as values.
498
+ """
499
+ coeffs = {}
500
+ for j in self.selected_variables:
501
+ coeffs[j] = {}
502
+ for ipar, par in enumerate(self.all_parents[j]):
503
+ coeffs[j][par] = self.fit_results[j]['model'].coef_[ipar]
504
+ return coeffs
505
+
506
+ def get_val_matrix(self):
507
+ """Returns the coefficient array for different lags for linear model.
508
+
509
+ Requires fit_model() before. An entry val_matrix[i,j,tau] gives the
510
+ coefficient of the link from i to j at lag tau, including tau=0.
511
+
512
+ Returns
513
+ -------
514
+ val_matrix : array-like, shape (N, N, tau_max + 1)
515
+ Array of coefficients for each time lag, including lag-zero.
516
+ """
517
+
518
+ coeffs = self.get_coefs()
519
+ val_matrix = np.zeros((self.N, self.N, self.tau_max + 1, ))
520
+
521
+ for j in list(coeffs):
522
+ for par in list(coeffs[j]):
523
+ i, tau = par
524
+ val_matrix[i,j,abs(tau)] = coeffs[j][par]
525
+
526
+ return val_matrix
527
+
528
+ def predict_full_model(self,
529
+ new_data=None,
530
+ pred_params=None,
531
+ cut_off='max_lag_or_tau_max'):
532
+ r"""Predict target variable with fitted model.
533
+
534
+ Uses the model.predict() function of the sklearn model.
535
+
536
+ A list of predicted time series for self.selected_variables is returned.
537
+
538
+ Parameters
539
+ ----------
540
+ new_data : data object, optional
541
+ New Tigramite dataframe object with optional new mask. Note that
542
+ the data will be cut off according to cut_off, see parameter
543
+ `cut_off` below.
544
+ pred_params : dict, optional
545
+ Optional parameters passed on to sklearn prediction function.
546
+ cut_off : {'2xtau_max', 'max_lag', 'max_lag_or_tau_max'}
547
+ How many samples to cutoff at the beginning. The default is
548
+ '2xtau_max', which guarantees that MCI tests are all conducted on
549
+ the same samples. For modeling, 'max_lag_or_tau_max' can be used,
550
+ which uses the maximum of tau_max and the conditions, which is
551
+ useful to compare multiple models on the same sample. Last,
552
+ 'max_lag' uses as much samples as possible.
553
+
554
+ Returns
555
+ -------
556
+ Results from prediction.
557
+ """
558
+
559
+ if hasattr(self, 'selected_variables'):
560
+ target_list = self.selected_variables
561
+ else:
562
+ raise ValueError("Model not yet fitted.")
563
+
564
+ pred_list = []
565
+ self.stored_test_array = {}
566
+ for target in target_list:
567
+ # Default value for pred_params
568
+ if pred_params is None:
569
+ pred_params = {}
570
+
571
+ # Construct the array form of the data
572
+ Y = [(target, 0)] # dummy
573
+ X = [(target, 0)] # dummy
574
+ Z = self.all_parents[target]
575
+
576
+ # Check if we've passed a new dataframe object
577
+ if new_data is not None:
578
+ # if new_data.mask is None:
579
+ # # if no mask is supplied, use the same mask as for the fitted array
580
+ # new_data_mask = self.test_mask
581
+ # else:
582
+ new_data_mask = new_data.mask
583
+ test_array, _, _ = new_data.construct_array(X, Y, Z,
584
+ tau_max=self.tau_max,
585
+ mask=new_data_mask,
586
+ mask_type=self.mask_type,
587
+ cut_off=cut_off,
588
+ remove_overlaps=True,
589
+ verbosity=self.verbosity)
590
+ # Otherwise use the default values
591
+ else:
592
+ test_array, _, _ = \
593
+ self.dataframe.construct_array(X, Y, Z,
594
+ tau_max=self.tau_max,
595
+ mask_type=self.mask_type,
596
+ cut_off=cut_off,
597
+ remove_overlaps=True,
598
+ verbosity=self.verbosity)
599
+ # Transform the data if needed
600
+ a_transform = self.fit_results[target]['data_transform']
601
+ if a_transform is not None:
602
+ test_array = a_transform.transform(X=test_array.T).T
603
+ # Cache the test array
604
+ self.stored_test_array[target] = test_array
605
+ # Run the predictor
606
+ predicted = self.fit_results[target]['model'].predict(
607
+ X=test_array[2:].T, **pred_params)
608
+
609
+ if test_array[2:].size == 0:
610
+ # If there are no predictors, return the value of
611
+ # empty_predictors_function, which is np.mean
612
+ # and expand to the test array length
613
+ predicted = predicted * np.ones(test_array.shape[1])
614
+
615
+ pred_list.append(predicted)
616
+
617
+ return pred_list
618
+
619
+
620
+ def get_residuals_cov_mean(self, new_data=None, pred_params=None):
621
+ r"""Returns covariance and means of residuals from fitted model.
622
+
623
+ Residuals are available as self.residuals.
624
+
625
+ Parameters
626
+ ----------
627
+ new_data : data object, optional
628
+ New Tigramite dataframe object with optional new mask. Note that
629
+ the data will be cut off according to cut_off, see parameter
630
+ `cut_off` below.
631
+ pred_params : dict, optional
632
+ Optional parameters passed on to sklearn prediction function.
633
+
634
+ Returns
635
+ -------
636
+ Results from prediction.
637
+ """
638
+
639
+ assert self.dataframe.analysis_mode == 'single'
640
+
641
+ N = self.dataframe.N
642
+ T = self.dataframe.T[0]
643
+
644
+ # Get overlapping samples
645
+ used_indices = {}
646
+ overlapping = set(list(range(0, T)))
647
+ for j in self.all_parents:
648
+ if self.fit_results[j] is not None:
649
+ if 'used_indices' not in self.fit_results[j]:
650
+ raise ValueError("Run ")
651
+ used_indices[j] = set(self.fit_results[j]['used_indices'][0])
652
+ overlapping = overlapping.intersection(used_indices[j])
653
+
654
+ overlapping = sorted(list(overlapping))
655
+
656
+ if len(overlapping) <= 10:
657
+ raise ValueError("Less than 10 overlapping samples due to masking and/or missing values,"
658
+ " cannot compute residual covariance!")
659
+
660
+ predicted = self.predict_full_model(new_data=new_data,
661
+ pred_params=pred_params,
662
+ cut_off='max_lag_or_tau_max')
663
+
664
+ # Residuals only exist after tau_max
665
+ residuals = self.dataframe.values[0].copy()
666
+
667
+ for index, j in enumerate([j for j in self.all_parents]): # if len(parents[j]) > 0]):
668
+ residuals[list(used_indices[j]), j] -= predicted[index]
669
+
670
+ overlapping_residuals = residuals[overlapping]
671
+
672
+ len_residuals = len(overlapping_residuals)
673
+
674
+ cov = np.cov(overlapping_residuals, rowvar=0)
675
+ mean = np.mean(overlapping_residuals, axis=0) # residuals should have zero mean due to prediction including constant
676
+
677
+ self.residuals = overlapping_residuals
678
+
679
+ return cov, mean
680
+
681
+ class LinearMediation(Models):
682
+ r"""Linear mediation analysis for time series models.
683
+
684
+ Fits linear model to parents and provides functions to return measures such
685
+ as causal effect, mediated causal effect, average causal effect, etc. as
686
+ described in [4]_. Also allows for contemporaneous links.
687
+
688
+ For general linear and nonlinear causal effect analysis including latent
689
+ variables and further functionality use the CausalEffects class.
690
+
691
+ Notes
692
+ -----
693
+ This class implements the following causal mediation measures introduced in
694
+ [4]_:
695
+
696
+ * causal effect (CE)
697
+ * mediated causal effect (MCE)
698
+ * average causal effect (ACE)
699
+ * average causal susceptibility (ACS)
700
+ * average mediated causal effect (AMCE)
701
+
702
+ Consider a simple model of a causal chain as given in the Example with
703
+
704
+ .. math:: X_t &= \eta^X_t \\
705
+ Y_t &= 0.5 X_{t-1} + \eta^Y_t \\
706
+ Z_t &= 0.5 Y_{t-1} + \eta^Z_t
707
+
708
+ Here the link coefficient of :math:`X_{t-2} \to Z_t` is zero while the
709
+ causal effect is 0.25. MCE through :math:`Y` is 0.25 implying that *all*
710
+ of the the CE is explained by :math:`Y`. ACE from :math:`X` is 0.37 since it
711
+ has CE 0.5 on :math:`Y` and 0.25 on :math:`Z`.
712
+
713
+ Examples
714
+ --------
715
+ >>> links_coeffs = {0: [], 1: [((0, -1), 0.5)], 2: [((1, -1), 0.5)]}
716
+ >>> data, true_parents = toys.var_process(links_coeffs, T=1000, seed=42)
717
+ >>> dataframe = pp.DataFrame(data)
718
+ >>> med = LinearMediation(dataframe=dataframe)
719
+ >>> med.fit_model(all_parents=true_parents, tau_max=3)
720
+ >>> print "Link coefficient (0, -2) --> 2: ", med.get_coeff(
721
+ i=0, tau=-2, j=2)
722
+ >>> print "Causal effect (0, -2) --> 2: ", med.get_ce(i=0, tau=-2, j=2)
723
+ >>> print "Mediated Causal effect (0, -2) --> 2 through 1: ", med.get_mce(
724
+ i=0, tau=-2, j=2, k=1)
725
+ >>> print "Average Causal Effect: ", med.get_all_ace()
726
+ >>> print "Average Causal Susceptibility: ", med.get_all_acs()
727
+ >>> print "Average Mediated Causal Effect: ", med.get_all_amce()
728
+ Link coefficient (0, -2) --> 2: 0.0
729
+ Causal effect (0, -2) --> 2: 0.250648072987
730
+ Mediated Causal effect (0, -2) --> 2 through 1: 0.250648072987
731
+ Average Causal Effect: [ 0.36897445 0.25718002 0. ]
732
+ Average Causal Susceptibility: [ 0. 0.24365041 0.38250406]
733
+ Average Mediated Causal Effect: [ 0. 0.12532404 0. ]
734
+
735
+ References
736
+ ----------
737
+ .. [4] J. Runge et al. (2015): Identifying causal gateways and mediators in
738
+ complex spatio-temporal systems.
739
+ Nature Communications, 6, 8502. http://doi.org/10.1038/ncomms9502
740
+
741
+ Parameters
742
+ ----------
743
+ dataframe : data object
744
+ Tigramite dataframe object. It must have the attributes dataframe.values
745
+ yielding a numpy array of shape (observations T, variables N) and
746
+ optionally a mask of the same shape and a missing values flag.
747
+ model_params : dictionary, optional (default: None)
748
+ Optional parameters passed on to sklearn model
749
+ data_transform : sklearn preprocessing object, optional (default: StandardScaler)
750
+ Used to transform data prior to fitting. For example,
751
+ sklearn.preprocessing.StandardScaler for simple standardization. The
752
+ fitted parameters are stored.
753
+ mask_type : {None, 'y','x','z','xy','xz','yz','xyz'}
754
+ Masking mode: Indicators for which variables in the dependence
755
+ measure I(X; Y | Z) the samples should be masked. If None, the mask
756
+ is not used. Explained in tutorial on masking and missing values.
757
+ verbosity : int, optional (default: 0)
758
+ Level of verbosity.
759
+ """
760
+
761
+ def __init__(self,
762
+ dataframe,
763
+ model_params=None,
764
+ data_transform=sklearn.preprocessing.StandardScaler(),
765
+ mask_type=None,
766
+ verbosity=0):
767
+ # Initialize the member variables to None
768
+ self.phi = None
769
+ self.psi = None
770
+ self.all_psi_k = None
771
+ self.dataframe = dataframe
772
+ self.mask_type = mask_type
773
+ self.data_transform = data_transform
774
+ if model_params is None:
775
+ self.model_params = {}
776
+ else:
777
+ self.model_params = model_params
778
+
779
+ self.bootstrap_available = False
780
+
781
+ # Build the model using the parameters
782
+ if model_params is None:
783
+ model_params = {}
784
+ this_model = sklearn.linear_model.LinearRegression(**model_params)
785
+ Models.__init__(self,
786
+ dataframe=dataframe,
787
+ model=this_model,
788
+ data_transform=data_transform,
789
+ mask_type=mask_type,
790
+ verbosity=verbosity)
791
+
792
+ def fit_model(self, all_parents, tau_max=None, return_data=False):
793
+ r"""Fit linear time series model.
794
+
795
+ Fits a sklearn.linear_model.LinearRegression model to the parents of
796
+ each variable and computes the coefficient matrices :math:`\Phi` and
797
+ :math:`\Psi` as described in [4]_. Does accept contemporaneous links.
798
+
799
+ Parameters
800
+ ----------
801
+ all_parents : dictionary
802
+ Dictionary of form {0:[(0, -1), (3, 0), ...], 1:[], ...} containing
803
+ the parents estimated with PCMCI.
804
+ tau_max : int, optional (default: None)
805
+ Maximum time lag. If None, the maximum lag in all_parents is used.
806
+ return_data : bool, optional (default: False)
807
+ Whether to save the data array. Needed to get residuals.
808
+ """
809
+
810
+ # Fit the model using the base class
811
+ self.fit_results = self.fit_full_model(all_parents=all_parents,
812
+ selected_variables=None,
813
+ return_data=return_data,
814
+ tau_max=tau_max)
815
+ # Cache the results in the member variables
816
+ coeffs = self.get_coefs()
817
+ self.phi = self._get_phi(coeffs)
818
+ self.psi = self._get_psi(self.phi)
819
+ self.all_psi_k = self._get_all_psi_k(self.phi)
820
+
821
+ self.all_parents = all_parents
822
+ # self.tau_max = tau_max
823
+
824
+ def fit_model_bootstrap(self,
825
+ boot_blocklength=1,
826
+ seed=None,
827
+ boot_samples=100):
828
+ """Fits boostrap-versions of Phi, Psi, etc.
829
+
830
+ Random draws are generated
831
+
832
+ Parameters
833
+ ----------
834
+ boot_blocklength : int, or in {'cube_root', 'from_autocorrelation'}
835
+ Block length for block-bootstrap. If 'cube_root' it is the cube
836
+ root of the time series length.
837
+ seed : int, optional(default = None)
838
+ Seed for RandomState (default_rng)
839
+ boot_samples : int
840
+ Number of bootstrap samples.
841
+ """
842
+
843
+ self.phi_boots = np.empty((boot_samples,) + self.phi.shape)
844
+ self.psi_boots = np.empty((boot_samples,) + self.psi.shape)
845
+ self.all_psi_k_boots = np.empty((boot_samples,) + self.all_psi_k.shape)
846
+
847
+ if self.verbosity > 0:
848
+ print("\n##\n## Generating bootstrap samples of Phi, Psi, etc " +
849
+ "\n##\n" +
850
+ "\nboot_samples = %s \n" % boot_samples +
851
+ "\nboot_blocklength = %s \n" % boot_blocklength
852
+ )
853
+
854
+
855
+ for b in range(boot_samples):
856
+ # # Replace dataframe in method args by bootstrapped dataframe
857
+ # method_args_bootstrap['dataframe'].bootstrap = boot_draw
858
+ if seed is None:
859
+ random_state = np.random.default_rng(None)
860
+ else:
861
+ random_state = np.random.default_rng(seed+b)
862
+
863
+ dataframe_here = deepcopy(self.dataframe)
864
+
865
+ dataframe_here.bootstrap = {'boot_blocklength':boot_blocklength,
866
+ 'random_state':random_state}
867
+ model = Models(dataframe=dataframe_here,
868
+ model=sklearn.linear_model.LinearRegression(**self.model_params),
869
+ data_transform=self.data_transform,
870
+ mask_type=self.mask_type,
871
+ verbosity=0)
872
+
873
+ model.fit_full_model(all_parents=self.all_parents,
874
+ tau_max=self.tau_max)
875
+ # Cache the results in the member variables
876
+ coeffs = model.get_coefs()
877
+ phi = self._get_phi(coeffs)
878
+ self.phi_boots[b] = phi
879
+ self.psi_boots[b] = self._get_psi(phi)
880
+ self.all_psi_k_boots[b] = self._get_all_psi_k(phi)
881
+
882
+ self.bootstrap_available = True
883
+
884
+ return self
885
+
886
+ def get_bootstrap_of(self, function, function_args, conf_lev=0.9):
887
+ """Applies bootstrap-versions of Phi, Psi, etc. to any function in
888
+ this class.
889
+
890
+ Parameters
891
+ ----------
892
+ function : string
893
+ Valid function from LinearMediation class
894
+ function_args : dict
895
+ Optional function arguments.
896
+ conf_lev : float
897
+ Confidence interval.
898
+
899
+ Returns
900
+ -------
901
+ Upper/Lower confidence interval of function.
902
+ """
903
+
904
+ valid_functions = [
905
+ 'get_coeff',
906
+ 'get_ce',
907
+ 'get_ce_max',
908
+ 'get_joint_ce',
909
+ 'get_joint_ce_matrix',
910
+ 'get_mce',
911
+ 'get_conditional_mce',
912
+ 'get_joint_mce',
913
+ 'get_ace',
914
+ 'get_all_ace',
915
+ 'get_acs',
916
+ 'get_all_acs',
917
+ 'get_amce',
918
+ 'get_all_amce',
919
+ 'get_val_matrix',
920
+ ]
921
+
922
+ if function not in valid_functions:
923
+ raise ValueError("function must be in %s" %valid_functions)
924
+
925
+ realizations = self.phi_boots.shape[0]
926
+
927
+ original_phi = deepcopy(self.phi)
928
+ original_psi = deepcopy(self.psi)
929
+ original_all_psi_k = deepcopy(self.all_psi_k)
930
+
931
+ for r in range(realizations):
932
+ self.phi = self.phi_boots[r]
933
+ self.psi = self.psi_boots[r]
934
+ self.all_psi_k = self.all_psi_k_boots[r]
935
+
936
+ boot_effect = getattr(self, function)(**function_args)
937
+
938
+ if r == 0:
939
+ bootstrap_result = np.empty((realizations,) + boot_effect.shape)
940
+
941
+ bootstrap_result[r] = boot_effect
942
+
943
+ # Confidence intervals for val_matrix; interval is two-sided
944
+ c_int = (1. - (1. - conf_lev)/2.)
945
+ confidence_interval = np.percentile(
946
+ bootstrap_result, axis=0,
947
+ q = [100*(1. - c_int), 100*c_int])
948
+
949
+ self.phi = original_phi
950
+ self.psi = original_psi
951
+ self.all_psi_k = original_all_psi_k
952
+ self.bootstrap_result = bootstrap_result
953
+
954
+ return confidence_interval
955
+
956
+
957
+ def _check_sanity(self, X, Y, k=None):
958
+ """Checks validity of some parameters."""
959
+
960
+ if len(X) != 1 or len(Y) != 1:
961
+ raise ValueError("X must be of form [(i, -tau)] and Y = [(j, 0)], "
962
+ "but are X = %s, Y=%s" % (X, Y))
963
+
964
+ i, tau = X[0]
965
+
966
+ if abs(tau) > self.tau_max:
967
+ raise ValueError("X must be of form [(i, -tau)] with"
968
+ " tau <= tau_max")
969
+
970
+ if k is not None and (k < 0 or k >= self.N):
971
+ raise ValueError("k must be in [0, N)")
972
+
973
+ def _get_phi(self, coeffs):
974
+ """Returns the linear coefficient matrices for different lags.
975
+
976
+ Parameters
977
+ ----------
978
+ coeffs : dictionary
979
+ Dictionary of coefficients for each parent.
980
+
981
+ Returns
982
+ -------
983
+ phi : array-like, shape (tau_max + 1, N, N)
984
+ Matrices of coefficients for each time lag.
985
+ """
986
+
987
+ phi = np.zeros((self.tau_max + 1, self.N, self.N))
988
+ # phi[0] = np.identity(self.N)
989
+
990
+ # Also includes contemporaneous lags
991
+ for j in list(coeffs):
992
+ for par in list(coeffs[j]):
993
+ i, tau = par
994
+ phi[abs(tau), j, i] = coeffs[j][par]
995
+
996
+ return phi
997
+
998
+ def _get_psi(self, phi):
999
+ """Returns the linear causal effect matrices for different lags incl
1000
+ lag zero.
1001
+
1002
+ Parameters
1003
+ ----------
1004
+ phi : array-like
1005
+ Coefficient matrices at different lags.
1006
+
1007
+ Returns
1008
+ -------
1009
+ psi : array-like, shape (tau_max + 1, N, N)
1010
+ Matrices of causal effects for each time lag incl contemporaneous links.
1011
+ """
1012
+
1013
+ psi = np.zeros((self.tau_max + 1, self.N, self.N))
1014
+
1015
+ psi[0] = np.linalg.pinv(np.identity(self.N) - phi[0])
1016
+ for tau in range(1, self.tau_max + 1):
1017
+ for s in range(1, tau + 1):
1018
+ psi[tau] += np.matmul(psi[0], np.matmul(phi[s], psi[tau - s]) )
1019
+
1020
+ # Lagged-only effects:
1021
+ # psi = np.zeros((self.tau_max + 1, self.N, self.N))
1022
+
1023
+ # psi[0] = np.identity(self.N)
1024
+ # for n in range(1, self.tau_max + 1):
1025
+ # psi[n] = np.zeros((self.N, self.N))
1026
+ # for s in range(1, n + 1):
1027
+ # psi[n] += np.dot(phi[s], psi[n - s])
1028
+
1029
+ return psi
1030
+
1031
+ def _get_psi_k(self, phi, k):
1032
+ """Returns the linear causal effect matrices excluding variable k.
1033
+
1034
+ Essentially, this blocks all path through parents of variable k
1035
+ at any lag.
1036
+
1037
+ Parameters
1038
+ ----------
1039
+ phi : array-like
1040
+ Coefficient matrices at different lags.
1041
+ k : int or list of ints
1042
+ Variable indices to exclude causal effects through.
1043
+
1044
+ Returns
1045
+ -------
1046
+ psi_k : array-like, shape (tau_max + 1, N, N)
1047
+ Matrices of causal effects excluding k.
1048
+ """
1049
+
1050
+ psi_k = np.zeros((self.tau_max + 1, self.N, self.N))
1051
+
1052
+ phi_k = np.copy(phi)
1053
+ if isinstance(k, int):
1054
+ phi_k[:, k, :] = 0.
1055
+ else:
1056
+ for k_here in k:
1057
+ phi_k[:, k_here, :] = 0.
1058
+
1059
+
1060
+ psi_k[0] = np.linalg.pinv(np.identity(self.N) - phi_k[0])
1061
+ for tau in range(1, self.tau_max + 1):
1062
+ # psi_k[tau] = np.matmul(psi_k[0], np.matmul(phi_k[tau], psi_k[0]))
1063
+ for s in range(1, tau + 1):
1064
+ psi_k[tau] += np.matmul(psi_k[0], np.matmul(phi_k[s], psi_k[tau - s]))
1065
+
1066
+
1067
+ # psi_k[0] = np.identity(self.N)
1068
+ # phi_k = np.copy(phi)
1069
+ # phi_k[:, k, :] = 0.
1070
+ # for n in range(1, self.tau_max + 1):
1071
+ # psi_k[n] = np.zeros((self.N, self.N))
1072
+ # for s in range(1, n + 1):
1073
+ # psi_k[n] += np.dot(phi_k[s], psi_k[n - s])
1074
+
1075
+ return psi_k
1076
+
1077
+ def _get_all_psi_k(self, phi):
1078
+ """Returns the linear causal effect matrices excluding variables.
1079
+
1080
+ Parameters
1081
+ ----------
1082
+ phi : array-like
1083
+ Coefficient matrices at different lags.
1084
+
1085
+ Returns
1086
+ -------
1087
+ all_psi_k : array-like, shape (N, tau_max + 1, N, N)
1088
+ Matrices of causal effects where for each row another variable is
1089
+ excluded.
1090
+ """
1091
+
1092
+ all_psi_k = np.zeros((self.N, self.tau_max + 1, self.N, self.N))
1093
+
1094
+ for k in range(self.N):
1095
+ all_psi_k[k] = self._get_psi_k(phi, k)
1096
+
1097
+ return all_psi_k
1098
+
1099
+ def get_coeff(self, i, tau, j):
1100
+ """Returns link coefficient.
1101
+
1102
+ This is the direct causal effect for a particular link (i, -tau) --> j.
1103
+
1104
+ Parameters
1105
+ ----------
1106
+ i : int
1107
+ Index of cause variable.
1108
+ tau : int
1109
+ Lag of cause variable (incl lag zero).
1110
+ j : int
1111
+ Index of effect variable.
1112
+
1113
+ Returns
1114
+ -------
1115
+ coeff : float
1116
+ """
1117
+ return self.phi[abs(tau), j, i]
1118
+
1119
+ def get_ce(self, i, tau, j):
1120
+ """Returns the causal effect.
1121
+
1122
+ This is the causal effect for (i, -tau) -- --> j.
1123
+
1124
+ Parameters
1125
+ ----------
1126
+ i : int
1127
+ Index of cause variable.
1128
+ tau : int
1129
+ Lag of cause variable (incl lag zero).
1130
+ j : int
1131
+ Index of effect variable.
1132
+
1133
+ Returns
1134
+ -------
1135
+ ce : float
1136
+ """
1137
+ return self.psi[abs(tau), j, i]
1138
+
1139
+ def get_ce_max(self, i, j):
1140
+ """Returns the causal effect.
1141
+
1142
+ This is the maximum absolute causal effect for i --> j across all
1143
+ lags (incl lag zero).
1144
+
1145
+ Parameters
1146
+ ----------
1147
+ i : int
1148
+ Index of cause variable.
1149
+ j : int
1150
+ Index of effect variable.
1151
+
1152
+ Returns
1153
+ -------
1154
+ ce : float
1155
+ """
1156
+ argmax = np.abs(self.psi[:, j, i]).argmax()
1157
+ return self.psi[:, j, i][argmax]
1158
+
1159
+ def get_joint_ce(self, i, j):
1160
+ """Returns the joint causal effect.
1161
+
1162
+ This is the causal effect from all lags [t, ..., t-tau_max]
1163
+ of i on j at time t. Note that the joint effect does not
1164
+ count links passing through parents of i itself.
1165
+
1166
+ Parameters
1167
+ ----------
1168
+ i : int
1169
+ Index of cause variable.
1170
+ j : int
1171
+ Index of effect variable.
1172
+
1173
+ Returns
1174
+ -------
1175
+ joint_ce : array of shape (tau_max + 1)
1176
+ Causal effect from each lag [t, ..., t-tau_max] of i on j.
1177
+ """
1178
+ joint_ce = self.all_psi_k[i, :, j, i]
1179
+ return joint_ce
1180
+
1181
+ def get_joint_ce_matrix(self, i, j):
1182
+ """Returns the joint causal effect matrix of i on j.
1183
+
1184
+ This is the causal effect from all lags [t, ..., t-tau_max]
1185
+ of i on j at times [t, ..., t-tau_max]. Note that the joint effect does not
1186
+ count links passing through parents of i itself.
1187
+
1188
+ An entry (taui, tauj) stands for the effect of i at t-taui on j at t-tauj.
1189
+
1190
+ Parameters
1191
+ ----------
1192
+ i : int
1193
+ Index of cause variable.
1194
+ j : int
1195
+ Index of effect variable.
1196
+
1197
+ Returns
1198
+ -------
1199
+ joint_ce_matrix : 2d array of shape (tau_max + 1, tau_max + 1)
1200
+ Causal effect matrix from each lag of i on each lag of j.
1201
+ """
1202
+ joint_ce_matrix = np.zeros((self.tau_max + 1, self.tau_max + 1))
1203
+ for tauj in range(self.tau_max + 1):
1204
+ joint_ce_matrix[tauj:, tauj] = self.all_psi_k[i, tauj:, j, i][::-1]
1205
+
1206
+ return joint_ce_matrix
1207
+
1208
+ def get_mce(self, i, tau, j, k):
1209
+ """Returns the mediated causal effect.
1210
+
1211
+ This is the causal effect for i --> j minus the causal effect not going
1212
+ through k.
1213
+
1214
+ Parameters
1215
+ ----------
1216
+ i : int
1217
+ Index of cause variable.
1218
+ tau : int
1219
+ Lag of cause variable.
1220
+ j : int
1221
+ Index of effect variable.
1222
+ k : int or list of ints
1223
+ Indices of mediator variables.
1224
+
1225
+ Returns
1226
+ -------
1227
+ mce : float
1228
+ """
1229
+ if isinstance(k, int):
1230
+ effect_without_k = self.all_psi_k[k, abs(tau), j, i]
1231
+ else:
1232
+ effect_without_k = self._get_psi_k(self.phi, k=k)[abs(tau), j, i]
1233
+
1234
+ mce = self.psi[abs(tau), j, i] - effect_without_k
1235
+ return mce
1236
+
1237
+ def get_conditional_mce(self, i, tau, j, k, notk):
1238
+ """Returns the conditional mediated causal effect.
1239
+
1240
+ This is the causal effect for i --> j for all paths going through k, but not through notk.
1241
+
1242
+ Parameters
1243
+ ----------
1244
+ i : int
1245
+ Index of cause variable.
1246
+ tau : int
1247
+ Lag of cause variable.
1248
+ j : int
1249
+ Index of effect variable.
1250
+ k : int or list of ints
1251
+ Indices of mediator variables.
1252
+ notk : int or list of ints
1253
+ Indices of mediator variables to exclude.
1254
+
1255
+ Returns
1256
+ -------
1257
+ mce : float
1258
+ """
1259
+ if isinstance(k, int):
1260
+ k = set([k])
1261
+ else:
1262
+ k = set(k)
1263
+ if isinstance(notk, int):
1264
+ notk = set([notk])
1265
+ else:
1266
+ notk = set(notk)
1267
+
1268
+ bothk = list(k.union(notk))
1269
+ notk = list(notk)
1270
+
1271
+ effect_without_bothk = self._get_psi_k(self.phi, k=bothk)[abs(tau), j, i]
1272
+ effect_without_notk = self._get_psi_k(self.phi, k=notk)[abs(tau), j, i]
1273
+
1274
+ # mce = self.psi[abs(tau), j, i] - effect_without_k
1275
+ mce = effect_without_notk - effect_without_bothk
1276
+
1277
+ return mce
1278
+
1279
+
1280
+ def get_joint_mce(self, i, j, k):
1281
+ """Returns the joint causal effect mediated through k.
1282
+
1283
+ This is the mediated causal effect from all lags [t, ..., t-tau_max]
1284
+ of i on j at time t for paths through k. Note that the joint effect
1285
+ does not count links passing through parents of i itself.
1286
+
1287
+ Parameters
1288
+ ----------
1289
+ i : int
1290
+ Index of cause variable.
1291
+ j : int
1292
+ Index of effect variable.
1293
+ k : int or list of ints
1294
+ Indices of mediator variables.
1295
+
1296
+ Returns
1297
+ -------
1298
+ joint_mce : array of shape (tau_max + 1)
1299
+ Mediated causal effect from each lag [t, ..., t-tau_max] of i on j through k.
1300
+ """
1301
+ if isinstance(k, int):
1302
+ k_here = [k]
1303
+
1304
+ effect_without_k = self._get_psi_k(self.phi, k=[i] + k_here)
1305
+
1306
+ joint_mce = self.all_psi_k[i, :, j, i] - effect_without_k[:, j, i]
1307
+ return joint_mce
1308
+
1309
+ def get_ace(self, i, lag_mode='absmax', exclude_i=True):
1310
+ """Returns the average causal effect.
1311
+
1312
+ This is the average causal effect (ACE) emanating from variable i to any
1313
+ other variable. With lag_mode='absmax' this is based on the lag of
1314
+ maximum CE for each pair.
1315
+
1316
+ Parameters
1317
+ ----------
1318
+ i : int
1319
+ Index of cause variable.
1320
+ lag_mode : {'absmax', 'all_lags'}
1321
+ Lag mode. Either average across all lags between each pair or only
1322
+ at the lag of maximum absolute causal effect.
1323
+ exclude_i : bool, optional (default: True)
1324
+ Whether to exclude causal effects on the variable itself at later
1325
+ lags.
1326
+
1327
+ Returns
1328
+ -------
1329
+ ace :float
1330
+ Average Causal Effect.
1331
+ """
1332
+
1333
+ all_but_i = np.ones(self.N, dtype='bool')
1334
+ if exclude_i:
1335
+ all_but_i[i] = False
1336
+
1337
+ if lag_mode == 'absmax':
1338
+ return np.abs(self.psi[:, all_but_i, i]).max(axis=0).mean()
1339
+ elif lag_mode == 'all_lags':
1340
+ return np.abs(self.psi[:, all_but_i, i]).mean()
1341
+ else:
1342
+ raise ValueError("lag_mode = %s not implemented" % lag_mode)
1343
+
1344
+ def get_all_ace(self, lag_mode='absmax', exclude_i=True):
1345
+ """Returns the average causal effect for all variables.
1346
+
1347
+ This is the average causal effect (ACE) emanating from variable i to any
1348
+ other variable. With lag_mode='absmax' this is based on the lag of
1349
+ maximum CE for each pair.
1350
+
1351
+ Parameters
1352
+ ----------
1353
+ lag_mode : {'absmax', 'all_lags'}
1354
+ Lag mode. Either average across all lags between each pair or only
1355
+ at the lag of maximum absolute causal effect.
1356
+ exclude_i : bool, optional (default: True)
1357
+ Whether to exclude causal effects on the variable itself at later
1358
+ lags.
1359
+
1360
+ Returns
1361
+ -------
1362
+ ace : array of shape (N,)
1363
+ Average Causal Effect for each variable.
1364
+ """
1365
+
1366
+ ace = np.zeros(self.N)
1367
+ for i in range(self.N):
1368
+ ace[i] = self.get_ace(i, lag_mode=lag_mode, exclude_i=exclude_i)
1369
+
1370
+ return ace
1371
+
1372
+ def get_acs(self, j, lag_mode='absmax', exclude_j=True):
1373
+ """Returns the average causal susceptibility.
1374
+
1375
+ This is the Average Causal Susceptibility (ACS) affecting a variable j
1376
+ from any other variable. With lag_mode='absmax' this is based on the lag
1377
+ of maximum CE for each pair.
1378
+
1379
+ Parameters
1380
+ ----------
1381
+ j : int
1382
+ Index of variable.
1383
+ lag_mode : {'absmax', 'all_lags'}
1384
+ Lag mode. Either average across all lags between each pair or only
1385
+ at the lag of maximum absolute causal effect.
1386
+ exclude_j : bool, optional (default: True)
1387
+ Whether to exclude causal effects on the variable itself at previous
1388
+ lags.
1389
+
1390
+ Returns
1391
+ -------
1392
+ acs : float
1393
+ Average Causal Susceptibility.
1394
+ """
1395
+
1396
+ all_but_j = np.ones(self.N, dtype='bool')
1397
+ if exclude_j:
1398
+ all_but_j[j] = False
1399
+
1400
+ if lag_mode == 'absmax':
1401
+ return np.abs(self.psi[:, j, all_but_j]).max(axis=0).mean()
1402
+ elif lag_mode == 'all_lags':
1403
+ return np.abs(self.psi[:, j, all_but_j]).mean()
1404
+ else:
1405
+ raise ValueError("lag_mode = %s not implemented" % lag_mode)
1406
+
1407
+ def get_all_acs(self, lag_mode='absmax', exclude_j=True):
1408
+ """Returns the average causal susceptibility.
1409
+
1410
+ This is the Average Causal Susceptibility (ACS) for each variable from
1411
+ any other variable. With lag_mode='absmax' this is based on the lag of
1412
+ maximum CE for each pair.
1413
+
1414
+ Parameters
1415
+ ----------
1416
+ lag_mode : {'absmax', 'all_lags'}
1417
+ Lag mode. Either average across all lags between each pair or only
1418
+ at the lag of maximum absolute causal effect.
1419
+ exclude_j : bool, optional (default: True)
1420
+ Whether to exclude causal effects on the variable itself at previous
1421
+ lags.
1422
+
1423
+ Returns
1424
+ -------
1425
+ acs : array of shape (N,)
1426
+ Average Causal Susceptibility.
1427
+ """
1428
+
1429
+ acs = np.zeros(self.N)
1430
+ for j in range(self.N):
1431
+ acs[j] = self.get_acs(j, lag_mode=lag_mode, exclude_j=exclude_j)
1432
+
1433
+ return acs
1434
+
1435
+ def get_amce(self, k, lag_mode='absmax',
1436
+ exclude_k=True, exclude_self_effects=True):
1437
+ """Returns the average mediated causal effect.
1438
+
1439
+ This is the Average Mediated Causal Effect (AMCE) through a variable k
1440
+ With lag_mode='absmax' this is based on the lag of maximum CE for each
1441
+ pair.
1442
+
1443
+ Parameters
1444
+ ----------
1445
+ k : int
1446
+ Index of variable.
1447
+ lag_mode : {'absmax', 'all_lags'}
1448
+ Lag mode. Either average across all lags between each pair or only
1449
+ at the lag of maximum absolute causal effect.
1450
+ exclude_k : bool, optional (default: True)
1451
+ Whether to exclude causal effects through the variable itself at
1452
+ previous lags.
1453
+ exclude_self_effects : bool, optional (default: True)
1454
+ Whether to exclude causal self effects of variables on themselves.
1455
+
1456
+ Returns
1457
+ -------
1458
+ amce : float
1459
+ Average Mediated Causal Effect.
1460
+ """
1461
+
1462
+ all_but_k = np.ones(self.N, dtype='bool')
1463
+ if exclude_k:
1464
+ all_but_k[k] = False
1465
+ N_new = self.N - 1
1466
+ else:
1467
+ N_new = self.N
1468
+
1469
+ if exclude_self_effects:
1470
+ weights = np.identity(N_new) == False
1471
+ else:
1472
+ weights = np.ones((N_new, N_new), dtype='bool')
1473
+
1474
+ # if self.tau_max < 2:
1475
+ # raise ValueError("Mediation only nonzero for tau_max >= 2")
1476
+
1477
+ all_mce = self.psi[:, :, :] - self.all_psi_k[k, :, :, :]
1478
+ # all_mce[:, range(self.N), range(self.N)] = 0.
1479
+
1480
+ if lag_mode == 'absmax':
1481
+ return np.average(np.abs(all_mce[:, all_but_k, :]
1482
+ [:, :, all_but_k]
1483
+ ).max(axis=0), weights=weights)
1484
+ elif lag_mode == 'all_lags':
1485
+ return np.abs(all_mce[:, all_but_k, :][:, :, all_but_k]).mean()
1486
+ else:
1487
+ raise ValueError("lag_mode = %s not implemented" % lag_mode)
1488
+
1489
+ def get_all_amce(self, lag_mode='absmax',
1490
+ exclude_k=True, exclude_self_effects=True):
1491
+ """Returns the average mediated causal effect.
1492
+
1493
+ This is the Average Mediated Causal Effect (AMCE) through all variables
1494
+ With lag_mode='absmax' this is based on the lag of maximum CE for each
1495
+ pair.
1496
+
1497
+ Parameters
1498
+ ----------
1499
+ lag_mode : {'absmax', 'all_lags'}
1500
+ Lag mode. Either average across all lags between each pair or only
1501
+ at the lag of maximum absolute causal effect.
1502
+ exclude_k : bool, optional (default: True)
1503
+ Whether to exclude causal effects through the variable itself at
1504
+ previous lags.
1505
+ exclude_self_effects : bool, optional (default: True)
1506
+ Whether to exclude causal self effects of variables on themselves.
1507
+
1508
+ Returns
1509
+ -------
1510
+ amce : array of shape (N,)
1511
+ Average Mediated Causal Effect.
1512
+ """
1513
+ amce = np.zeros(self.N)
1514
+ for k in range(self.N):
1515
+ amce[k] = self.get_amce(k,
1516
+ lag_mode=lag_mode,
1517
+ exclude_k=exclude_k,
1518
+ exclude_self_effects=exclude_self_effects)
1519
+
1520
+ return amce
1521
+
1522
+
1523
+ def get_val_matrix(self, symmetrize=False):
1524
+ """Returns the matrix of linear coefficients.
1525
+
1526
+ Requires fit_model() before. An entry val_matrix[i,j,tau] gives the
1527
+ coefficient of the link from i to j at lag tau. Lag=0 is always set
1528
+ to zero for LinearMediation, use Models class for contemporaneous
1529
+ models.
1530
+
1531
+ Parameters
1532
+ ----------
1533
+ symmetrize : bool
1534
+ If True, the lag-zero entries will be symmetrized such that
1535
+ no zeros appear. Useful since other parts of tigramite
1536
+ through an error for non-symmetric val_matrix, eg plotting.
1537
+
1538
+ Returns
1539
+ -------
1540
+ val_matrix : array
1541
+ Matrix of linear coefficients, shape (N, N, tau_max + 1).
1542
+ """
1543
+ val_matrix = np.copy(self.phi.transpose())
1544
+ N = val_matrix.shape[0]
1545
+
1546
+ if symmetrize:
1547
+ # Symmetrize since otherwise other parts of tigramite through an error
1548
+ for i in range(N):
1549
+ for j in range(N):
1550
+ if val_matrix[i,j, 0] == 0.:
1551
+ val_matrix[i,j, 0] = val_matrix[j,i, 0]
1552
+
1553
+ return val_matrix
1554
+
1555
+ def net_to_tsg(self, row, lag, max_lag):
1556
+ """Helper function to translate from network to time series graph."""
1557
+ return row * max_lag + lag
1558
+
1559
+ def tsg_to_net(self, node, max_lag):
1560
+ """Helper function to translate from time series graph to network."""
1561
+ row = node // max_lag
1562
+ lag = node % max_lag
1563
+ return (row, -lag)
1564
+
1565
+ def get_tsg(self, link_matrix, val_matrix=None, include_neighbors=False):
1566
+ """Returns time series graph matrix.
1567
+
1568
+ Constructs a matrix of shape (N*tau_max, N*tau_max) from link_matrix.
1569
+ This matrix can be used for plotting the time series graph and analyzing
1570
+ causal pathways.
1571
+
1572
+ Parameters
1573
+ ----------
1574
+ link_matrix : bool array-like, optional (default: None)
1575
+ Matrix of significant links. Must be of same shape as val_matrix.
1576
+ Either sig_thres or link_matrix has to be provided.
1577
+ val_matrix : array_like
1578
+ Matrix of shape (N, N, tau_max+1) containing test statistic values.
1579
+ include_neighbors : bool, optional (default: False)
1580
+ Whether to include causal paths emanating from neighbors of i
1581
+
1582
+ Returns
1583
+ -------
1584
+ tsg : array of shape (N*tau_max, N*tau_max)
1585
+ Time series graph matrix.
1586
+ """
1587
+
1588
+ N = len(link_matrix)
1589
+ max_lag = link_matrix.shape[2] + 1
1590
+
1591
+ # Create TSG
1592
+ tsg = np.zeros((N * max_lag, N * max_lag))
1593
+ for i, j, tau in np.column_stack(np.where(link_matrix)):
1594
+ # if tau > 0 or include_neighbors:
1595
+ for t in range(max_lag):
1596
+ link_start = self.net_to_tsg(i, t - tau, max_lag)
1597
+ link_end = self.net_to_tsg(j, t, max_lag)
1598
+ if (0 <= link_start and
1599
+ (link_start % max_lag) <= (link_end % max_lag)):
1600
+ if val_matrix is not None:
1601
+ tsg[link_start, link_end] = val_matrix[i, j, tau]
1602
+ else:
1603
+ tsg[link_start, link_end] = 1
1604
+ return tsg
1605
+
1606
+ def get_mediation_graph_data(self, i, tau, j, include_neighbors=False):
1607
+ r"""Returns link and node weights for mediation analysis.
1608
+
1609
+ Returns array with non-zero entries for links that are on causal
1610
+ paths between :math:`i` and :math:`j` at lag :math:`\tau`.
1611
+ ``path_val_matrix`` contains the corresponding path coefficients and
1612
+ ``path_node_array`` the MCE values. ``tsg_path_val_matrix`` contains the
1613
+ corresponding values in the time series graph format.
1614
+
1615
+ Parameters
1616
+ ----------
1617
+ i : int
1618
+ Index of cause variable.
1619
+ tau : int
1620
+ Lag of cause variable.
1621
+ j : int
1622
+ Index of effect variable.
1623
+ include_neighbors : bool, optional (default: False)
1624
+ Whether to include causal paths emanating from neighbors of i
1625
+
1626
+ Returns
1627
+ -------
1628
+ graph_data : dictionary
1629
+ Dictionary of matrices for coloring mediation graph plots.
1630
+ """
1631
+
1632
+ path_link_matrix = np.zeros((self.N, self.N, self.tau_max + 1))
1633
+ path_val_matrix = np.zeros((self.N, self.N, self.tau_max + 1))
1634
+
1635
+ # Get mediation of path variables
1636
+ path_node_array = (self.psi.reshape(1, self.tau_max + 1, self.N, self.N)
1637
+ - self.all_psi_k)[:, abs(tau), j, i]
1638
+
1639
+ # Get involved links
1640
+ val_matrix = self.phi.transpose()
1641
+ link_matrix = val_matrix != 0.
1642
+
1643
+ max_lag = link_matrix.shape[2] + 1
1644
+
1645
+ # include_neighbors = False because True would allow
1646
+ # --> o -- motifs in networkx.all_simple_paths as paths, but
1647
+ # these are blocked...
1648
+ tsg = self.get_tsg(link_matrix, val_matrix=val_matrix,
1649
+ include_neighbors=False)
1650
+
1651
+ if include_neighbors:
1652
+ # Add contemporaneous links only at source node
1653
+ for m, n in zip(*np.where(link_matrix[:, :, 0])):
1654
+ # print m,n
1655
+ if m != n:
1656
+ tsg[self.net_to_tsg(m, max_lag - tau - 1, max_lag),
1657
+ self.net_to_tsg(n, max_lag - tau - 1, max_lag)
1658
+ ] = val_matrix[m, n, 0]
1659
+
1660
+ tsg_path_val_matrix = np.zeros(tsg.shape)
1661
+
1662
+ graph = networkx.DiGraph(tsg)
1663
+ pathways = []
1664
+
1665
+ for path in networkx.all_simple_paths(graph,
1666
+ source=self.net_to_tsg(i,
1667
+ max_lag - tau - 1,
1668
+ max_lag),
1669
+ target=self.net_to_tsg(j,
1670
+ max_lag - 0 - 1,
1671
+ max_lag)):
1672
+ pathways.append([self.tsg_to_net(p, max_lag) for p in path])
1673
+ for ip, p in enumerate(path[1:]):
1674
+ tsg_path_val_matrix[path[ip], p] = tsg[path[ip], p]
1675
+
1676
+ k, tau_k = self.tsg_to_net(p, max_lag)
1677
+ link_start = self.tsg_to_net(path[ip], max_lag)
1678
+ link_end = self.tsg_to_net(p, max_lag)
1679
+ delta_tau = abs(link_end[1] - link_start[1])
1680
+ path_val_matrix[link_start[0],
1681
+ link_end[0],
1682
+ delta_tau] = val_matrix[link_start[0],
1683
+ link_end[0],
1684
+ delta_tau]
1685
+
1686
+ graph_data = {'path_node_array': path_node_array,
1687
+ 'path_val_matrix': path_val_matrix,
1688
+ 'tsg_path_val_matrix': tsg_path_val_matrix}
1689
+
1690
+ return graph_data
1691
+
1692
+
1693
+ class Prediction(Models, PCMCI):
1694
+ r"""Prediction class for time series models.
1695
+
1696
+ Allows to fit and predict from any sklearn model. The optimal predictors can
1697
+ be estimated using PCMCI. Also takes care of missing values, masking and
1698
+ preprocessing.
1699
+
1700
+ Parameters
1701
+ ----------
1702
+ dataframe : data object
1703
+ Tigramite dataframe object. It must have the attributes dataframe.values
1704
+ yielding a numpy array of shape (observations T, variables N) and
1705
+ optionally a mask of the same shape and a missing values flag.
1706
+ train_indices : array-like
1707
+ Either boolean array or time indices marking the training data.
1708
+ test_indices : array-like
1709
+ Either boolean array or time indices marking the test data.
1710
+ prediction_model : sklearn model object
1711
+ For example, sklearn.linear_model.LinearRegression() for a linear
1712
+ regression model.
1713
+ cond_ind_test : Conditional independence test object, optional
1714
+ Only needed if predictors are estimated with causal algorithm.
1715
+ The class will be initialized with masking set to the training data.
1716
+ data_transform : sklearn preprocessing object, optional (default: None)
1717
+ Used to transform data prior to fitting. For example,
1718
+ sklearn.preprocessing.StandardScaler for simple standardization. The
1719
+ fitted parameters are stored.
1720
+ verbosity : int, optional (default: 0)
1721
+ Level of verbosity.
1722
+ """
1723
+
1724
+ def __init__(self,
1725
+ dataframe,
1726
+ train_indices,
1727
+ test_indices,
1728
+ prediction_model,
1729
+ cond_ind_test=None,
1730
+ data_transform=None,
1731
+ verbosity=0):
1732
+
1733
+ if dataframe.analysis_mode != 'single':
1734
+ raise ValueError("Prediction class currently only supports single "
1735
+ "datasets.")
1736
+
1737
+ # dataframe.values = {0: dataframe.values[0]}
1738
+
1739
+ # Default value for the mask
1740
+ if dataframe.mask is not None:
1741
+ mask = {0: dataframe.mask[0]}
1742
+ else:
1743
+ mask = {0: np.zeros(dataframe.values[0].shape, dtype='bool')}
1744
+ # Get the dataframe shape
1745
+ T = dataframe.T[0]
1746
+
1747
+ # Have the default dataframe be the training data frame
1748
+ train_mask = deepcopy(mask)
1749
+ train_mask[0][[t for t in range(T) if t not in train_indices]] = True
1750
+ self.dataframe = deepcopy(dataframe)
1751
+ self.dataframe.mask = train_mask
1752
+ self.dataframe._initialized_from = 'dict'
1753
+ # = DataFrame(dataframe.values[0],
1754
+ # mask=train_mask,
1755
+ # missing_flag=dataframe.missing_flag)
1756
+ # Initialize the models baseclass with the training dataframe
1757
+ Models.__init__(self,
1758
+ dataframe=self.dataframe,
1759
+ model=prediction_model,
1760
+ data_transform=data_transform,
1761
+ mask_type='y',
1762
+ verbosity=verbosity)
1763
+
1764
+ # Build the testing dataframe as well
1765
+ self.test_mask = deepcopy(mask)
1766
+ self.test_mask[0][[t for t in range(T) if t not in test_indices]] = True
1767
+
1768
+ self.train_indices = train_indices
1769
+ self.test_indices = test_indices
1770
+
1771
+ # Setup the PCMCI instance
1772
+ if cond_ind_test is not None:
1773
+ # Force the masking
1774
+ cond_ind_test.set_mask_type('y')
1775
+ cond_ind_test.verbosity = verbosity
1776
+ # PCMCI.__init__(self,
1777
+ # dataframe=self.dataframe,
1778
+ # cond_ind_test=cond_ind_test,
1779
+ # verbosity=verbosity)
1780
+ self.pcmci = PCMCI(dataframe=self.dataframe,
1781
+ cond_ind_test=cond_ind_test,
1782
+ verbosity=verbosity)
1783
+
1784
+ # Set the member variables
1785
+ self.cond_ind_test = cond_ind_test
1786
+ # Initialize member varialbes that are set outside
1787
+ self.target_predictors = None
1788
+ self.selected_targets = None
1789
+ self.fitted_model = None
1790
+ self.test_array = None
1791
+
1792
+ def get_predictors(self,
1793
+ selected_targets=None,
1794
+ selected_links=None,
1795
+ steps_ahead=1,
1796
+ tau_max=1,
1797
+ pc_alpha=0.2,
1798
+ max_conds_dim=None,
1799
+ max_combinations=1):
1800
+ """Estimate predictors using PC1 algorithm.
1801
+
1802
+ Wrapper around PCMCI.run_pc_stable that estimates causal predictors.
1803
+ The lead time can be specified by ``steps_ahead``.
1804
+
1805
+ Parameters
1806
+ ----------
1807
+ selected_targets : list of ints, optional (default: None)
1808
+ List of variables to estimate predictors of. If None, predictors of
1809
+ all variables are estimated.
1810
+ selected_links : dict or None
1811
+ Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...}
1812
+ specifying whether only selected links should be tested. If None is
1813
+ passed, all links are tested
1814
+ steps_ahead : int, default: 1
1815
+ Minimum time lag to test. Useful for multi-step ahead predictions.
1816
+ tau_max : int, default: 1
1817
+ Maximum time lag. Must be larger or equal to tau_min.
1818
+ pc_alpha : float or list of floats, default: 0.2
1819
+ Significance level in algorithm. If a list or None is passed, the
1820
+ pc_alpha level is optimized for every variable across the given
1821
+ pc_alpha values using the score computed in
1822
+ cond_ind_test.get_model_selection_criterion()
1823
+ max_conds_dim : int or None
1824
+ Maximum number of conditions to test. If None is passed, this number
1825
+ is unrestricted.
1826
+ max_combinations : int, default: 1
1827
+ Maximum number of combinations of conditions of current cardinality
1828
+ to test. Defaults to 1 for PC_1 algorithm. For original PC algorithm
1829
+ a larger number, such as 10, can be used.
1830
+
1831
+ Returns
1832
+ -------
1833
+ predictors : dict
1834
+ Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...}
1835
+ containing estimated predictors.
1836
+ """
1837
+
1838
+ if selected_links is not None:
1839
+ link_assumptions = {}
1840
+ for j in selected_links.keys():
1841
+ link_assumptions[j] = {(i, -tau):"-?>" for i in range(self.N) for tau in range(1, tau_max+1)}
1842
+ else:
1843
+ link_assumptions = None
1844
+
1845
+ # Ensure an independence model is given
1846
+ if self.cond_ind_test is None:
1847
+ raise ValueError("No cond_ind_test given!")
1848
+ # Set the selected variables
1849
+ self.selected_variables = range(self.N)
1850
+ if selected_targets is not None:
1851
+ self.selected_variables = selected_targets
1852
+
1853
+ predictors = self.pcmci.run_pc_stable(link_assumptions=link_assumptions,
1854
+ tau_min=steps_ahead,
1855
+ tau_max=tau_max,
1856
+ save_iterations=False,
1857
+ pc_alpha=pc_alpha,
1858
+ max_conds_dim=max_conds_dim,
1859
+ max_combinations=max_combinations)
1860
+ return predictors
1861
+
1862
+ def fit(self, target_predictors,
1863
+ selected_targets=None, tau_max=None, return_data=False):
1864
+ r"""Fit time series model.
1865
+
1866
+ Wrapper around ``Models.fit_full_model()``. To each variable in
1867
+ ``selected_targets``, the sklearn model is fitted with :math:`y` given
1868
+ by the target variable, and :math:`X` given by its predictors. The
1869
+ fitted model class is returned for later use.
1870
+
1871
+ Parameters
1872
+ ----------
1873
+ target_predictors : dictionary
1874
+ Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} containing
1875
+ the predictors estimated with PCMCI.
1876
+ selected_targets : list of integers, optional (default: range(N))
1877
+ Specify to fit model only for selected targets. If None is
1878
+ passed, models are estimated for all variables.
1879
+ tau_max : int, optional (default: None)
1880
+ Maximum time lag. If None, the maximum lag in target_predictors is
1881
+ used.
1882
+ return_data : bool, optional (default: False)
1883
+ Whether to save the data array.
1884
+
1885
+ Returns
1886
+ -------
1887
+ self : instance of self
1888
+ """
1889
+
1890
+ if selected_targets is None:
1891
+ self.selected_targets = range(self.N)
1892
+ else:
1893
+ self.selected_targets = selected_targets
1894
+
1895
+ if tau_max is None:
1896
+ # Find the maximal parents lag
1897
+ max_parents_lag = 0
1898
+ for j in self.selected_targets:
1899
+ if target_predictors[j]:
1900
+ this_parent_lag = np.abs(np.array(target_predictors[j])[:, 1]).max()
1901
+ max_parents_lag = max(max_parents_lag, this_parent_lag)
1902
+ else:
1903
+ max_parents_lag = tau_max
1904
+
1905
+ if len(set(np.array(self.test_indices) - max_parents_lag)
1906
+ .intersection(self.train_indices)) > 0:
1907
+ if self.verbosity > 0:
1908
+ warnings.warn("test_indices - maxlag(predictors) [or tau_max] "
1909
+ "overlaps with train_indices: Choose test_indices "
1910
+ "such that there is a gap of max_lag to train_indices!")
1911
+
1912
+ self.target_predictors = target_predictors
1913
+
1914
+ for target in self.selected_targets:
1915
+ if target not in list(self.target_predictors):
1916
+ raise ValueError("No predictors given for target %s" % target)
1917
+
1918
+ self.fitted_model = \
1919
+ self.fit_full_model(all_parents=self.target_predictors,
1920
+ selected_variables=self.selected_targets,
1921
+ tau_max=tau_max,
1922
+ return_data=return_data)
1923
+ return self
1924
+
1925
+ def predict(self, target,
1926
+ new_data=None,
1927
+ pred_params=None,
1928
+ cut_off='max_lag_or_tau_max'):
1929
+ r"""Predict target variable with fitted model.
1930
+
1931
+ Uses the model.predict() function of the sklearn model.
1932
+
1933
+ If target is an int, the predicted time series is returned. If target
1934
+ is a list of integers, then a list of predicted time series is returned.
1935
+ If the list of integers equals range(N), then an array of shape (T, N)
1936
+ of the predicted series is returned.
1937
+
1938
+ Parameters
1939
+ ----------
1940
+ target : int or list of integers
1941
+ Index or indices of target variable(s).
1942
+ new_data : data object, optional
1943
+ New Tigramite dataframe object with optional new mask. Note that
1944
+ the data will be cut off according to cut_off, see parameter
1945
+ `cut_off` below.
1946
+ pred_params : dict, optional
1947
+ Optional parameters passed on to sklearn prediction function.
1948
+ cut_off : {'2xtau_max', 'max_lag', 'max_lag_or_tau_max'}
1949
+ How many samples to cutoff at the beginning. The default is
1950
+ '2xtau_max', which guarantees that MCI tests are all conducted on
1951
+ the same samples. For modeling, 'max_lag_or_tau_max' can be used,
1952
+ which uses the maximum of tau_max and the conditions, which is
1953
+ useful to compare multiple models on the same sample. Last,
1954
+ 'max_lag' uses as much samples as possible.
1955
+
1956
+ Returns
1957
+ -------
1958
+ Results from prediction.
1959
+ """
1960
+
1961
+ if isinstance(target, int):
1962
+ target_list = [target]
1963
+ elif isinstance(target, list):
1964
+ target_list = target
1965
+ else:
1966
+ raise ValueError("target must be either int or list of integers "
1967
+ "indicating the index of the variables to "
1968
+ "predict.")
1969
+
1970
+ if target_list == list(range(self.N)):
1971
+ return_type = 'array'
1972
+ elif len(target_list) == 1:
1973
+ return_type = 'series'
1974
+ else:
1975
+ return_type = 'list'
1976
+
1977
+ pred_list = []
1978
+ self.stored_test_array = {}
1979
+ for target in target_list:
1980
+ # Print message
1981
+ if self.verbosity > 0:
1982
+ print("\n##\n## Predicting target %s\n##" % target)
1983
+ if pred_params is not None:
1984
+ for key in list(pred_params):
1985
+ print("%s = %s" % (key, pred_params[key]))
1986
+ # Default value for pred_params
1987
+ if pred_params is None:
1988
+ pred_params = {}
1989
+ # Check this is a valid target
1990
+ if target not in self.selected_targets:
1991
+ raise ValueError("Target %s not yet fitted" % target)
1992
+ # Construct the array form of the data
1993
+ Y = [(target, 0)] # dummy
1994
+ X = [(target, 0)] # dummy
1995
+ Z = self.target_predictors[target]
1996
+
1997
+ # Check if we've passed a new dataframe object
1998
+ if new_data is not None:
1999
+ # if new_data.mask is None:
2000
+ # # if no mask is supplied, use the same mask as for the fitted array
2001
+ # new_data_mask = self.test_mask
2002
+ # else:
2003
+ new_data_mask = new_data.mask
2004
+ test_array, _, _ = new_data.construct_array(X, Y, Z,
2005
+ tau_max=self.tau_max,
2006
+ mask=new_data_mask,
2007
+ mask_type=self.mask_type,
2008
+ cut_off=cut_off,
2009
+ remove_overlaps=True,
2010
+ verbosity=self.verbosity)
2011
+ # Otherwise use the default values
2012
+ else:
2013
+ test_array, _, _ = \
2014
+ self.dataframe.construct_array(X, Y, Z,
2015
+ tau_max=self.tau_max,
2016
+ mask=self.test_mask,
2017
+ mask_type=self.mask_type,
2018
+ cut_off=cut_off,
2019
+ remove_overlaps=True,
2020
+ verbosity=self.verbosity)
2021
+ # Transform the data if needed
2022
+ a_transform = self.fitted_model[target]['data_transform']
2023
+ if a_transform is not None:
2024
+ test_array = a_transform.transform(X=test_array.T).T
2025
+ # Cache the test array
2026
+ self.stored_test_array[target] = test_array
2027
+ # Run the predictor
2028
+ predicted = self.fitted_model[target]['model'].predict(
2029
+ X=test_array[2:].T, **pred_params)
2030
+
2031
+ if test_array[2:].size == 0:
2032
+ # If there are no predictors, return the value of
2033
+ # empty_predictors_function, which is np.mean
2034
+ # and expand to the test array length
2035
+ predicted = predicted * np.ones(test_array.shape[1])
2036
+
2037
+ pred_list.append(predicted)
2038
+
2039
+ if return_type == 'series':
2040
+ return pred_list[0]
2041
+ elif return_type == 'list':
2042
+ return pred_list
2043
+ elif return_type == 'array':
2044
+ return np.array(pred_list).transpose()
2045
+
2046
+ def get_train_array(self, j):
2047
+ """Returns training array for variable j."""
2048
+ return self.fitted_model[j]['data']
2049
+
2050
+ def get_test_array(self, j):
2051
+ """Returns test array for variable j."""
2052
+ return self.stored_test_array[j]
2053
+
2054
+ if __name__ == '__main__':
2055
+
2056
+ import tigramite
2057
+ import tigramite.data_processing as pp
2058
+ from tigramite.toymodels import structural_causal_processes as toys
2059
+ from tigramite.independence_tests.parcorr import ParCorr
2060
+ import tigramite.plotting as tp
2061
+
2062
+ from sklearn.linear_model import LinearRegression, LogisticRegression
2063
+ from sklearn.multioutput import MultiOutputRegressor
2064
+
2065
+ def lin_f(x): return x
2066
+
2067
+
2068
+ T = 1000
2069
+ def lin_f(x): return x
2070
+ auto_coeff = 0.
2071
+ coeff = 2.
2072
+ links = {
2073
+ 0: [((0, -1), auto_coeff, lin_f)],
2074
+ 1: [((1, -1), auto_coeff, lin_f), ((0, 0), coeff, lin_f)],
2075
+ }
2076
+ data, nonstat = toys.structural_causal_process(links, T=T,
2077
+ noises=None, seed=7)
2078
+
2079
+ # data[:,1] = data[:,1] > 0.
2080
+
2081
+ # # Create some missing values
2082
+ # data[-10:,:] = 999.
2083
+ # var_names = range(2)
2084
+
2085
+ # graph = np.array([['', '-->'],
2086
+ # ['<--', '']],
2087
+ # dtype='<U3')
2088
+ print(data, data.mean(axis=0))
2089
+ dataframe = pp.DataFrame(data,
2090
+ # vector_vars={0:[(0,0), (1,0)], 1:[(2,0), (3,0)]}
2091
+ )
2092
+ graph = toys.links_to_graph(links, tau_max=4)
2093
+
2094
+ # # We are interested in lagged total effect of X on Y
2095
+ X = [(0, 0), (0, -1)]
2096
+ Y = [(1, 0), (1, -1)]
2097
+
2098
+ model = Models(dataframe=dataframe,
2099
+ model = LinearRegression(),
2100
+ # model = LogisticRegression(),
2101
+ # model = MultiOutputRegressor(LogisticRegression()),
2102
+
2103
+ )
2104
+
2105
+ model.get_general_fitted_model(
2106
+ Y=Y, X=X, Z=[(0, -2)],
2107
+ conditions=[(0, -3)],
2108
+ tau_max=7,
2109
+ cut_off='tau_max',
2110
+ empty_predictors_function=np.mean,
2111
+ return_data=False)
2112
+
2113
+ # print(model.fit_results[(1, 0)]['model'].coef_)
2114
+
2115
+ dox_vals = np.array([0.]) #np.linspace(-1., 1., 1)
2116
+ intervention_data = np.tile(dox_vals.reshape(len(dox_vals), 1), len(X))
2117
+
2118
+ conditions_data = np.tile(1. + dox_vals.reshape(len(dox_vals), 1), 1)
2119
+
2120
+ def aggregation_func(x, axis=0, bins=2):
2121
+ x = x.astype('int64')
2122
+ return np.apply_along_axis(np.bincount, axis=axis, arr=x, minlength=bins).T
2123
+ aggregation_func = np.mean
2124
+
2125
+ pred = model.get_general_prediction(
2126
+ intervention_data=intervention_data,
2127
+ conditions_data=conditions_data,
2128
+ pred_params=None,
2129
+ transform_interventions_and_prediction=False,
2130
+ return_further_pred_results=False,
2131
+ aggregation_func=aggregation_func,
2132
+ )
2133
+
2134
+ print("\n", pred)
2135
+
2136
+ # T = 1000
2137
+
2138
+ # links = {0: [((0, -1), 0.9, lin_f)],
2139
+ # 1: [((1, -1), 0.9, lin_f), ((0, 0), -0.8, lin_f)],
2140
+ # 2: [((2, -1), 0.9, lin_f), ((0, 0), 0.9, lin_f), ((1, 0), 0.8, lin_f)],
2141
+ # # 3: [((3, -1), 0.9, lin_f), ((1, 0), 0.8, lin_f), ((2, 0), -0.9, lin_f)]
2142
+ # }
2143
+ # # noises = [np.random.randn for j in links.keys()]
2144
+ # data, nonstat = toys.structural_causal_process(links, T=T, noises=None, seed=7)
2145
+
2146
+ # missing_flag = 999
2147
+ # for i in range(0, 20):
2148
+ # data[i::100] = missing_flag
2149
+
2150
+ # # mask = data>0
2151
+
2152
+ # parents = toys._get_true_parent_neighbor_dict(links)
2153
+ # dataframe = pp.DataFrame(data, missing_flag = missing_flag)
2154
+
2155
+
2156
+
2157
+ # model = LinearRegression()
2158
+ # model.fit(X=np.random.randn(10,2), y=np.random.randn(10))
2159
+ # model.predict(X=np.random.randn(10,2)[:,2:])
2160
+ # sys.exit(0)
2161
+
2162
+ # med = LinearMediation(dataframe=dataframe, #mask_type='y',
2163
+ # data_transform=None)
2164
+ # med.fit_model(all_parents=parents, tau_max=None, return_data=True)
2165
+
2166
+ # print(med.get_residuals_cov_mean())
2167
+
2168
+ # med.fit_model_bootstrap(
2169
+ # boot_blocklength='cube_root',
2170
+ # seed = 42,
2171
+ # )
2172
+
2173
+ # # print(med.get_val_matrix())
2174
+
2175
+ # print (med.get_ce(i=0, tau=0, j=3))
2176
+ # print(med.get_bootstrap_of(function='get_ce',
2177
+ # function_args={'i':0, 'tau':0, 'j':3}, conf_lev=0.9))
2178
+
2179
+ # print (med.get_coeff(i=0, tau=-2, j=1))
2180
+
2181
+ # print (med.get_ce_max(i=0, j=2))
2182
+ # print (med.get_ce(i=0, tau=0, j=3))
2183
+ # print (med.get_mce(i=0, tau=0, k=[2], j=3))
2184
+ # print (med.get_mce(i=0, tau=0, k=[1,2], j=3) - med.get_mce(i=0, tau=0, k=[1], j=3))
2185
+ # print (med.get_conditional_mce(i=0, tau=0, k=[2], notk=[1], j=3))
2186
+ # print (med.get_bootstrap_of('get_conditional_mce', {'i':0, 'tau':0, 'k':[2], 'notk':[1], 'j':3}))
2187
+
2188
+ # print(med.get_joint_ce(i=0, j=2))
2189
+ # print(med.get_joint_mce(i=0, j=2, k=1))
2190
+
2191
+ # print(med.get_joint_ce_matrix(i=0, j=2))
2192
+
2193
+ # i=0; tau=4; j=2
2194
+ # graph_data = med.get_mediation_graph_data(i=i, tau=tau, j=j)
2195
+ # tp.plot_mediation_time_series_graph(
2196
+ # # var_names=var_names,
2197
+ # path_node_array=graph_data['path_node_array'],
2198
+ # tsg_path_val_matrix=graph_data['tsg_path_val_matrix']
2199
+ # )
2200
+ # tp.plot_mediation_graph(
2201
+ # # var_names=var_names,
2202
+ # path_val_matrix=graph_data['path_val_matrix'],
2203
+ # path_node_array=graph_data['path_node_array'],
2204
+ # );
2205
+ # plt.show()
2206
+
2207
+ # print ("Average Causal Effect X=%.2f, Y=%.2f, Z=%.2f " % tuple(med.get_all_ace()))
2208
+ # print ("Average Causal Susceptibility X=%.2f, Y=%.2f, Z=%.2f " % tuple(med.get_all_acs()))
2209
+ # print ("Average Mediated Causal Effect X=%.2f, Y=%.2f, Z=%.2f " % tuple(med.get_all_amce()))
2210
+ # med = Models(dataframe=dataframe, model=sklearn.linear_model.LinearRegression(), data_transform=None)
2211
+ # # Fit the model
2212
+ # med.get_fit(all_parents=true_parents, tau_max=3)
2213
+
2214
+ # print(med.get_val_matrix())
2215
+
2216
+ # for j, i, tau, coeff in toys._iter_coeffs(links):
2217
+ # print(i, j, tau, coeff, med.get_coeff(i=i, tau=tau, j=j))
2218
+
2219
+ # for causal_coeff in [med.get_ce(i=0, tau=-2, j=2),
2220
+ # med.get_mce(i=0, tau=-2, j=2, k=1)]:
2221
+ # print(causal_coeff)
2222
+
2223
+
2224
+ # pred = Prediction(dataframe=dataframe,
2225
+ # cond_ind_test=ParCorr(), #CMIknn ParCorr
2226
+ # prediction_model = sklearn.linear_model.LinearRegression(),
2227
+ # # prediction_model = sklearn.gaussian_process.GaussianProcessRegressor(),
2228
+ # # prediction_model = sklearn.neighbors.KNeighborsRegressor(),
2229
+ # data_transform=sklearn.preprocessing.StandardScaler(),
2230
+ # train_indices= list(range(int(0.8*T))),
2231
+ # test_indices= list(range(int(0.8*T), T)),
2232
+ # verbosity=0
2233
+ # )
2234
+
2235
+ # # predictors = pred.get_predictors(
2236
+ # # selected_targets=[2],
2237
+ # # selected_links=None,
2238
+ # # steps_ahead=1,
2239
+ # # tau_max=1,
2240
+ # # pc_alpha=0.2,
2241
+ # # max_conds_dim=None,
2242
+ # # max_combinations=1)
2243
+ # predictors = {0: [], # [(0, -1)],
2244
+ # 1: [(1, -1), (0, -1)],
2245
+ # 2: [(2, -1), (1, 0)]}
2246
+ # pred.fit(target_predictors=predictors,
2247
+ # selected_targets=None, tau_max=None, return_data=False)
2248
+
2249
+ # res = pred.predict(target=0,
2250
+ # new_data=None,
2251
+ # pred_params=None,
2252
+ # cut_off='max_lag_or_tau_max')
2253
+
2254
+ # print(data[:,2])
2255
+ # print(res)
2256
+
2257
+