pySEQTarget 0.13.2__tar.gz → 0.13.3__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.
Files changed (69) hide show
  1. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/PKG-INFO +1 -1
  2. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/SEQopts.py +3 -0
  3. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/analysis/_outcome_fit.py +29 -5
  4. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/error/_param_checker.py +3 -0
  5. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget.egg-info/PKG-INFO +1 -1
  6. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pyproject.toml +1 -1
  7. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_followup_options.py +12 -11
  8. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/LICENSE +0 -0
  9. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/README.md +0 -0
  10. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/SEQoutput.py +0 -0
  11. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/SEQuential.py +0 -0
  12. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/__init__.py +0 -0
  13. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/analysis/__init__.py +0 -0
  14. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/analysis/_hazard.py +0 -0
  15. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/analysis/_risk_estimates.py +0 -0
  16. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/analysis/_subgroup_fit.py +0 -0
  17. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/analysis/_survival_pred.py +0 -0
  18. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/data/__init__.py +0 -0
  19. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/error/__init__.py +0 -0
  20. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/error/_check_separation.py +0 -0
  21. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/error/_data_checker.py +0 -0
  22. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/expansion/__init__.py +0 -0
  23. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/expansion/_binder.py +0 -0
  24. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/expansion/_diagnostics.py +0 -0
  25. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/expansion/_dynamic.py +0 -0
  26. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/expansion/_mapper.py +0 -0
  27. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/expansion/_selection.py +0 -0
  28. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/__init__.py +0 -0
  29. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_bootstrap.py +0 -0
  30. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_col_string.py +0 -0
  31. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_fix_categories.py +0 -0
  32. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_format_time.py +0 -0
  33. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_offloader.py +0 -0
  34. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_output_files.py +0 -0
  35. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_pad.py +0 -0
  36. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_predict_model.py +0 -0
  37. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/helpers/_prepare_data.py +0 -0
  38. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/initialization/__init__.py +0 -0
  39. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/initialization/_censoring.py +0 -0
  40. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/initialization/_denominator.py +0 -0
  41. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/initialization/_numerator.py +0 -0
  42. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/initialization/_outcome.py +0 -0
  43. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/plot/__init__.py +0 -0
  44. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/plot/_survival_plot.py +0 -0
  45. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/weighting/__init__.py +0 -0
  46. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/weighting/_weight_bind.py +0 -0
  47. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/weighting/_weight_data.py +0 -0
  48. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/weighting/_weight_fit.py +0 -0
  49. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/weighting/_weight_offload.py +0 -0
  50. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/weighting/_weight_pred.py +0 -0
  51. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget/weighting/_weight_stats.py +0 -0
  52. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget.egg-info/SOURCES.txt +0 -0
  53. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget.egg-info/dependency_links.txt +0 -0
  54. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget.egg-info/requires.txt +0 -0
  55. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/pySEQTarget.egg-info/top_level.txt +0 -0
  56. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/setup.cfg +0 -0
  57. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_accessor.py +0 -0
  58. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_check_separation.py +0 -0
  59. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_coefficients.py +0 -0
  60. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_covariates.py +0 -0
  61. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_expansion.py +0 -0
  62. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_fix_categories.py +0 -0
  63. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_hazard.py +0 -0
  64. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_no_variation.py +0 -0
  65. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_offload.py +0 -0
  66. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_parallel.py +0 -0
  67. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_plot.py +0 -0
  68. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_reproducibility.py +0 -0
  69. {pyseqtarget-0.13.2 → pyseqtarget-0.13.3}/tests/test_survival.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pySEQTarget
3
- Version: 0.13.2
3
+ Version: 0.13.3
4
4
  Summary: Sequentially Nested Target Trial Emulation
5
5
  Author-email: Ryan O'Dea <ryan.odea@psi.ch>, Alejandro Szmulewicz <aszmulewicz@hsph.harvard.edu>, Tom Palmer <tom.palmer@bristol.ac.uk>, Miguel Hernán <mhernan@hsph.harvard.edu>
6
6
  Maintainer-email: Ryan O'Dea <ryan.odea@psi.ch>, Tom Palmer <remlapmot@hotmail.com>
@@ -45,6 +45,8 @@ class SEQopts:
45
45
  :type followup_include: bool
46
46
  :param followup_spline: Boolean to force followup values to be fit to cubic spline
47
47
  :type followup_spline: bool
48
+ :param followup_spline_df: Degrees of freedom for the followup cubic spline, default ``4``
49
+ :type followup_spline_df: int
48
50
  :param followup_max: Maximum allowed followup in analysis
49
51
  :type followup_max: int or None
50
52
  :param followup_min: Minimum allowed followup in analysis
@@ -130,6 +132,7 @@ class SEQopts:
130
132
  followup_max: int = None
131
133
  followup_min: int = 0
132
134
  followup_spline: bool = False
135
+ followup_spline_df: int = 4
133
136
  hazard_estimate: bool = False
134
137
  indicator_baseline: str = "_bas"
135
138
  indicator_squared: str = "_sq"
@@ -1,12 +1,30 @@
1
1
  import re
