ins-pricing 0.1.11__py3-none-any.whl → 0.2.0__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 (126) hide show
  1. ins_pricing/README.md +9 -6
  2. ins_pricing/__init__.py +3 -11
  3. ins_pricing/cli/BayesOpt_entry.py +24 -0
  4. ins_pricing/{modelling → cli}/BayesOpt_incremental.py +197 -64
  5. ins_pricing/cli/Explain_Run.py +25 -0
  6. ins_pricing/{modelling → cli}/Explain_entry.py +169 -124
  7. ins_pricing/cli/Pricing_Run.py +25 -0
  8. ins_pricing/cli/__init__.py +1 -0
  9. ins_pricing/cli/bayesopt_entry_runner.py +1312 -0
  10. ins_pricing/cli/utils/__init__.py +1 -0
  11. ins_pricing/cli/utils/cli_common.py +320 -0
  12. ins_pricing/cli/utils/cli_config.py +375 -0
  13. ins_pricing/{modelling → cli/utils}/notebook_utils.py +74 -19
  14. {ins_pricing_gemini/modelling → ins_pricing/cli}/watchdog_run.py +2 -2
  15. ins_pricing/{modelling → docs/modelling}/BayesOpt_USAGE.md +69 -49
  16. ins_pricing/docs/modelling/README.md +34 -0
  17. ins_pricing/modelling/__init__.py +57 -6
  18. ins_pricing/modelling/core/__init__.py +1 -0
  19. ins_pricing/modelling/{bayesopt → core/bayesopt}/config_preprocess.py +64 -1
  20. ins_pricing/modelling/{bayesopt → core/bayesopt}/core.py +150 -810
  21. ins_pricing/modelling/core/bayesopt/model_explain_mixin.py +296 -0
  22. ins_pricing/modelling/core/bayesopt/model_plotting_mixin.py +548 -0
  23. ins_pricing/modelling/core/bayesopt/models/__init__.py +27 -0
  24. ins_pricing/modelling/core/bayesopt/models/model_ft_components.py +316 -0
  25. ins_pricing/modelling/core/bayesopt/models/model_ft_trainer.py +808 -0
  26. ins_pricing/modelling/core/bayesopt/models/model_gnn.py +675 -0
  27. ins_pricing/modelling/core/bayesopt/models/model_resn.py +435 -0
  28. ins_pricing/modelling/core/bayesopt/trainers/__init__.py +19 -0
  29. ins_pricing/modelling/core/bayesopt/trainers/trainer_base.py +1020 -0
  30. ins_pricing/modelling/core/bayesopt/trainers/trainer_ft.py +787 -0
  31. ins_pricing/modelling/core/bayesopt/trainers/trainer_glm.py +195 -0
  32. ins_pricing/modelling/core/bayesopt/trainers/trainer_gnn.py +312 -0
  33. ins_pricing/modelling/core/bayesopt/trainers/trainer_resn.py +261 -0
  34. ins_pricing/modelling/core/bayesopt/trainers/trainer_xgb.py +348 -0
  35. ins_pricing/modelling/{bayesopt → core/bayesopt}/utils.py +2 -2
  36. ins_pricing/modelling/core/evaluation.py +115 -0
  37. ins_pricing/production/__init__.py +4 -0
  38. ins_pricing/production/preprocess.py +71 -0
  39. ins_pricing/setup.py +10 -5
  40. {ins_pricing_gemini/modelling/tests → ins_pricing/tests/modelling}/test_plotting.py +2 -2
  41. {ins_pricing-0.1.11.dist-info → ins_pricing-0.2.0.dist-info}/METADATA +4 -4
  42. ins_pricing-0.2.0.dist-info/RECORD +125 -0
  43. {ins_pricing-0.1.11.dist-info → ins_pricing-0.2.0.dist-info}/top_level.txt +0 -1
  44. ins_pricing/modelling/BayesOpt_entry.py +0 -633
  45. ins_pricing/modelling/Explain_Run.py +0 -36
  46. ins_pricing/modelling/Pricing_Run.py +0 -36
  47. ins_pricing/modelling/README.md +0 -33
  48. ins_pricing/modelling/bayesopt/models.py +0 -2196
  49. ins_pricing/modelling/bayesopt/trainers.py +0 -2446
  50. ins_pricing/modelling/cli_common.py +0 -136
  51. ins_pricing/modelling/tests/test_plotting.py +0 -63
  52. ins_pricing/modelling/watchdog_run.py +0 -211
  53. ins_pricing-0.1.11.dist-info/RECORD +0 -169
  54. ins_pricing_gemini/__init__.py +0 -23
  55. ins_pricing_gemini/governance/__init__.py +0 -20
  56. ins_pricing_gemini/governance/approval.py +0 -93
  57. ins_pricing_gemini/governance/audit.py +0 -37
  58. ins_pricing_gemini/governance/registry.py +0 -99
  59. ins_pricing_gemini/governance/release.py +0 -159
  60. ins_pricing_gemini/modelling/Explain_Run.py +0 -36
  61. ins_pricing_gemini/modelling/Pricing_Run.py +0 -36
  62. ins_pricing_gemini/modelling/__init__.py +0 -151
  63. ins_pricing_gemini/modelling/cli_common.py +0 -141
  64. ins_pricing_gemini/modelling/config.py +0 -249
  65. ins_pricing_gemini/modelling/config_preprocess.py +0 -254
  66. ins_pricing_gemini/modelling/core.py +0 -741
  67. ins_pricing_gemini/modelling/data_container.py +0 -42
  68. ins_pricing_gemini/modelling/explain/__init__.py +0 -55
  69. ins_pricing_gemini/modelling/explain/gradients.py +0 -334
  70. ins_pricing_gemini/modelling/explain/metrics.py +0 -176
  71. ins_pricing_gemini/modelling/explain/permutation.py +0 -155
  72. ins_pricing_gemini/modelling/explain/shap_utils.py +0 -146
  73. ins_pricing_gemini/modelling/features.py +0 -215
  74. ins_pricing_gemini/modelling/model_manager.py +0 -148
  75. ins_pricing_gemini/modelling/model_plotting.py +0 -463
  76. ins_pricing_gemini/modelling/models.py +0 -2203
  77. ins_pricing_gemini/modelling/notebook_utils.py +0 -294
  78. ins_pricing_gemini/modelling/plotting/__init__.py +0 -45
  79. ins_pricing_gemini/modelling/plotting/common.py +0 -63
  80. ins_pricing_gemini/modelling/plotting/curves.py +0 -572
  81. ins_pricing_gemini/modelling/plotting/diagnostics.py +0 -139
  82. ins_pricing_gemini/modelling/plotting/geo.py +0 -362
  83. ins_pricing_gemini/modelling/plotting/importance.py +0 -121
  84. ins_pricing_gemini/modelling/run_logging.py +0 -133
  85. ins_pricing_gemini/modelling/tests/conftest.py +0 -8
  86. ins_pricing_gemini/modelling/tests/test_cross_val_generic.py +0 -66
  87. ins_pricing_gemini/modelling/tests/test_distributed_utils.py +0 -18
  88. ins_pricing_gemini/modelling/tests/test_explain.py +0 -56
  89. ins_pricing_gemini/modelling/tests/test_geo_tokens_split.py +0 -49
  90. ins_pricing_gemini/modelling/tests/test_graph_cache.py +0 -33
  91. ins_pricing_gemini/modelling/tests/test_plotting_library.py +0 -150
  92. ins_pricing_gemini/modelling/tests/test_preprocessor.py +0 -48
  93. ins_pricing_gemini/modelling/trainers.py +0 -2447
  94. ins_pricing_gemini/modelling/utils.py +0 -1020
  95. ins_pricing_gemini/pricing/__init__.py +0 -27
  96. ins_pricing_gemini/pricing/calibration.py +0 -39
  97. ins_pricing_gemini/pricing/data_quality.py +0 -117
  98. ins_pricing_gemini/pricing/exposure.py +0 -85
  99. ins_pricing_gemini/pricing/factors.py +0 -91
  100. ins_pricing_gemini/pricing/monitoring.py +0 -99
  101. ins_pricing_gemini/pricing/rate_table.py +0 -78
  102. ins_pricing_gemini/production/__init__.py +0 -21
  103. ins_pricing_gemini/production/drift.py +0 -30
  104. ins_pricing_gemini/production/monitoring.py +0 -143
  105. ins_pricing_gemini/production/scoring.py +0 -40
  106. ins_pricing_gemini/reporting/__init__.py +0 -11
  107. ins_pricing_gemini/reporting/report_builder.py +0 -72
  108. ins_pricing_gemini/reporting/scheduler.py +0 -45
  109. ins_pricing_gemini/scripts/BayesOpt_incremental.py +0 -722
  110. ins_pricing_gemini/scripts/Explain_entry.py +0 -545
  111. ins_pricing_gemini/scripts/__init__.py +0 -1
  112. ins_pricing_gemini/scripts/train.py +0 -568
  113. ins_pricing_gemini/setup.py +0 -55
  114. ins_pricing_gemini/smoke_test.py +0 -28
  115. /ins_pricing/{modelling → cli/utils}/run_logging.py +0 -0
  116. /ins_pricing/modelling/{BayesOpt.py → core/BayesOpt.py} +0 -0
  117. /ins_pricing/modelling/{bayesopt → core/bayesopt}/__init__.py +0 -0
  118. /ins_pricing/{modelling/tests → tests/modelling}/conftest.py +0 -0
  119. /ins_pricing/{modelling/tests → tests/modelling}/test_cross_val_generic.py +0 -0
  120. /ins_pricing/{modelling/tests → tests/modelling}/test_distributed_utils.py +0 -0
  121. /ins_pricing/{modelling/tests → tests/modelling}/test_explain.py +0 -0
  122. /ins_pricing/{modelling/tests → tests/modelling}/test_geo_tokens_split.py +0 -0
  123. /ins_pricing/{modelling/tests → tests/modelling}/test_graph_cache.py +0 -0
  124. /ins_pricing/{modelling/tests → tests/modelling}/test_plotting_library.py +0 -0
  125. /ins_pricing/{modelling/tests → tests/modelling}/test_preprocessor.py +0 -0
  126. {ins_pricing-0.1.11.dist-info → ins_pricing-0.2.0.dist-info}/WHEEL +0 -0
