PyProcessMacro 1.0.14__tar.gz → 2.0.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyProcessMacro
3
- Version: 1.0.14
3
+ Version: 2.0.0
4
4
  Summary: A Python library for moderation, mediation and conditional process analysis. Based on Andrew F. Hayes' Process Macro.
5
5
  Author-email: Quentin André <quentin.andre@insead.edu>
6
6
  License-Expression: MIT
@@ -29,6 +29,7 @@ Requires-Dist: matplotlib>=3.7
29
29
  Requires-Dist: seaborn>=0.13
30
30
  Provides-Extra: test
31
31
  Requires-Dist: pytest>=8; extra == "test"
32
+ Requires-Dist: statsmodels>=0.14; extra == "test"
32
33
  Dynamic: license-file
33
34
 
34
35
  PyProcessMacro: A Python Implementation of Andrew F. Hayes' 'Process' Macro
@@ -68,7 +69,8 @@ In the current version, PyProcessMacro replicates the following features from th
68
69
  * All statistics reported by Process:
69
70
  * Variable parameters for outcome models
70
71
  * (Conditional) direct and indirect effects
71
- * Indices for Partial/Conditional/Moderated Moderated Mediation are always reported if the model supports them.
72
+ * The index of moderated mediation and, following PROCESS 3, the indices of partial, conditional and
73
+ moderated moderated mediation, whenever the indirect effect is linear in the moderator(s).
72
74
  * Automatic generation of spotlight values for continuous/discrete moderators.
73
75
  * Rich set of options to tweak the estimation and display of the different models: (almost) all the options from
74
76
  Process exist in PyProcessMacro. Check the doc for more details.
@@ -94,6 +96,38 @@ In the current version, the following features have not yet been ported to PyPro
94
96
  * Some options (`normal`, `varorder`, ...). PyProcessMacro will issue a warning to tell you if an option you are
95
97
  trying to use is not implemented.
96
98
 
99
+ # Upgrading to 2.0
100
+
101
+ Version 2.0 corrects several statistics and tightens input handling. Reported numbers change in these ways:
102
+
103
+ * Confidence intervals of OLS coefficients and of (conditional) direct effects use t critical values with the
104
+ residual degrees of freedom, as PROCESS does. They were based on z, so they widen slightly; the difference is
105
+ visible in small samples.
106
+ * Adjusted R² of OLS outcome models is slightly higher: the previous value used one degree of freedom too many.
107
+ * Cox-Snell and Nagelkerke pseudo R² of logistic outcome models are finite for large samples instead of NaN.
108
+ * No index of moderated mediation is reported when a moderator sits on both the X-to-M and the M-to-Y paths
109
+ (models 58 to 73, 75 and 76), matching PROCESS: the indirect effect is not linear in such a moderator. The
110
+ `*_index_summary()` methods raise `NotImplementedError` for those models.
111
+ * The sample size reported after listwise deletion is the number of rows kept.
112
+
113
+ Behaviour that used to be silent now speaks up:
114
+
115
+ * A misspelled key in `modval`, or a keyword argument that is neither a variable nor an option, raises an error
116
+ instead of being ignored.
117
+ * Unsupported PROCESS options (`jn`, `effsize`, `mc`, `normal`, ...) raise a visible `UserWarning`.
118
+ * A logistic regression that does not converge raises `pyprocessmacro.ConvergenceError`. Bootstrap resamples
119
+ that fail are counted, and the bootstrap stops with an error if more resamples fail than were requested.
120
+
121
+ Removed and added:
122
+
123
+ * `plot_direct_effects()` and `plot_indirect_effects()` are removed; use `plot_conditional_direct_effects()`
124
+ and `plot_conditional_indirect_effects()`.
125
+ * `cov_type` selects the OLS covariance estimator (`"standard"`, `"HC0"`, `"HC1"`, `"HC2"` or `"HC3"`);
126
+ `hc3=True` remains as shorthand for `"HC3"`.
127
+ * `seed=None` draws a different bootstrap sample on every run, and `seed=0` is accepted.
128
+ * `Process.dv` names the outcome variable (`iv` is kept for compatibility).
129
+ * Python 3.11 or newer is required (since 1.0.14).
130
+
97
131
  # Version History
98
132
 
99
133
  ## Master Versions
@@ -117,7 +151,7 @@ report and for the fix.
117
151
 
118
152
  ### 1.0.4
119
153
  **Bug fix for standard error estimate in all models**
120
- PyProcessMacro was, by default, using the HC3 estimator for the variance-covariance matrix instead of the HC0 estimator.
154
+ PyProcessMacro was, by default, using the HC3 estimator for the variance-covariance matrix instead of the standard (non-robust) estimator.
121
155
  This has now been changed. To continue using the HC3 estimator, specify `hc3=True` when initializing the Process instance.
122
156
  Thanks to Zoé Ziani for the bug report.
123
157
 