2
2
 
3
+ import numpy as np
3
4
  import polars as pl
4
5
  import statsmodels.api as sm
5
6
  import statsmodels.formula.api as smf
6
7
 
7
8
 
8
- def _apply_spline_formula(formula, indicator_squared):
9
- spline = "cr(followup, df=3)"
9
+ def _compute_spline_knots(followup_arr, df=3):
10
+ lower = float(np.min(followup_arr))
11
+ upper = float(np.max(followup_arr))
12
+ n_inner = df - 2
13
+ if n_inner == 0:
14
+ inner_knots = []
15
+ else:
16
+ # Replicate patsy's knot placement: percentiles of unique values in [lower, upper]
17
+ x = np.unique(followup_arr[(lower <= followup_arr) & (followup_arr <= upper)])
18
+ q = np.linspace(0, 100, n_inner + 2)[1:-1]
19
+ inner_knots = np.percentile(x, q.tolist()).tolist()
20
+ return inner_knots, lower, upper
21
+
22
+
23
+ def _apply_spline_formula(formula, indicator_squared, spline_knots):
24
+ inner_knots, lower, upper = spline_knots
25
+ spline = (
26
+ f"cr(followup, knots={inner_knots}, lower_bound={lower}, upper_bound={upper})"
27
+ )
10
28
 
11
29
  formula = re.sub(r"(\w+)\s*\*\s*followup\b", rf"\1*{spline}", formula)
12
30
  formula = re.sub(r"\bfollowup\s*\*\s*(\w+)", rf"{spline}*\1", formula)
@@ -18,8 +36,8 @@ def _apply_spline_formula(formula, indicator_squared):
18
36
  formula = re.sub(r"^\s*\+\s*|\s*\+\s*$", "", formula).strip()
19
37
 
20
38
  if formula:
21
- return f"{formula} + I({spline}**2)"
22
- return f"I({spline}**2)"
39
+ return f"{formula} + {spline}"
40
+ return spline
23
41
 
24
42
 
25
43
  def _cast_categories(self, df_pd):
@@ -64,7 +82,13 @@ def _outcome_fit(
64
82
  df_pd = _cast_categories(self, df.to_pandas())
65
83
 
66
84
  if self.followup_spline:
67
- formula = _apply_spline_formula(formula, self.indicator_squared)
85
+ if getattr(self, "_current_boot_idx", None) is None:
86
+ self._followup_spline_knots = _compute_spline_knots(
87
+ self.DT["followup"].to_numpy(), df=self.followup_spline_df
88
+ )
89
+ formula = _apply_spline_formula(
90
+ formula, self.indicator_squared, self._followup_spline_knots
91
+ )
68
92
 
69
93
  full_formula = f"{outcome} ~ {formula}"
70
94
 
@@ -44,6 +44,9 @@ def _param_checker(self):
44
44
  "Only one of followup_class or followup_include can be set to True."
45
45
  )
46
46
 
47
+ if self.followup_spline_df < 2:
48
+ raise ValueError("followup_spline_df must be at least 2.")
49
+
47
50
  if (
48
51
  self.weighted
49
52
  and self.method == "ITT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pySEQTarget
3
- Version: 0.13.2
3
+ Version: 0.13.3
4
4
  Summary: Sequentially Nested Target Trial Emulation
5
5
  Author-email: Ryan O'Dea <ryan.odea@psi.ch>, Alejandro Szmulewicz <aszmulewicz@hsph.harvard.edu>, Tom Palmer <tom.palmer@bristol.ac.uk>, Miguel Hernán <mhernan@hsph.harvard.edu>
6
6
  Maintainer-email: Ryan O'Dea <ryan.odea@psi.ch>, Tom Palmer <remlapmot@hotmail.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pySEQTarget"
7
- version = "0.13.2"
7
+ version = "0.13.3"
8
8
  description = "Sequentially Nested Target Trial Emulation"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -57,17 +57,18 @@ def test_followup_spline():
57
57
  s.fit()
58
58
  matrix = s.outcome_model[0]["outcome"].summary2().tables[1]["Coef."].to_list()
59
59
  expected = [
60
- -6.264817962084417,
61
- 0.20125056343026881,
62
- 0.12568743032952776,
63
- 0.03823426390103046,
64
- 0.0006607691746414019,
65
- 0.003343365539743267,
66
- -0.01319460158923785,
67
- 0.19601796921732118,
68
- -0.5186462478511427,
69
- 0.37598656666756103,
70
- 1.6553848469346044,
60
+ -4.804282252748607,
61
+ 0.19115933860001255,
62
+ 0.12717121164606823,
63
+ 0.044310717515918724,
64
+ 0.0005814999431447507,
65
+ 0.0032948355025455216,
66
+ -0.013371824500839971,
67
+ 0.19972467861548412,
68
+ -2.027245615586753,
69
+ -1.395729861856384,
70
+ -0.9397731941281695,
71
+ -0.4415335811772879,
71
72
  ]
72
73
  assert [round(x, 3) for x in matrix] == [round(x, 3) for x in expected]
73
74
 
File without changes
File without changes
File without changes