@@ -1,294 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import subprocess
5
- import sys
6
- from pathlib import Path
7
- from typing import Iterable, List, Optional, Sequence, cast
8
-
9
-
10
- def _find_ins_pricing_dir(cwd: Optional[Path] = None) -> Path:
11
- cwd = (cwd or Path().resolve()).resolve()
12
- # If running from root/scripts or root
13
- candidates = [cwd, cwd.parent, cwd / "ins_pricing", cwd.parent / "ins_pricing"]
14
- for cand in candidates:
15
- # Check for modelling/core.py in root (since we restructured)
16
- if (cand / "modelling" / "core.py").exists() and (cand / "scripts" / "train.py").exists():
17
- return cand
18
-
19
- # Fallback: try finding package via file path if inside package
20
- # If this file is in modelling/, parent is modelling, parent.parent is root (ins_pricing package dir or root of repo)
21
- pkg_dir = Path(__file__).resolve().parent.parent
22
- if (pkg_dir / "modelling" / "core.py").exists():
23
- return pkg_dir
24
-
25
- raise FileNotFoundError(
26
- "Cannot locate ins_pricing root directory (expected modelling/core.py and scripts/train.py). "
27
- f"cwd={cwd}"
28
- )
29
-
30
-
31
- def _stringify_cmd(cmd: Sequence[object]) -> List[str]:
32
- return [str(x) for x in cmd]
33
-
34
-
35
- def build_bayesopt_entry_cmd(
36
- config_json: str | Path,
37
- model_keys: Sequence[str],
38
- *,
39
- nproc_per_node: int = 1,
40
- standalone: bool = True,
41
- entry_script: str | Path = "scripts/train.py",
42
- extra_args: Optional[Sequence[str]] = None,
43
- ) -> List[str]:
44
- """Build a command to run train.py (optional torchrun/DDP)."""
45
- pkg_dir = _find_ins_pricing_dir()
46
- entry_script_path = Path(entry_script)
47
- if entry_script_path.is_absolute():
48
- entry_path = entry_script_path.resolve()
49
- else:
50
- # Check relative to root
51
- entry_path = (pkg_dir / entry_script_path).resolve()
52
-
53
- config_path = Path(config_json)
54
- if not config_path.is_absolute():
55
- config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
56
-
57
- cmd: List[object]
58
- if int(nproc_per_node) > 1:
59
- cmd = [
60
- sys.executable,
61
- "-m",
62
- "torch.distributed.run",
63
- *(["--standalone"] if standalone else []),
64
- f"--nproc_per_node={int(nproc_per_node)}",
65
- str(entry_path),
66
- ]
67
- else:
68
- cmd = [sys.executable, str(entry_path)]
69
-
70
- cmd += ["--config-json", str(config_path), "--model-keys", *list(model_keys)]
71
- if extra_args:
72
- cmd += list(extra_args)
73
- return _stringify_cmd(cmd)
74
-
75
-
76
- def build_incremental_cmd(
77
- config_json: str | Path,
78
- *,
79
- entry_script: str | Path = "scripts/BayesOpt_incremental.py",
80
- extra_args: Optional[Sequence[str]] = None,
81
- ) -> List[str]:
82
- """Build a command to run BayesOpt_incremental.py."""
83
- pkg_dir = _find_ins_pricing_dir()
84
- entry_script_path = Path(entry_script)
85
- if entry_script_path.is_absolute():
86
- entry_path = entry_script_path.resolve()
87
- else:
88
- entry_path = (pkg_dir / entry_script_path).resolve()
89
-
90
- config_path = Path(config_json)
91
- if not config_path.is_absolute():
92
- config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
93
-
94
- cmd: List[object] = [sys.executable, str(entry_path), "--config-json", str(config_path)]
95
- if extra_args:
96
- cmd += list(extra_args)
97
- return _stringify_cmd(cmd)
98
-
99
-
100
- def build_explain_cmd(
101
- config_json: str | Path,
102
- *,
103
- entry_script: str | Path = "scripts/Explain_entry.py",
104
- extra_args: Optional[Sequence[str]] = None,
105
- ) -> List[str]:
106
- """Build a command to run Explain_entry.py."""
107
- pkg_dir = _find_ins_pricing_dir()
108
- entry_script_path = Path(entry_script)
109
- if entry_script_path.is_absolute():
110
- entry_path = entry_script_path.resolve()
111
- else:
112
- entry_path = (pkg_dir / entry_script_path).resolve()
113
-
114
- config_path = Path(config_json)
115
- if not config_path.is_absolute():
116
- config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
117
-
118
- cmd: List[object] = [sys.executable, str(entry_path), "--config-json", str(config_path)]
119
- if extra_args:
120
- cmd += list(extra_args)
121
- return _stringify_cmd(cmd)
122
-
123
-
124
- def wrap_with_watchdog(
125
- cmd: Sequence[str],
126
- *,
127
- idle_seconds: int = 7200,
128
- max_restarts: int = 50,
129
- restart_delay_seconds: int = 10,
130
- stop_on_nonzero_exit: bool = True,
131
- watchdog_script: str | Path = "watchdog_run.py",
132
- ) -> List[str]:
133
- """Wrap a command with watchdog: restart when idle_seconds elapses with no output."""
134
- pkg_dir = _find_ins_pricing_dir()
135
- watchdog_script_path = Path(watchdog_script)
136
- if watchdog_script_path.is_absolute():
137
- watchdog_path = watchdog_script_path.resolve()
138
- else:
139
- # watchdog_run.py is in root
140
- watchdog_path = (pkg_dir / watchdog_script_path).resolve()
141
-
142
- wd_cmd: List[object] = [
143
- sys.executable,
144
- str(watchdog_path),
145
- "--idle-seconds",
146
- str(int(idle_seconds)),
147
- "--max-restarts",
148
- str(int(max_restarts)),
149
- "--restart-delay-seconds",
150
- str(int(restart_delay_seconds)),
151
- ]
152
- if stop_on_nonzero_exit:
153
- wd_cmd.append("--stop-on-nonzero-exit")
154
- wd_cmd.append("--")
155
- wd_cmd.extend(list(cmd))
156
- return _stringify_cmd(wd_cmd)
157
-
158
-
159
- def run(cmd: Sequence[str], *, check: bool = True) -> subprocess.CompletedProcess:
160
- """Run an external command from a notebook (blocking)."""
161
- return subprocess.run(list(cmd), check=check)
162
-
163
-
164
- def run_bayesopt_entry(
165
- *,
166
- config_json: str | Path,
167
- model_keys: Sequence[str],
168
- max_evals: int = 50,
169
- plot_curves: bool = True,
170
- ft_role: Optional[str] = None,
171
- nproc_per_node: int = 1,
172
- use_watchdog: bool = False,
173
- idle_seconds: int = 7200,
174
- max_restarts: int = 50,
175
- restart_delay_seconds: int = 10,
176
- extra_args: Optional[Sequence[str]] = None,
177
- ) -> subprocess.CompletedProcess:
178
- """Convenience wrapper: build and run BayesOpt_entry (optional torchrun + watchdog)."""
179
- args: List[str] = ["--max-evals", str(int(max_evals))]
180
- if plot_curves:
181
- args.append("--plot-curves")
182
- if ft_role:
183
- args += ["--ft-role", str(ft_role)]
184
- if extra_args:
185
- args += list(extra_args)
186
-
187
- cmd = build_bayesopt_entry_cmd(
188
- config_json=config_json,
189
- model_keys=model_keys,
190
- nproc_per_node=nproc_per_node,
191
- extra_args=args,
192
- )
193
- if use_watchdog:
194
- cmd = wrap_with_watchdog(
195
- cmd,
196
- idle_seconds=idle_seconds,
197
- max_restarts=max_restarts,
198
- restart_delay_seconds=restart_delay_seconds,
199
- )
200
- return run(cmd, check=True)
201
-
202
-
203
- def run_from_config(config_json: str | Path) -> subprocess.CompletedProcess:
204
- """Notebook entry point: switch execution modes by editing config.json.
205
-
206
- Convention: config.json may include a `runner` section for notebook control:
207
- - runner.mode: "entry" (default), "incremental", or "explain"
208
- - runner.nproc_per_node: >1 enables torchrun/DDP (entry only)
209
- - runner.model_keys: list of models to run (entry only)
210
- - runner.max_evals / runner.plot_curves / runner.ft_role (entry only; override config fields)
211
- - runner.use_watchdog / runner.idle_seconds / runner.max_restarts / runner.restart_delay_seconds
212
- - runner.incremental_args: List[str] (incremental only; extra args for BayesOpt_incremental.py)
213
- """
214
- pkg_dir = _find_ins_pricing_dir()
215
- config_path = Path(config_json)
216
- if not config_path.is_absolute():
217
- config_path = (pkg_dir / config_path).resolve() if (pkg_dir / config_path).exists() else config_path.resolve()
218
- raw = json.loads(config_path.read_text(encoding="utf-8", errors="replace"))
219
- runner = cast(dict, raw.get("runner") or {})
220
-
221
- mode = str(runner.get("mode") or "entry").strip().lower()
222
- use_watchdog = bool(runner.get("use_watchdog", False))
223
- idle_seconds = int(runner.get("idle_seconds", 7200))
224
- max_restarts = int(runner.get("max_restarts", 50))
225
- restart_delay_seconds = int(runner.get("restart_delay_seconds", 10))
226
-
227
- if mode == "incremental":
228
- inc_args = runner.get("incremental_args") or []
229
- if not isinstance(inc_args, list):
230
- raise ValueError("config.runner.incremental_args must be a list of strings.")
231
- cmd = build_incremental_cmd(config_path, extra_args=[str(x) for x in inc_args])
232
- if use_watchdog:
233
- cmd = wrap_with_watchdog(
234
- cmd,
235
- idle_seconds=idle_seconds,
236
- max_restarts=max_restarts,
237
- restart_delay_seconds=restart_delay_seconds,
238
- )
239
- return run(cmd, check=True)
240
-
241
- if mode == "explain":
242
- exp_args = runner.get("explain_args") or []
243
- if not isinstance(exp_args, list):
244
- raise ValueError("config.runner.explain_args must be a list of strings.")
245
- cmd = build_explain_cmd(config_path, extra_args=[str(x) for x in exp_args])
246
- if use_watchdog:
247
- cmd = wrap_with_watchdog(
248
- cmd,
249
- idle_seconds=idle_seconds,
250
- max_restarts=max_restarts,
251
- restart_delay_seconds=restart_delay_seconds,
252
- )
253
- return run(cmd, check=True)
254
-
255
- if mode != "entry":
256
- raise ValueError(
257
- f"Unsupported runner.mode={mode!r}, expected 'entry', 'incremental', or 'explain'."
258
- )
259
-
260
- model_keys = runner.get("model_keys")
261
- if not model_keys:
262
- model_keys = raw.get("model_keys")
263
- if not model_keys:
264
- model_keys = ["ft"]
265
- if not isinstance(model_keys, list):
266
- raise ValueError("runner.model_keys must be a list of strings.")
267
-
268
- nproc_per_node = int(runner.get("nproc_per_node", 1))
269
- max_evals = int(runner.get("max_evals", raw.get("max_evals", 50)))
270
- plot_curves = bool(runner.get("plot_curves", raw.get("plot_curves", True)))
271
- ft_role = runner.get("ft_role", None)
272
- if ft_role is None:
273
- ft_role = raw.get("ft_role")
274
-
275
- cmd = build_bayesopt_entry_cmd(
276
- config_path,
277
- model_keys=[str(x) for x in model_keys],
278
- nproc_per_node=nproc_per_node,
279
- extra_args=[
280
- "--max-evals",
281
- str(max_evals),
282
- *(["--plot-curves"] if plot_curves else []),
283
- *(["--ft-role", str(ft_role)] if ft_role else []),
284
- ],
285
- )
286
-
287
- if use_watchdog:
288
- cmd = wrap_with_watchdog(
289
- cmd,
290
- idle_seconds=idle_seconds,
291
- max_restarts=max_restarts,
292
- restart_delay_seconds=restart_delay_seconds,
293
- )
294
- return run(cmd, check=True)
@@ -1,45 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from .common import EPS, PlotStyle
4
- from .curves import (
5
- double_lift_table,
6
- lift_table,
7
- plot_calibration_curve,
8
- plot_conversion_lift,
9
- plot_double_lift_curve,
10
- plot_ks_curve,
11
- plot_lift_curve,
12
- plot_pr_curves,
13
- plot_roc_curves,
14
- )
15
- from .diagnostics import plot_loss_curve, plot_oneway
16
- from .geo import (
17
- plot_geo_contour,
18
- plot_geo_contour_on_map,
19
- plot_geo_heatmap,
20
- plot_geo_heatmap_on_map,
21
- )
22
- from .importance import plot_feature_importance, plot_shap_importance, shap_importance
23
-
24
- __all__ = [
25
- "EPS",
26
- "PlotStyle",
27
- "double_lift_table",
28
- "lift_table",
29
- "plot_calibration_curve",
30
- "plot_conversion_lift",
31
- "plot_double_lift_curve",
32
- "plot_feature_importance",
33
- "plot_geo_contour",
34
- "plot_geo_contour_on_map",
35
- "plot_geo_heatmap",
36
- "plot_geo_heatmap_on_map",
37
- "plot_ks_curve",
38
- "plot_lift_curve",
39
- "plot_loss_curve",
40
- "plot_oneway",
41
- "plot_pr_curves",
42
- "plot_roc_curves",
43
- "plot_shap_importance",
44
- "shap_importance",
45
- ]
@@ -1,63 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- from dataclasses import dataclass
5
- from pathlib import Path
6
- from typing import Optional, Sequence, Tuple
7
-
8
- import matplotlib
9
-
10
- if os.name != "nt" and not os.environ.get("DISPLAY") and not os.environ.get("MPLBACKEND"):
11
- matplotlib.use("Agg")
12
- import matplotlib.pyplot as plt
13
-
14
- EPS = 1e-8
15
-
16
-
17
- @dataclass(frozen=True)
18
- class PlotStyle:
19
- figsize: Tuple[float, float] = (8.0, 4.5)
20
- dpi: int = 300
21
- palette: Sequence[str] = (
22
- "#1f77b4",
23
- "#ff7f0e",
24
- "#2ca02c",
25
- "#d62728",
26
- "#9467bd",
27
- "#8c564b",
28
- "#e377c2",
29
- "#7f7f7f",
30
- "#bcbd22",
31
- "#17becf",
32
- )
33
- grid: bool = True
34
- grid_alpha: float = 0.3
35
- grid_style: str = "--"
36
- title_size: int = 10
37
- label_size: int = 8
38
- tick_size: int = 7
39
- legend_size: int = 7
40
- weight_color: str = "seagreen"
41
-
42
-
43
- def ensure_parent_dir(path: str | Path) -> None:
44
- target = Path(path).expanduser().resolve()
45
- target.parent.mkdir(parents=True, exist_ok=True)
46
-
47
-
48
- def finalize_figure(
49
- fig: plt.Figure,
50
- *,
51
- save_path: Optional[str] = None,
52
- show: bool = False,
53
- close: bool = True,
54
- style: Optional[PlotStyle] = None,
55
- ) -> None:
56
- if save_path:
57
- ensure_parent_dir(save_path)
58
- dpi = style.dpi if style else 300
59
- fig.savefig(save_path, dpi=dpi, bbox_inches="tight")
60
- if show:
61
- plt.show()
62
- if close:
63
- plt.close(fig)