@@ -274,6 +308,17 @@ p = Process(data=df, model=13, x="Effort", y="Success", w="Motivation", z="Skill
274
308
  p.summary()
275
309
  ````
276
310
 
311
+ ### F. Choosing the covariance estimator
312
+
313
+ By default, the standard errors of the OLS outcome models use the standard (homoskedastic) estimator. The
314
+ `cov_type` argument selects a heteroskedasticity-consistent estimator instead: `"HC0"`, `"HC1"`, `"HC2"` or
315
+ `"HC3"`. `hc3=True` is shorthand for `cov_type="HC3"`, which is what the original Process macro uses when
316
+ `hc3=1` is specified. Logistic outcome models always use the inverse of the Hessian.
317
+
318
+ ````python
319
+ p = Process(data=df, model=4, x="Effort", y="Success", m=["MediationSkills"], cov_type="HC3")
320
+ ````
321
+
277
322
  ## 2. Accessing the estimation results
278
323
 
279
324
  After the `Process` object is initialized, you are not limited to printing the summary. PyProcessMacro implements the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyProcessMacro
3
- Version: 1.0.14
3
+ Version: 2.0.0
4
4
  Summary: A Python library for moderation, mediation and conditional process analysis. Based on Andrew F. Hayes' Process Macro.
5
5
  Author-email: Quentin André <quentin.andre@insead.edu>
6
6
  License-Expression: MIT
@@ -29,6 +29,7 @@ Requires-Dist: matplotlib>=3.7
29
29
  Requires-Dist: seaborn>=0.13
30
30
  Provides-Extra: test
31
31
  Requires-Dist: pytest>=8; extra == "test"
32
+ Requires-Dist: statsmodels>=0.14; extra == "test"
32
33
  Dynamic: license-file
33
34
 
34
35
  PyProcessMacro: A Python Implementation of Andrew F. Hayes' 'Process' Macro
@@ -68,7 +69,8 @@ In the current version, PyProcessMacro replicates the following features from th
68
69
  * All statistics reported by Process:
69
70
  * Variable parameters for outcome models
70
71
  * (Conditional) direct and indirect effects
71
- * Indices for Partial/Conditional/Moderated Moderated Mediation are always reported if the model supports them.
72
+ * The index of moderated mediation and, following PROCESS 3, the indices of partial, conditional and
73
+ moderated moderated mediation, whenever the indirect effect is linear in the moderator(s).
72
74
  * Automatic generation of spotlight values for continuous/discrete moderators.
73
75
  * Rich set of options to tweak the estimation and display of the different models: (almost) all the options from
74
76
  Process exist in PyProcessMacro. Check the doc for more details.
@@ -94,6 +96,38 @@ In the current version, the following features have not yet been ported to PyPro
94
96
  * Some options (`normal`, `varorder`, ...). PyProcessMacro will issue a warning to tell you if an option you are
95
97
  trying to use is not implemented.
96
98
 
99
+ # Upgrading to 2.0
100
+
101
+ Version 2.0 corrects several statistics and tightens input handling. Reported numbers change in these ways:
102
+
103
+ * Confidence intervals of OLS coefficients and of (conditional) direct effects use t critical values with the
104
+ residual degrees of freedom, as PROCESS does. They were based on z, so they widen slightly; the difference is
105
+ visible in small samples.
106
+ * Adjusted R² of OLS outcome models is slightly higher: the previous value used one degree of freedom too many.
107
+ * Cox-Snell and Nagelkerke pseudo R² of logistic outcome models are finite for large samples instead of NaN.
108
+ * No index of moderated mediation is reported when a moderator sits on both the X-to-M and the M-to-Y paths
109
+ (models 58 to 73, 75 and 76), matching PROCESS: the indirect effect is not linear in such a moderator. The
110
+ `*_index_summary()` methods raise `NotImplementedError` for those models.
111
+ * The sample size reported after listwise deletion is the number of rows kept.
112
+
113
+ Behaviour that used to be silent now speaks up:
114
+
115
+ * A misspelled key in `modval`, or a keyword argument that is neither a variable nor an option, raises an error
116
+ instead of being ignored.
117
+ * Unsupported PROCESS options (`jn`, `effsize`, `mc`, `normal`, ...) raise a visible `UserWarning`.
118
+ * A logistic regression that does not converge raises `pyprocessmacro.ConvergenceError`. Bootstrap resamples
119
+ that fail are counted, and the bootstrap stops with an error if more resamples fail than were requested.
120
+
121
+ Removed and added:
122
+
123
+ * `plot_direct_effects()` and `plot_indirect_effects()` are removed; use `plot_conditional_direct_effects()`
124
+ and `plot_conditional_indirect_effects()`.
125
+ * `cov_type` selects the OLS covariance estimator (`"standard"`, `"HC0"`, `"HC1"`, `"HC2"` or `"HC3"`);
126
+ `hc3=True` remains as shorthand for `"HC3"`.
127
+ * `seed=None` draws a different bootstrap sample on every run, and `seed=0` is accepted.
128
+ * `Process.dv` names the outcome variable (`iv` is kept for compatibility).
129
+ * Python 3.11 or newer is required (since 1.0.14).
130
+
97
131
  # Version History
98
132
 
99
133
  ## Master Versions
@@ -117,7 +151,7 @@ report and for the fix.
117
151
 
118
152
  ### 1.0.4
119
153
  **Bug fix for standard error estimate in all models**
120
- PyProcessMacro was, by default, using the HC3 estimator for the variance-covariance matrix instead of the HC0 estimator.
154
+ PyProcessMacro was, by default, using the HC3 estimator for the variance-covariance matrix instead of the standard (non-robust) estimator.
121
155
  This has now been changed. To continue using the HC3 estimator, specify `hc3=True` when initializing the Process instance.
122
156
  Thanks to Zoé Ziani for the bug report.
123
157
 
@@ -274,6 +308,17 @@ p = Process(data=df, model=13, x="Effort", y="Success", w="Motivation", z="Skill
274
308
  p.summary()
275
309
  ````
276
310
 
311
+ ### F. Choosing the covariance estimator
312
+
313
+ By default, the standard errors of the OLS outcome models use the standard (homoskedastic) estimator. The
314
+ `cov_type` argument selects a heteroskedasticity-consistent estimator instead: `"HC0"`, `"HC1"`, `"HC2"` or
315
+ `"HC3"`. `hc3=True` is shorthand for `cov_type="HC3"`, which is what the original Process macro uses when
316
+ `hc3=1` is specified. Logistic outcome models always use the inverse of the Hessian.
317
+
318
+ ````python
319
+ p = Process(data=df, model=4, x="Effort", y="Success", m=["MediationSkills"], cov_type="HC3")
320
+ ````
321
+
277
322
  ## 2. Accessing the estimation results
278
323
 
279
324
  After the `Process` object is initialized, you are not limited to printing the summary. PyProcessMacro implements the
@@ -9,8 +9,5 @@ PyProcessMacro.egg-info/requires.txt
9
9
  PyProcessMacro.egg-info/top_level.txt
10
10
  pyprocessmacro/__init__.py
11
11
  pyprocessmacro/models.py
12
- pyprocessmacro/models.pyi
13
12
  pyprocessmacro/process.py
14
- pyprocessmacro/process.pyi
15
- pyprocessmacro/utils.py
16
- pyprocessmacro/utils.pyi
13
+ pyprocessmacro/utils.py
@@ -6,3 +6,4 @@ seaborn>=0.13
6
6
 
7
7
  [test]
8
8
  pytest>=8
9
+ statsmodels>=0.14
@@ -35,7 +35,8 @@ In the current version, PyProcessMacro replicates the following features from th
35
35
  * All statistics reported by Process:
36
36
  * Variable parameters for outcome models
37
37
  * (Conditional) direct and indirect effects
38
- * Indices for Partial/Conditional/Moderated Moderated Mediation are always reported if the model supports them.
38
+ * The index of moderated mediation and, following PROCESS 3, the indices of partial, conditional and
39
+ moderated moderated mediation, whenever the indirect effect is linear in the moderator(s).
39
40
  * Automatic generation of spotlight values for continuous/discrete moderators.
40
41
  * Rich set of options to tweak the estimation and display of the different models: (almost) all the options from
41
42
  Process exist in PyProcessMacro. Check the doc for more details.
@@ -61,6 +62,38 @@ In the current version, the following features have not yet been ported to PyPro
61
62
  * Some options (`normal`, `varorder`, ...). PyProcessMacro will issue a warning to tell you if an option you are
62
63
  trying to use is not implemented.
63
64
 
65
+ # Upgrading to 2.0
66
+
67
+ Version 2.0 corrects several statistics and tightens input handling. Reported numbers change in these ways:
68
+
69
+ * Confidence intervals of OLS coefficients and of (conditional) direct effects use t critical values with the
70
+ residual degrees of freedom, as PROCESS does. They were based on z, so they widen slightly; the difference is
71
+ visible in small samples.
72
+ * Adjusted R² of OLS outcome models is slightly higher: the previous value used one degree of freedom too many.
73
+ * Cox-Snell and Nagelkerke pseudo R² of logistic outcome models are finite for large samples instead of NaN.
74
+ * No index of moderated mediation is reported when a moderator sits on both the X-to-M and the M-to-Y paths
75
+ (models 58 to 73, 75 and 76), matching PROCESS: the indirect effect is not linear in such a moderator. The
76
+ `*_index_summary()` methods raise `NotImplementedError` for those models.
77
+ * The sample size reported after listwise deletion is the number of rows kept.
78
+
79
+ Behaviour that used to be silent now speaks up:
80
+
81
+ * A misspelled key in `modval`, or a keyword argument that is neither a variable nor an option, raises an error
82
+ instead of being ignored.
83
+ * Unsupported PROCESS options (`jn`, `effsize`, `mc`, `normal`, ...) raise a visible `UserWarning`.
84
+ * A logistic regression that does not converge raises `pyprocessmacro.ConvergenceError`. Bootstrap resamples
85
+ that fail are counted, and the bootstrap stops with an error if more resamples fail than were requested.
86
+
87
+ Removed and added:
88
+
89
+ * `plot_direct_effects()` and `plot_indirect_effects()` are removed; use `plot_conditional_direct_effects()`
90
+ and `plot_conditional_indirect_effects()`.
91
+ * `cov_type` selects the OLS covariance estimator (`"standard"`, `"HC0"`, `"HC1"`, `"HC2"` or `"HC3"`);
92
+ `hc3=True` remains as shorthand for `"HC3"`.
93
+ * `seed=None` draws a different bootstrap sample on every run, and `seed=0` is accepted.
94
+ * `Process.dv` names the outcome variable (`iv` is kept for compatibility).
95
+ * Python 3.11 or newer is required (since 1.0.14).
96
+
64
97
  # Version History
65
98
 
66
99
  ## Master Versions
@@ -84,7 +117,7 @@ report and for the fix.
84
117
 
85
118
  ### 1.0.4
86
119
  **Bug fix for standard error estimate in all models**
87
- PyProcessMacro was, by default, using the HC3 estimator for the variance-covariance matrix instead of the HC0 estimator.
120
+ PyProcessMacro was, by default, using the HC3 estimator for the variance-covariance matrix instead of the standard (non-robust) estimator.
88
121
  This has now been changed. To continue using the HC3 estimator, specify `hc3=True` when initializing the Process instance.
89
122
  Thanks to Zoé Ziani for the bug report.
90
123
 
@@ -241,6 +274,17 @@ p = Process(data=df, model=13, x="Effort", y="Success", w="Motivation", z="Skill
241
274
  p.summary()
242
275
  ````
243
276
 
277
+ ### F. Choosing the covariance estimator
278
+
279
+ By default, the standard errors of the OLS outcome models use the standard (homoskedastic) estimator. The
280
+ `cov_type` argument selects a heteroskedasticity-consistent estimator instead: `"HC0"`, `"HC1"`, `"HC2"` or
281
+ `"HC3"`. `hc3=True` is shorthand for `cov_type="HC3"`, which is what the original Process macro uses when
282
+ `hc3=1` is specified. Logistic outcome models always use the inverse of the Hessian.
283
+
284
+ ````python
285
+ p = Process(data=df, model=4, x="Effort", y="Success", m=["MediationSkills"], cov_type="HC3")
286
+ ````
287
+
244
288
  ## 2. Accessing the estimation results
245
289
 
246
290
  After the `Process` object is initialized, you are not limited to printing the summary. PyProcessMacro implements the
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Core module for the PyProcess Macro.
4
+ """
5
+ from .process import Process
6
+ from .utils import ConvergenceError
7
+
8
+ __all__ = ["Process", "ConvergenceError"]
9
+
10
+ __version__ = "2.0.0"
@@ -16,8 +16,10 @@ from .utils import (
16
16
  eval_expression,
17
17
  bias_corrected_ci,
18
18
  z_score,
19
+ t_score,
19
20
  percentile_ci,
20
21
  find_significance_region,
22
+ ConvergenceError,
21
23
  )
22
24
 
23
25
 
@@ -103,14 +105,26 @@ class BaseLogit(object):
103
105
  return -self._hessian(params) / self._n_obs
104
106
 
105
107
  oldparams = np.inf
106
- newparams = np.repeat(0, self._n_vars)
108
+ newparams = np.zeros(self._n_vars)
107
109
  while iterations < max_iter and np.any(
108
110
  np.abs(newparams - oldparams) > tolerance
109
111
  ):
110
112
  oldparams = newparams
111
- H = hess(oldparams)
112
- newparams = oldparams - dot(inv(H), score(oldparams))
113
+ try:
114
+ H = hess(oldparams)
115
+ newparams = oldparams - dot(inv(H), score(oldparams))
116
+ except LinAlgError:
117
+ raise ConvergenceError("The Hessian of the logistic regression is singular.")
113
118
  iterations += 1
119
+ if not np.all(np.isfinite(newparams)):
120
+ raise ConvergenceError(
121
+ "The logistic regression diverged (the outcome may be perfectly separated)."
122
+ )
123
+ if np.any(np.abs(newparams - oldparams) > tolerance):
124
+ raise ConvergenceError(
125
+ f"The logistic regression did not converge in {max_iter} iterations "
126
+ "(increase 'iterate', relax 'convergence', or check the outcome for separation)."
127
+ )
114
128
  return newparams
115
129
 
116
130
 
@@ -286,7 +300,7 @@ class OLSOutcomeModel(BaseOutcomeModel):
286
300
  resid = y - dot(x, betas)
287
301
  mse = (resid ** 2).sum() / df_e
288
302
  sse = dot(resid.T, resid) / df_e
289
- errortype = "standard" if self._options["hc3"] is False else "HC3"
303
+ errortype = self._options.get("cov_type") or ("HC3" if self._options.get("hc3") else "standard")
290
304
  if errortype == "standard":
291
305
  vcv = np.true_divide(1, n_obs - n_vars) * dot(resid.T, resid) * inv_xx
292
306
  elif errortype == "HC0":
@@ -294,7 +308,7 @@ class OLSOutcomeModel(BaseOutcomeModel):
294
308
  vcv = dot(dot(dot(inv_xx, x.T) * sq_resid, x), inv_xx)
295
309
  elif errortype == "HC1":
296
310
  sq_resid = (resid ** 2).squeeze()
297
- vcv = np.true_divide(n_obs, n_obs - n_vars - 1) * dot(
311
+ vcv = np.true_divide(n_obs, n_obs - n_vars) * dot( # n_vars counts the constant (#52)
298
312
  dot(dot(inv_xx, x.T) * sq_resid, x), inv_xx
299
313
  )
300
314
  elif errortype == "HC2":
@@ -316,13 +330,13 @@ class OLSOutcomeModel(BaseOutcomeModel):
316
330
  t = betas / se
317
331
  p = stats.t.sf(np.abs(t), df_e) * 2
318
332
  conf = self._options["conf"]
319
- zscore = z_score(conf)
333
+ tcrit = t_score(conf, df_e) # OLS intervals use the t distribution, as PROCESS does (#40)
320
334
  R2 = 1 - resid.var() / y.var()
321
- adjR2 = 1 - (1 - R2) * ((n_obs - 1) / (n_obs - n_vars - 1))
335
+ adjR2 = 1 - (1 - R2) * ((n_obs - 1) / df_e) # n_vars already counts the constant (#41)
322
336
  F = (R2 / df_r) / ((1 - R2) / df_e)
323
- F_pval = 1 - stats.f.cdf(F, df_r, df_e)
324
- llci = betas - (se * zscore)
325
- ulci = betas + (se * zscore)
337
+ F_pval = stats.f.sf(F, df_r, df_e)
338
+ llci = betas - (se * tcrit)
339
+ ulci = betas + (se * tcrit)
326
340
  names = [self._symb_to_var.get(x, x) for x in self._exogvars]
327
341
  estimation_results = {
328
342
  "betas": betas,
@@ -418,19 +432,19 @@ class LogitOutcomeModel(BaseOutcomeModel, BaseLogit):
418
432
 
419
433
  # GOF statistics
420
434
  llmodel = self._loglike(betas)
421
- lmodel = np.exp(llmodel)
422
435
  minus2ll = -2 * llmodel
423
436
 
424
437
  null_model = NullLogitModel(self._endog, self._options)
425
438
  betas_null = null_model._optimize()
426
439
  llnull = null_model._loglike(betas_null)
427
- lnull = np.exp(llnull)
428
440
 
429
441
  d = 2 * (llmodel - llnull)
430
442
  pvalue = stats.chi2.sf(d, self._n_vars - 1)
431
443
  mcfadden = 1 - llmodel / llnull
432
- coxsnell = 1 - (lnull / lmodel) ** (2 / self._n_obs)
433
- nagelkerke = coxsnell / (1 - lnull ** (2 / self._n_obs))
444
+ # Likelihood ratios are taken in log space: exp(llnull) underflows to 0 beyond about a
445
+ # thousand observations, which turned both pseudo R-squared into NaN (#42).
446
+ coxsnell = 1 - np.exp(2 * (llnull - llmodel) / self._n_obs)
447
+ nagelkerke = coxsnell / (1 - np.exp(2 * llnull / self._n_obs))
434
448
  names = [self._symb_to_var.get(x, x) for x in self._exogvars]
435
449
  estimation_results = {
436
450
  "betas": betas,
@@ -650,6 +664,7 @@ class ParallelMediationModel(object):
650
664
  boot_betas_y = np.empty((n_boots, len(self._exog_terms_y)))
651
665
  boot_betas_m = np.empty((self._n_meds, n_boots, len(self._exog_terms_m)))
652
666
  n_fail_samples = 0
667
+ max_failures = n_boots # give up once more resamples failed than were requested (#49)
653
668
  boot_ind = 0
654
669
  sampler = bootstrap_sampler(self._n_obs, seed)
655
670
  while boot_ind < n_boots:
@@ -666,8 +681,14 @@ class ParallelMediationModel(object):
666
681
  m_b = self._compute_betas_m(m_e, m_x)
667
682
  boot_betas_m[j][boot_ind] = m_b
668
683
  boot_ind += 1
669
- except LinAlgError: # Hessian (Logit) or X'X (OLS) cannot be inverted
684
+ except (LinAlgError, ConvergenceError): # X'X or the Hessian is singular, or the logit diverged
670
685
  n_fail_samples += 1
686
+ if n_fail_samples > max_failures:
687
+ raise RuntimeError(
688
+ f"{n_fail_samples} bootstrap samples failed to estimate before {n_boots} succeeded. "
689
+ "The model is probably not estimable on resamples of this data (check for separation, "
690
+ "collinearity, or a very small sample)."
691
+ )
671
692
 
672
693
  return boot_betas_y, boot_betas_m, n_fail_samples
673
694
 
@@ -1585,9 +1606,12 @@ class DirectEffectModel(object):
1585
1606
  dot(grad, vcv), np.transpose(grad)
1586
1607
  ) # V(Grad(X)) = Grad(X).V(X).Grad'(X)
1587
1608
  se = np.sqrt(var)
1588
- zscore = z_score(conf)
1589
- llci = betas - (se * zscore)
1590
- ulci = betas + (se * zscore)
1609
+ if self._is_logit:
1610
+ crit = z_score(conf)
1611
+ else: # OLS intervals use the t distribution, as PROCESS does (#40)
1612
+ crit = t_score(conf, self._model.estimation_results["df_e"])
1613
+ llci = betas - (se * crit)
1614
+ ulci = betas + (se * crit)
1591
1615
  return betas, se, llci, ulci
1592
1616
 
1593
1617
  def coeff_summary(self):
@@ -16,8 +16,6 @@ from .models import (
16
16
  )
17
17
  from .utils import plot_conditional_effects, gen_moderators
18
18
 
19
- warnings.simplefilter("default")
20
-
21
19
 
22
20
  class Process(object):
23
21
  __var_kws__ = {"x", "m", "w", "z", "v", "q", "y"}
@@ -31,6 +29,7 @@ class Process(object):
31
29
  "effsize",
32
30
  "jn",
33
31
  "hc3",
32
+ "cov_type",
34
33
  "controls_in",
35
34
  "total",
36
35
  "center",
@@ -564,6 +563,7 @@ class Process(object):
564
563
  effsize=False,
565
564
  jn=False,
566
565
  hc3=False,
566
+ cov_type="standard",
567
567
  controls=None,
568
568
  controls_in="all",
569
569
  total=False,
@@ -601,7 +601,8 @@ class Process(object):
601
601
  :param boot: int
602
602
  The number of bootstrap repetitions for the estimation of the SE and CI in indirect effects.
603
603
  :param seed: int
604
- The seed to use for bootstrap samples. Specify an integer between 0 and 1e10 for a replicable seed.
604
+ The seed of the bootstrap sampler: an integer between 0 and 2**32 - 1 for reproducible samples,
605
+ or None for a different draw on every run.
605
606
  :param conf: int
606
607
  A value between 51 and 99, representing the desired level of confidence for the confidence intervals
607
608
  :param effsize: bool
@@ -610,6 +611,11 @@ class Process(object):
610
611
  If True, the Johnson-Neymann region of significance will be reported.
611
612
  :param hc3: bool
612
613
  If True, the HC3 estimator will be used for the variance/covariance matrix of the parameters.
614
+ Shorthand for cov_type="HC3".
615
+ :param cov_type: "standard", "HC0", "HC1", "HC2" or "HC3"
616
+ The estimator of the variance/covariance matrix of the OLS parameters: the standard homoskedastic
617
+ estimator, or one of the heteroskedasticity-consistent estimators. Logistic outcome models always
618
+ use the inverse of the Hessian.
613
619
  :param controls: list of string
614
620
  A list of control variables to include to the model(s).
615
621
  :param controls_in: "all", "x_to_m", "all_to_y"
@@ -642,55 +648,65 @@ class Process(object):
642
648
  :param precision:
643
649
  The number of decimal places to display in the summary of the model results.
644
650
  """
645
- if kwargs.pop("mc", None):
651
+ if mc:
646
652
  warnings.warn(
647
- "The argument 'mc' for Monte-Carlo simulations is not supported",
648
- DeprecationWarning,
653
+ "The argument 'mc' for Monte-Carlo confidence intervals is not supported; "
654
+ "bootstrap confidence intervals are used.",
655
+ UserWarning,
656
+ stacklevel=2,
649
657
  )
650
658
  if kwargs.pop("normal", None):
651
659
  warnings.warn(
652
660
  "The argument 'normal' for normal theory tests is not supported. "
653
661
  "Bootstrapped CI are recommended.",
654
- DeprecationWarning,
662
+ UserWarning,
663
+ stacklevel=2,
655
664
  )
656
665
  if kwargs.pop("varorder", None):
657
666
  warnings.warn(
658
667
  "The argument 'varorder' for normal theory tests is not supported. "
659
668
  "Bootstrapped CI are recommended.",
660
- DeprecationWarning,
669
+ UserWarning,
670
+ stacklevel=2,
661
671
  )
662
672
  if kwargs.pop("varlist", None):
663
673
  warnings.warn(
664
674
  "The 'varlist' is not required. To specify controls, use the 'controls' arguments",
665
- DeprecationWarning,
675
+ UserWarning,
676
+ stacklevel=2,
666
677
  )
667
678
  if kwargs.pop("coeffci", None):
668
679
  warnings.warn(
669
- "The argument 'coeffci' is not supported.", DeprecationWarning
680
+ "The argument 'coeffci' is not supported.",
681
+ UserWarning,
682
+ stacklevel=2,
670
683
  )
671
684
  if kwargs.pop("plot", None):
672
685
  warnings.warn(
673
686
  "The argument 'plot' is not supported. Check the 'plot_conditional_direct_effects() and"
674
687
  "'plot_conditional_indirect_effects()' methods instead.",
675
- DeprecationWarning,
688
+ UserWarning,
689
+ stacklevel=2,
676
690
  )
677
691
  if kwargs.pop("save", None):
678
692
  warnings.warn(
679
693
  "The argument 'save' is not supported. Call the 'get_bootstrap_estimates() method to recover"
680
694
  "the bootstrap samples instead.",
681
- DeprecationWarning,
695
+ UserWarning,
696
+ stacklevel=2,
682
697
  )
683
- if kwargs.pop("effsize", None):
698
+ if effsize:
684
699
  warnings.warn(
685
- "The argument 'effsize' for effect sizes is not supported yet."
686
- "It is coming in future versions of PyProcessMacro.",
687
- SyntaxWarning,
700
+ "The argument 'effsize' for effect sizes is not supported and is ignored.",
701
+ UserWarning,
702
+ stacklevel=2,
688
703
  )
689
- if kwargs.pop("jn", None):
704
+ if jn:
690
705
  warnings.warn(
691
- "The argument 'jn' for the Johnson-Neyman region of significance is not supported."
692
- "Call the 'floodlight_direct_effect()' and 'floodlight_indirect_effect()' methods instead.",
693
- DeprecationWarning,
706
+ "The argument 'jn' for the Johnson-Neyman region of significance is not supported and is "
707
+ "ignored. Call the 'floodlight_direct_effect()' and 'floodlight_indirect_effect()' methods instead.",
708
+ UserWarning,
709
+ stacklevel=2,
694
710
  )
695
711
 
696
712
  if model == 6:
@@ -720,6 +736,12 @@ class Process(object):
720
736
 
721
737
  # Check the congruence between the model specifications, the model number, and the data, and store the final
722
738
  # list of variables used
739
+ unknown_kwargs = set(kwargs) - self.__var_kws__
740
+ if unknown_kwargs:
741
+ raise TypeError(
742
+ f"Process() got unexpected keyword argument(s): {', '.join(sorted(unknown_kwargs))}. "
743
+ "Variables are x, y, m, w, z, v and q; check the spelling of the options."
744
+ )
723
745
  var_kwargs = {k: v for k, v in kwargs.items() if k in self.__var_kws__}
724
746
 
725
747
  # _gen_valid_varlist normalizes every variable argument to a list, so the mediator and
@@ -763,7 +785,8 @@ class Process(object):
763
785
  self.outcome_models = self._gen_outcome_models()
764
786
 
765
787
  # Rename the dictionary of custom spotlight values, and generating the spotlight values.
766
- modval_symb = {self._var_to_symb.get(k): v for k, v in modval.items()}
788
+ self._check_moderator_names(modval, "modval")
789
+ modval_symb = {self._var_to_symb[k]: v for k, v in modval.items()}
767
790
  self._spotlight_values = self._gen_spotlight_values(modval_symb)
768
791
 
769
792
  # Generate the direct model.
@@ -779,6 +802,20 @@ class Process(object):
779
802
  if not suppr_init:
780
803
  self._print_init()
781
804
 
805
+ def _check_moderator_names(self, names, argument):
806
+ """
807
+ Raise a ValueError if any of the names is not a moderator of the model (#46).
808
+ :param names: iterable of variable names
809
+ :param argument: the name of the argument being validated, for the error message
810
+ """
811
+ moderators = {self._symb_to_var[s] for s in self._moderators["all"]}
812
+ unknown = [str(n) for n in names if n not in moderators]
813
+ if unknown:
814
+ raise ValueError(
815
+ f"The variable(s) {', '.join(unknown)} in '{argument}' are not moderators of Model "
816
+ f"{self.model_num}. Moderators of this model: {', '.join(sorted(moderators)) or 'none'}."
817
+ )
818
+
782
819
  def _gen_valid_options(self, arguments):
783
820
  """
784
821
  Validate the arguments specified for the different options used in Process.
@@ -793,8 +830,8 @@ class Process(object):
793
830
  "The option 'conf' must be an integer between 50 and 100, exclusive.\n"
794
831
  )
795
832
 
796
- if not isinstance(seed, int) or ((seed <= 0) or (seed >= 1e9)):
797
- errstr += "The option 'seed' must be an integer between 0 and 1 000 000 000, exclusive.\n"
833
+ if seed is not None and (not isinstance(seed, (int, np.integer)) or not (0 <= seed <= 2**32 - 1)):
834
+ errstr += "The option 'seed' must be None or an integer between 0 and 2**32 - 1.\n"
798
835
 
799
836
  if options["contrast"] not in [True, False]:
800
837
  errstr += "The option 'contrast' must be 'True' or 'False'.\n"
@@ -804,6 +841,12 @@ class Process(object):
804
841
  errstr += "The option 'jn' must be 'True' or 'False'.\n"
805
842
  if options["hc3"] not in [True, False]:
806
843
  errstr += "The option 'hc3' must be 'True' or 'False'.\n"
844
+ if options["cov_type"] not in ["standard", "HC0", "HC1", "HC2", "HC3"]:
845
+ errstr += "The option 'cov_type' must be one of 'standard', 'HC0', 'HC1', 'HC2' or 'HC3'.\n"
846
+ elif options["hc3"] is True:
847
+ if options["cov_type"] not in ["standard", "HC3"]:
848
+ errstr += "The options hc3=True and cov_type disagree; use one or the other.\n"
849
+ options["cov_type"] = "HC3" # hc3 is shorthand for cov_type="HC3" (#52)
807
850
  if options["center"] not in [True, False]:
808
851
  errstr += "The option 'center' must be 'True' or 'False'.\n"
809
852
  if options["quantile"] not in [True, False]:
@@ -996,9 +1039,9 @@ class Process(object):
996
1039
  """
997
1040
  # Subset the data to the columns used in the model
998
1041
  data = self._data[self.varlist].copy()
999
- n_obs_before = self._data.shape[0]
1000
- data = data.dropna().reset_index()
1001
- n_obs_after = self._data.shape[0]
1042
+ n_obs_before = data.shape[0]
1043
+ data = data.dropna().reset_index(drop=True)
1044
+ n_obs_after = data.shape[0] # rows that survived dropna (#44)
1002
1045
  n_obs_null = n_obs_before - n_obs_after
1003
1046
 
1004
1047
  # Map each variable name to a unique variable code, and rename the columns in the data.)
@@ -1224,7 +1267,9 @@ class Process(object):
1224
1267
  1. If the two moderators are on two different paths (X to M, or M to Y): both CMM and MMM are reported.
1225
1268
  2. If the two moderators are on the same path and form a 3-way interaction: both CMM and MMM are reported.
1226
1269
  3. If the two moderators are on the same path and do not form a 3-way: the PMM is reported.
1227
- 4. If at least one of the two moderators is present on both paths: no analysis is reported.
1270
+ 4. If a moderator is present on both paths (models 58 to 73, 75 and 76): no index is reported, because
1271
+ the indirect effect is not linear in that moderator and the indices assume it is.
1272
+ MM and PMM match PROCESS 2.16. MMM and CMM follow PROCESS 3 (Hayes, 2018), which 2.16 did not report.
1228
1273
 
1229
1274
  This function returns the list of additional analysis to report. If no additional analysis must be performed,
1230
1275
  this list is empty.
@@ -1240,6 +1285,11 @@ class Process(object):
1240
1285
  if n_mods_ind == 0: # No moderators on indirect path, so no additional analysis.
1241
1286
  return []
1242
1287
 
1288
+ # Rule 4: a moderator on both the X-to-M and the M-to-Y paths makes the indirect effect quadratic in
1289
+ # that moderator, and every index below assumes linearity. PROCESS reports no index then (#43).
1290
+ if self._moderators["x_indirect"] & self._moderators["m"]:
1291
+ return []
1292
+
1243
1293
  terms = y_exogvars + m_exogvars
1244
1294
  threeway = any(
1245
1295
  [1 if len(term.split("*")) == 3 else 0 for term in terms]
@@ -1269,6 +1319,7 @@ class Process(object):
1269
1319
  :param path:
1270
1320
  :return:
1271
1321
  """
1322
+ self._check_moderator_names(modval, "modval")
1272
1323
  modval_symb = {self._var_to_symb[k]: v for k, v in modval.items()}
1273
1324
  spotlight_values_symb = self._spotlight_values.copy()
1274
1325
 
@@ -1350,13 +1401,19 @@ class Process(object):
1350
1401
  m_var = self._symb_to_var[m]
1351
1402
  if modval_parsed.get(m_var) is None:
1352
1403
  warnings.warn(
1353
- f"The moderator {m_var} exerts an influence on the effect, but is not specified as a factor on\
1354
- the graph. Its value has been explicitely set to 0.",
1355
- SyntaxWarning,
1404
+ f"The moderator {m_var} exerts an influence on the effect but is not a factor of the graph; "
1405
+ "its value has been set to 0.",
1406
+ UserWarning,
1407
+ stacklevel=3,
1356
1408
  )
1357
1409
  modval_parsed[m_var] = [0]
1358
1410
  return modval_parsed
1359
1411
 
1412
+ @property
1413
+ def dv(self):
1414
+ """The name of the dependent variable (the outcome Y). `iv` holds the same value for compatibility."""
1415
+ return self.iv
1416
+
1360
1417
  # API
1361
1418
  def summary(self):
1362
1419
  """
@@ -1816,16 +1873,3 @@ class Process(object):
1816
1873
  plot_kws,
1817
1874
  err_kws,
1818
1875
  )
1819
-
1820
- # DEPRECATED METHODS
1821
- def plot_indirect_effects(self, *args, **kwargs):
1822
- raise DeprecationWarning(
1823
- "The method 'plot_indirect_effects' has been deprecated. Please use the equivalent method named \
1824
- 'plot_conditional_indirect_effects."
1825
- )
1826
-
1827
- def plot_direct_effects(self, *args, **kwargs):
1828
- raise DeprecationWarning(
1829
- "The method 'plot_direct_effects' has been deprecated. Please use the equivalent method named \
1830
- 'plot_conditional_direct_effects."
1831
- )
@@ -4,7 +4,7 @@ import matplotlib.pyplot as plt
4
4
  import numpy as np
5
5
  from numpy import dot
6
6
  from numpy.linalg import inv, LinAlgError
7
- from scipy.stats import norm
7
+ from scipy.stats import norm, t
8
8
  from seaborn import FacetGrid
9
9
 
10
10
 
@@ -16,6 +16,15 @@ def z_score(conf):
16
16
  return norm.ppf((100 - (100 - conf) / 2) / 100)
17
17
 
18
18
 
19
+ def t_score(conf, df):
20
+ """
21
+ :param conf: Desired level of confidence
22
+ :param df: Degrees of freedom of the t distribution
23
+ :return: The critical t value corresponding to the level of confidence desired.
24
+ """
25
+ return t.ppf((100 - (100 - conf) / 2) / 100, df)
26
+
27
+
19
28
  def bias_corrected_ci(estimate, samples, conf=95):
20
29
  """
21
30
  Return the bias-corrected bootstrap confidence interval for an estimate
@@ -24,8 +33,10 @@ def bias_corrected_ci(estimate, samples, conf=95):
24
33
  :param conf: Level of the desired confidence interval
25
34
  :return: Bias-corrected bootstrapped LLCI and ULCI for the estimate.
26
35
  """
27
- # noinspection PyUnresolvedReferences
28
36
  ptilde = ((samples < estimate) * 1).mean()
37
+ # Every draw on one side of the estimate would make the bias correction infinite; clip to the
38
+ # resolution of the bootstrap distribution instead (#49).
39
+ ptilde = min(max(ptilde, 1 / len(samples)), 1 - 1 / len(samples))
29
40
  Z = norm.ppf(ptilde)
30
41
  Zci = z_score(conf)
31
42
  Zlow, Zhigh = -Zci + 2 * Z, Zci + 2 * Z
@@ -47,15 +58,16 @@ def percentile_ci(samples, conf):
47
58
  return np.percentile(samples, [lower, upper])
48
59
 
49
60
 
61
+ class ConvergenceError(RuntimeError):
62
+ """Raised when the Newton-Raphson estimation of a logistic model does not converge."""
63
+
64
+
50
65
  def fast_OLS(endog, exog):
51
66
  """
52
67
  A simple function for (X'X)^(-1)X'Y
53
68
  :return: The Kx1 array of estimated coefficients.
54
69
  """
55
- try:
56
- return dot(dot(inv(dot(exog.T, exog)), exog.T), endog).squeeze()
57
- except LinAlgError:
58
- raise LinAlgError
70
+ return dot(dot(inv(dot(exog.T, exog)), exog.T), endog).squeeze()
59
71
 
60
72
 
61
73
  def logit_cdf(X):
@@ -109,17 +121,22 @@ def fast_optimize(endog, exog, n_obs=0, n_vars=0, max_iter=10000, tolerance=1e-1
109
121
  """
110
122
  iterations = 0
111
123
  oldparams = np.inf
112
- newparams = np.repeat(0, n_vars)
124
+ newparams = np.zeros(n_vars)
113
125
  while iterations < max_iter and np.any(np.abs(newparams - oldparams) > tolerance):
114
126
  oldparams = newparams
115
127
  try:
116
128
  H = logit_hessian(exog, oldparams, n_obs)
117
- newparams = oldparams - dot(
118
- inv(H), logit_score(endog, exog, oldparams, n_obs)
119
- )
129
+ newparams = oldparams - dot(inv(H), logit_score(endog, exog, oldparams, n_obs))
120
130
  except LinAlgError:
121
- raise LinAlgError
131
+ raise ConvergenceError("The Hessian of the logistic regression is singular.")
122
132
  iterations += 1
133
+ if not np.all(np.isfinite(newparams)):
134
+ raise ConvergenceError("The logistic regression diverged (the outcome may be perfectly separated).")
135
+ if np.any(np.abs(newparams - oldparams) > tolerance):
136
+ raise ConvergenceError(
137
+ f"The logistic regression did not converge in {max_iter} iterations "
138
+ "(increase 'iterate', relax 'convergence', or check the outcome for separation)."
139
+ )
123
140
  return newparams
124
141
 
125
142
 
@@ -130,8 +147,7 @@ def bootstrap_sampler(n_obs, seed=None):
130
147
  :param seed: The seed to use for the random number generator
131
148
  :return: Bootstrapped indices of size n_obs
132
149
  """
133
- seeder = np.random.RandomState(seed)
134
- seeder.seed(seed)
150
+ seeder = np.random.RandomState(seed) # None draws fresh entropy (#45)
135
151
  while True:
136
152
  yield seeder.randint(n_obs, size=n_obs)
137
153
 
@@ -43,7 +43,7 @@ dependencies = [
43
43
  ]
44
44
 
45
45
  [project.optional-dependencies]
46
- test = ["pytest>=8"]
46
+ test = ["pytest>=8", "statsmodels>=0.14"]
47
47
 
48
48
  [project.urls]
49
49
  Homepage = "https://github.com/QuentinAndre/pyprocessmacro"
@@ -1,9 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Core module for the PyProcess Macro.
4
- """
5
- from .process import Process
6
-
7
- __all__ = ["Process"]
8
-
9
- __version__ = "1.0.14"
@@ -1,228 +0,0 @@
1
- from numpy import ndarray
2
- from typing import Any, Dict, List, Optional, Tuple, Union, Iterable, Callable
3
-
4
- from pandas import DataFrame
5
-
6
- class BaseLogit:
7
- _endog: ndarray
8
- _exog: ndarray
9
- _n_obs: int
10
- _n_vars: int
11
- _options: Dict[str, Any]
12
- def __init__(
13
- self, endog: ndarray, exog: ndarray, options: Dict[str, Any]
14
- ) -> None: ...
15
- @staticmethod
16
- def _cdf(X: ndarray) -> ndarray: ...
17
- def _hessian(self, params: ndarray) -> ndarray: ...
18
- def _loglike(self, params: ndarray) -> float: ...
19
- def _loglikeobs(self, params: ndarray) -> ndarray: ...
20
- def _optimize(self) -> ndarray: ...
21
- def _score(self, params: ndarray) -> ndarray: ...
22
-
23
- class NullLogitModel(BaseLogit):
24
- def __init__(self, endog: ndarray, options: Optional[Dict[str, Any]]) -> None: ...
25
-
26
- class BaseOutcomeModel:
27
- _data: ndarray
28
- _derivative: ndarray
29
- _endog: ndarray
30
- _endogvar: str
31
- _exog: ndarray
32
- _exogvars: List[str]
33
- _n_obs: int
34
- _n_vars: int
35
- _options: Dict[Union[None, str], Any]
36
- _symb_to_ind: Dict[str, int]
37
- _symb_to_var: Dict[str, str]
38
- _varnames: List[str]
39
- estimation_results: Dict[str, Union[ndarray, float, int, List[str]]]
40
- def __init__(
41
- self,
42
- data: ndarray,
43
- endogvar: str,
44
- exogvars: List[str],
45
- symb_to_ind: Dict[str, int],
46
- symb_to_var: Dict[str, str],
47
- options: Optional[Dict[str, Any]] = ...,
48
- ) -> None: ...
49
- def _gen_derivative(self, wrt: str) -> ndarray: ...
50
- def coeff_summary(self) -> DataFrame: ...
51
- def _estimate(self) -> Dict[str, Union[ndarray, float, int, List[str]]]: ...
52
-
53
- class ParallelMediationModel:
54
- _analysis_list: List[str]
55
- _base_derivs: Dict[str, Union[ndarray, List[ndarray]]]
56
- _boot_betas_m: ndarray
57
- _boot_betas_y: ndarray
58
- _compute_betas_m: Callable[[ndarray, ndarray], ndarray]
59
- _compute_betas_y: Callable[[ndarray, ndarray], ndarray]
60
- _data: ndarray
61
- _endog_vars_m: List[str]
62
- _exog_inds_m: List[int]
63
- _exog_inds_y: List[int]
64
- _exog_terms_m: List[str]
65
- _exog_terms_y: List[str]
66
- _has_moderation: bool
67
- _ind_y: int
68
- _inds_m: List[int]
69
- _moderators_symb: List[str]
70
- _moderators_values: List[List[float]]
71
- _n_fail_samples: int
72
- _n_meds: int
73
- _n_obs: int
74
- _options: Dict[Union[None, str], Any]
75
- _symb_to_ind: Dict[str, int]
76
- _symb_to_var: Dict[str, str]
77
- _true_betas_m: ndarray
78
- _true_betas_y: ndarray
79
- _vars_m: List[str]
80
- _vars_y: List[str]
81
- estimation_results: Dict[str, ndarray]
82
- def __init__(
83
- self,
84
- data: ndarray,
85
- exog_terms_y: List[str],
86
- exog_terms_m: List[str],
87
- mod_symb: Iterable[str],
88
- spot_values: Dict[str, List[float]],
89
- n_meds: int,
90
- analysis_list: List[str],
91
- symb_to_ind: Dict[str, int],
92
- symb_to_var: Dict[str, str],
93
- options: Optional[Dict[str, Any]] = ...,
94
- ) -> None: ...
95
- def _simple_ind_effects(self) -> Dict[str, ndarray]: ...
96
- def _simple_ind_effects_wrapper(self) -> DataFrame: ...
97
- def _MM_index(self) -> Dict[str, ndarray]: ...
98
- def _MMM_index(self) -> Dict[str, ndarray]: ...
99
- def _CMM_index(self) -> Dict[str, ndarray]: ...
100
- def _PMM_index(self) -> Dict[str, ndarray]: ...
101
- def _MM_index_wrapper(self) -> DataFrame: ...
102
- def _MMM_index_wrapper(self) -> DataFrame: ...
103
- def _CMM_index_wrapper(self) -> DataFrame: ...
104
- def _PMM_index_wrapper(self) -> DataFrame: ...
105
- def MM_index_summary(self) -> DataFrame: ...
106
- def MMM_index_summary(self) -> DataFrame: ...
107
- def CMM_index_summary(self) -> DataFrame: ...
108
- def PMM_index_summary(self) -> DataFrame: ...
109
- def _cond_ind_effects(self) -> DataFrame: ...
110
- def _cond_ind_effects_wrapper(self) -> DataFrame: ...
111
- def _estimate_bootstrapped_params(self) -> Tuple[ndarray, ndarray, int]: ...
112
- def _estimate_true_params(self) -> Tuple[ndarray, List[ndarray]]: ...
113
- def _floodlight_analysis(
114
- self,
115
- med_index: int,
116
- mod_symb: str,
117
- modval_range: List[float],
118
- other_modval_symb: Dict[str, int],
119
- atol: float,
120
- rtol: float,
121
- ) -> Union[List[List[float]]]: ...
122
- def _gen_derivatives(self) -> Dict[str, Union[ndarray, List[ndarray]]]: ...
123
- def _get_conditional_indirect_effects(
124
- self,
125
- med_index: int,
126
- mod_symb: Iterable[str],
127
- mod_values: Union[ndarray, List[Iterable[float]]],
128
- ) -> Tuple[ndarray, ndarray, ndarray, ndarray, ndarray]: ...
129
- def _indirect_effect_at(
130
- self, med_index: int, mod_dict: Dict[str, float]
131
- ) -> Tuple[float, ndarray, float, float, float]: ...
132
- def coeff_summary(self) -> DataFrame: ...
133
- def summary(self) -> str: ...
134
-
135
- class LogitOutcomeModel(BaseOutcomeModel, BaseLogit):
136
- def __init__(
137
- self,
138
- data: ndarray,
139
- endogvar: str,
140
- exogvars: List[str],
141
- symb_to_ind: Dict[str, int],
142
- symb_to_var: Dict[str, str],
143
- options: Optional[Dict[str, Any]] = ...,
144
- ) -> None: ...
145
- def _estimate(self) -> Dict[str, Union[ndarray, float, int, List[str]]]: ...
146
-
147
- class OLSOutcomeModel(BaseOutcomeModel):
148
- def __init__(
149
- self,
150
- data: ndarray,
151
- endogvar: str,
152
- exogvars: List[str],
153
- symb_to_ind: Dict[str, int],
154
- symb_to_var: Dict[str, str],
155
- options: Optional[Dict[str, Any]] = ...,
156
- ) -> None: ...
157
- def _estimate(self) -> Dict[str, Union[ndarray, float, int, List[str]]]: ...
158
-
159
- class DirectEffectModel:
160
- _derivative: ndarray
161
- _estimation_results: Dict[str, ndarray]
162
- _has_mediation: bool
163
- _has_moderation: bool
164
- _is_logit: bool
165
- _model: Union[LogitOutcomeModel, OLSOutcomeModel]
166
- _moderators_symb: List[str]
167
- _moderators_values: List[List[float]]
168
- _options: Dict[Union[None, str], Any]
169
- _symb_to_var: Dict[str, str]
170
- def __init__(
171
- self,
172
- model: OLSOutcomeModel,
173
- mod_symb: Iterable[str],
174
- spot_values: Dict[str, List[float]],
175
- has_mediation: bool,
176
- symb_to_var: Dict[str, str],
177
- options: Optional[Dict[str, Any]] = ...,
178
- ) -> None: ...
179
- def _direct_effect_at(
180
- self, mod_dict: Dict[str, float]
181
- ) -> Tuple[float, float, float, float]: ...
182
- def _estimate(self) -> Dict[str, ndarray]: ...
183
- def _floodlight_analysis(
184
- self,
185
- mod_symb: str,
186
- modval_range: List[float],
187
- other_modval_symb: Dict[str, int],
188
- atol: float,
189
- rtol: float,
190
- ) -> List[List[float]]: ...
191
- def _get_conditional_direct_effects(
192
- self, mod_symb: List[str], mod_values: Union[ndarray, List[Iterable[float]]]
193
- ) -> Tuple[ndarray, ndarray, ndarray, ndarray]: ...
194
-
195
- class BaseFloodlightAnalysis:
196
- sig_regions: List[List[float]]
197
- def __init__(
198
- self,
199
- med_name: Optional[str],
200
- mod_name: str,
201
- sig_regions: List[List[float]],
202
- modval_range: List[float],
203
- other_modval_name: Dict[str, float],
204
- precision: int,
205
- ) -> None: ...
206
- def get_significance_regions(self) -> Dict[str, List[float]]: ...
207
-
208
- class DirectFloodlightAnalysis(BaseFloodlightAnalysis):
209
- def __init__(
210
- self,
211
- mod_name: str,
212
- sig_regions: List[List[float]],
213
- modval_range: List[float],
214
- other_modval_name: Dict[str, int],
215
- precision: int,
216
- ) -> None: ...
217
-
218
-
219
- class IndirectFloodlightAnalysis(BaseFloodlightAnalysis):
220
- def __init__(
221
- self,
222
- med_name: str,
223
- mod_name: str,
224
- sig_regions: List[List[float]],
225
- modval_range: List[float],
226
- other_modval_name: Dict[str, int],
227
- precision: int,
228
- ) -> None: ...
@@ -1,163 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from typing import Union, Iterable, Dict, List, Any, Set, Tuple, Optional, NoReturn
3
-
4
- from numpy import ndarray
5
- from pandas.core.frame import DataFrame
6
- from seaborn.axisgrid import FacetGrid
7
-
8
- from .models import (
9
- OLSOutcomeModel,
10
- DirectEffectModel,
11
- ParallelMediationModel,
12
- LogitOutcomeModel,
13
- DirectFloodlightAnalysis,
14
- IndirectFloodlightAnalysis,
15
- )
16
-
17
- class Process(object):
18
- __var_kws__: Set[str]
19
-
20
- __options_kws__: Set[str]
21
-
22
- __models_vars__: Dict[str, Set[str]]
23
-
24
- __models_eqs__: Dict[str, Dict[List[str]]]
25
-
26
- model_num: int
27
- controls: Iterable
28
- options = Dict[str, Any]
29
- varlist = List[str]
30
- mediators = List[str]
31
-
32
- iv: str
33
- n_obs: int
34
- n_obs_null: int
35
- n_meds: int
36
- has_mediation: bool
37
- outcome_models: Dict[str, Union[OLSOutcomeModel, LogitOutcomeModel]]
38
- direct_model: DirectEffectModel
39
- indirect_model: Union[None, ParallelMediationModel]
40
- centered_vars: Union[List, None]
41
-
42
- _data: DataFrame
43
- _moderators: Dict[str, Set[str]]
44
- _spotlight_values: Dict[str, List[float]]
45
- _var_to_symb: Dict[str, str]
46
- _symb_to_var: Dict[str, str]
47
- _symb_to_ind: Dict[str, int]
48
-
49
- _equations: List[Tuple[Union[str, List[str]]]]
50
- def __init__(
51
- self,
52
- data: DataFrame,
53
- model: int,
54
- modval: Optional[Dict[str, List[float]]],
55
- cluster: Optional[str],
56
- boot: Optional[int],
57
- seed: Optional[int],
58
- mc: Optional[bool],
59
- conf: Optional[int],
60
- effsize: Optional[bool],
61
- jn: Optional[bool],
62
- hc3: Optional[bool],
63
- controls: Optional[Iterable[str]],
64
- controls_in: str,
65
- total: Optional[bool],
66
- contrast: Optional[bool],
67
- center: Optional[bool],
68
- quantile: Optional[bool],
69
- detail: Optional[bool],
70
- percent: Optional[bool],
71
- logit: Optional[bool],
72
- iterate: Optional[int],
73
- convergence: Optional[float],
74
- precision: Optional[int],
75
- suppr_init: Optional[bool],
76
- **kwargs
77
- ) -> None: ...
78
- # PRIVATE METHODS
79
- def _gen_valid_options(self, argument: Dict[str, Any]) -> Dict[str, Any]: ...
80
- def _gen_valid_varlist(
81
- self, var_kwargs: Dict[str, Union[str, List]]
82
- ) -> List[str]: ...
83
- def _gen_analysis_list(self) -> List[str]: ...
84
- def _gen_spotlight_values(
85
- self, modval: Optional[Dict[str, List[float]]]
86
- ) -> Dict[str, Iterable[float]]: ...
87
- def _gen_var_mapping(
88
- self, var_kwargs: Dict[str, str]
89
- ) -> Tuple[Dict[str, str], Dict[str, str]]: ...
90
- def _prepare_data(self) -> Tuple[DataFrame, int, int, List[str]]: ...
91
- def _gen_equations(
92
- self, all_to_y: List[str], x_to_m: List[str], controls_in: str
93
- ) -> List[Tuple[str, List[str]]]: ...
94
- def _gen_outcome_models(
95
- self
96
- ) -> Dict[str, Union[OLSOutcomeModel, LogitOutcomeModel]]: ...
97
- def _gen_direct_effect_model(self) -> DirectEffectModel: ...
98
- def _gen_indirect_effect_model(self) -> ParallelMediationModel: ...
99
- def _print_init(self) -> None: ...
100
- def _parse_moderator_values(
101
- self,
102
- x: str,
103
- hue: Optional[Union[str, List[str]]],
104
- row: Optional[str],
105
- col: Optional[str],
106
- modval: Optional[Dict[str, Union[ndarray, List[float]]]],
107
- path: str,
108
- ) -> Dict[str, Union[ndarray, List[float]]]: ...
109
- # API METHODS
110
- def summary(self) -> None: ...
111
- def get_bootstrap_estimates(self) -> DataFrame: ...
112
- def floodlight_indirect_effect(
113
- self,
114
- med_name: str,
115
- mod_name: str,
116
- other_modval: Optional[Dict[str, float]],
117
- atol: Optional[float],
118
- rtol: Optional[float],
119
- ) -> IndirectFloodlightAnalysis: ...
120
- def floodlight_direct_effect(
121
- self,
122
- mod_name: str,
123
- other_modval: Optional[Dict[str, float]],
124
- atol: Optional[float],
125
- rtol: Optional[float],
126
- ) -> DirectFloodlightAnalysis: ...
127
- def spotlight_indirect_effect(
128
- self, med_name: str, spotval: Optional[Dict[str, Union[ndarray, List[float]]]]
129
- ) -> DataFrame: ...
130
- def spotlight_direct_effect(
131
- self, spotval: Optional[Dict[str, Union[ndarray, List[float]]]]
132
- ) -> DataFrame: ...
133
- def plot_conditional_direct_effects(
134
- self,
135
- x: str,
136
- hue: Optional[Union[str, List[str]]],
137
- row: Optional[str],
138
- col: Optional[str],
139
- modval: Optional[Dict[str, Union[ndarray, List[float]]]],
140
- errstyle: Optional[str],
141
- hue_format: Optional[str],
142
- facet_kws: Optional[Dict[str, Any]],
143
- plot_kws: Optional[Dict[str, Any]],
144
- err_kws: Optional[Dict[str, Any]],
145
- ) -> FacetGrid: ...
146
- def plot_conditional_indirect_effects(
147
- self,
148
- med_name: str,
149
- x: str,
150
- hue: Optional[Union[str, List[str]]],
151
- row: Optional[str],
152
- col: Optional[str],
153
- modval: Optional[Dict[str, Union[ndarray, List[float]]]],
154
- errstyle: Optional[str],
155
- hue_format: Optional[str],
156
- facet_kws: Optional[Dict[str, Any]],
157
- plot_kws: Optional[Dict[str, Any]],
158
- err_kws: Optional[Dict[str, Any]],
159
- ) -> FacetGrid: ...
160
-
161
- # DEPRECATED METHODS
162
- def plot_indirect_effects(self, *args, **kwargs) -> NoReturn: ...
163
- def plot_direct_effects(self, *args, **kwargs) -> NoReturn: ...
@@ -1,92 +0,0 @@
1
- from typing import Callable, Union, Iterable, Dict, List, Optional, Any, Set
2
-
3
- import numpy as np
4
- from pandas import DataFrame
5
- from seaborn import FacetGrid
6
-
7
- def z_score(conf: float) -> float: ...
8
- def bias_corrected_ci(
9
- estimate: np.array, samples: np.array, conf: Union[float, int]
10
- ) -> (float, float): ...
11
- def percentile_ci(samples: np.array, conf: Union[float, int]) -> np.array: ...
12
- def fast_OLS(endog: np.array, exog: np.array) -> np.array: ...
13
- def logit_cdf(X: np.array) -> np.array: ...
14
- def logit_score(
15
- endog: np.array, exog: np.array, params: np.array, n_obs: int
16
- ) -> np.array: ...
17
- def logit_hessian(exog: np.array, params: np.array, n_obs: int) -> np.array: ...
18
- def fast_optimize(
19
- endog: np.array,
20
- exog: np.array,
21
- n_obs: int,
22
- n_vars: int,
23
- max_iter: int,
24
- tolerance: float = 1e-10,
25
- ) -> np.array: ...
26
- def bootstrap_sampler(n_obs: int, seed: int) -> np.array: ...
27
- def eigvals(exog: np.array) -> np.array: ...
28
- def eval_expression(expr: np.array, values: Dict) -> np.array: ...
29
- def gen_moderators(
30
- raw_equations: Dict[str, List[str]], raw_varlist: List[str]
31
- ) -> Dict[str, Set[str]]: ...
32
- def plot_errorbars(
33
- x: np.array,
34
- y: np.array,
35
- yerrlow: np.array,
36
- yerrhigh: np.array,
37
- plot_kws: Optional[Dict[str, Any]],
38
- err_kws: Optional[Dict[str, Any]],
39
- *args,
40
- **kwargs
41
- ) -> None: ...
42
- def plot_errorbands(
43
- x: np.array,
44
- y: np.array,
45
- llci: np.array,
46
- ulci: np.array,
47
- plot_kws: Optional[Dict[str, Any]],
48
- err_kws: Optional[Dict[str, Any]],
49
- *args,
50
- **kwargs
51
- ) -> None: ...
52
- def plot_conditional_effects(
53
- df_effects: DataFrame,
54
- x: str,
55
- hue: Optional[Union[str, List[str]]],
56
- row: Optional[str],
57
- col: Optional[str],
58
- errstyle: Optional[str],
59
- hue_format: Optional[str],
60
- facet_kws: Optional[Dict[str, Any]],
61
- plot_kws: Optional[Dict[str, Any]],
62
- err_kws: Optional[Dict[str, Any]],
63
- ) -> FacetGrid: ...
64
- def find_significance_region(
65
- spotlight_func: Callable[[Dict], Iterable],
66
- mod_symb: str,
67
- modval_min: float,
68
- modval_max: float,
69
- modval_other_symb: Dict,
70
- atol: float,
71
- rtol: float,
72
- ) -> List[List[float]]: ...
73
- def search_mid_range(
74
- spotlight_func: Callable[[Dict], Iterable],
75
- min_val: float,
76
- max_val: float,
77
- mod_symb: str,
78
- mod_dict: Dict,
79
- atol: float,
80
- rtol: float,
81
- ) -> List[List[float]]: ...
82
- def search_critical_values(
83
- spotlight_func: Callable[[Dict], Iterable],
84
- min_val: float,
85
- max_val: float,
86
- mod_symb: str,
87
- mod_dict: Dict,
88
- slope: str,
89
- region: str,
90
- atol: float,
91
- rtol: float,
92
- ) -> float: ...