scmgen 0.10.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 (89) hide show
  1. scmgen/__init__.py +39 -0
  2. scmgen/betafn.py +158 -0
  3. scmgen/config.py +768 -0
  4. scmgen/diagnostics.py +45 -0
  5. scmgen/generate.py +608 -0
  6. scmgen/graph.py +700 -0
  7. scmgen/io.py +474 -0
  8. scmgen/mechanisms.py +419 -0
  9. scmgen/outcomes.py +259 -0
  10. scmgen/pathologies.py +1108 -0
  11. scmgen/reference.py +57 -0
  12. scmgen/scenarios/__init__.py +38 -0
  13. scmgen/scenarios/ctl-binary-rd.yaml +76 -0
  14. scmgen/scenarios/ctl-const-neg.yaml +76 -0
  15. scmgen/scenarios/ctl-const-pos.yaml +76 -0
  16. scmgen/scenarios/ctl-linear-cate.yaml +76 -0
  17. scmgen/scenarios/ctl-null.yaml +76 -0
  18. scmgen/scenarios/obs-cluster.yaml +83 -0
  19. scmgen/scenarios/obs-confounding.yaml +76 -0
  20. scmgen/scenarios/obs-hidden-conf.yaml +82 -0
  21. scmgen/scenarios/obs-noise-conf.yaml +84 -0
  22. scmgen/scenarios/obs-overlap.yaml +82 -0
  23. scmgen/scenarios/obs-targeted.yaml +82 -0
  24. scmgen/scenarios/rct-bad-controls.yaml +82 -0
  25. scmgen/scenarios/rct-beta-clean.yaml +76 -0
  26. scmgen/scenarios/rct-beta-het-seg.yaml +76 -0
  27. scmgen/scenarios/rct-beta-hetero-seg.yaml +82 -0
  28. scmgen/scenarios/rct-beta-indep.yaml +76 -0
  29. scmgen/scenarios/rct-beta-zoi-both.yaml +76 -0
  30. scmgen/scenarios/rct-beta-zoi.yaml +76 -0
  31. scmgen/scenarios/rct-clean-disc.yaml +79 -0
  32. scmgen/scenarios/rct-clean.yaml +76 -0
  33. scmgen/scenarios/rct-cluster-assign.yaml +83 -0
  34. scmgen/scenarios/rct-cluster.yaml +82 -0
  35. scmgen/scenarios/rct-covariate-shift.yaml +82 -0
  36. scmgen/scenarios/rct-drift-seg.yaml +82 -0
  37. scmgen/scenarios/rct-drift-tau.yaml +95 -0
  38. scmgen/scenarios/rct-drift.yaml +82 -0
  39. scmgen/scenarios/rct-dup-rows.yaml +82 -0
  40. scmgen/scenarios/rct-fewplacebo-nl.yaml +76 -0
  41. scmgen/scenarios/rct-fewplacebo.yaml +76 -0
  42. scmgen/scenarios/rct-graph-blocks-nl.yaml +76 -0
  43. scmgen/scenarios/rct-graph-blocks.yaml +76 -0
  44. scmgen/scenarios/rct-graph-clones-nl.yaml +76 -0
  45. scmgen/scenarios/rct-graph-clones.yaml +76 -0
  46. scmgen/scenarios/rct-graph-deep.yaml +76 -0
  47. scmgen/scenarios/rct-graph-dense.yaml +76 -0
  48. scmgen/scenarios/rct-graph-layered.yaml +76 -0
  49. scmgen/scenarios/rct-graph-sf-disc.yaml +79 -0
  50. scmgen/scenarios/rct-graph-sf-nl.yaml +76 -0
  51. scmgen/scenarios/rct-graph-sf.yaml +76 -0
  52. scmgen/scenarios/rct-graph-sparse.yaml +76 -0
  53. scmgen/scenarios/rct-het-seg-disc.yaml +78 -0
  54. scmgen/scenarios/rct-het-seg.yaml +82 -0
  55. scmgen/scenarios/rct-hidden-modifier.yaml +82 -0
  56. scmgen/scenarios/rct-indep.yaml +76 -0
  57. scmgen/scenarios/rct-mediation-binary.yaml +76 -0
  58. scmgen/scenarios/rct-mediation-customer.yaml +186 -0
  59. scmgen/scenarios/rct-mediation-partial.yaml +76 -0
  60. scmgen/scenarios/rct-missing-mar.yaml +83 -0
  61. scmgen/scenarios/rct-missing-seg.yaml +82 -0
  62. scmgen/scenarios/rct-missing-sentinel.yaml +83 -0
  63. scmgen/scenarios/rct-missing-ty.yaml +86 -0
  64. scmgen/scenarios/rct-money-het.yaml +76 -0
  65. scmgen/scenarios/rct-money-null.yaml +82 -0
  66. scmgen/scenarios/rct-nl-clean.yaml +76 -0
  67. scmgen/scenarios/rct-nl-het-seg.yaml +82 -0
  68. scmgen/scenarios/rct-nl-noise-y-seg.yaml +82 -0
  69. scmgen/scenarios/rct-nl-zeroinfl.yaml +76 -0
  70. scmgen/scenarios/rct-noise-y-seg.yaml +82 -0
  71. scmgen/scenarios/rct-noisey-beta.yaml +82 -0
  72. scmgen/scenarios/rct-noisey-bin.yaml +82 -0
  73. scmgen/scenarios/rct-noisey-diff-y.yaml +85 -0
  74. scmgen/scenarios/rct-noisey-diff.yaml +85 -0
  75. scmgen/scenarios/rct-noisey-ziln.yaml +82 -0
  76. scmgen/scenarios/rct-noncompliance.yaml +82 -0
  77. scmgen/scenarios/rct-outliers.yaml +82 -0
  78. scmgen/scenarios/rct-quantized.yaml +84 -0
  79. scmgen/scenarios/rct-select-seg-t.yaml +83 -0
  80. scmgen/scenarios/rct-select-seg.yaml +82 -0
  81. scmgen/scenarios/rct-t-noise.yaml +82 -0
  82. scmgen/scenarios/rct-zeroinfl-smalltau.yaml +76 -0
  83. scmgen/scm.py +577 -0
  84. scmgen/segments.py +144 -0
  85. scmgen/truth.py +1042 -0
  86. scmgen-0.10.0.dist-info/METADATA +155 -0
  87. scmgen-0.10.0.dist-info/RECORD +89 -0
  88. scmgen-0.10.0.dist-info/WHEEL +5 -0
  89. scmgen-0.10.0.dist-info/top_level.txt +1 -0
scmgen/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ """SCM-генератор синтетических данных для uplift/CATE-бенчмарка.
2
+
3
+ Спецификация: `docs/model.md`; контракт полей истины и фазы патологий (0.5.0) —
4
+ см. CHANGELOG 0.5.0; объект SCM, уровни seed, provenance, оси 0.6.0 — `docs/model.md`;
5
+ контракт истины 0.7.0, рецепт/replay, версионная публикация — `docs/truth-contract.md`;
6
+ реестр патологий — `docs/pathology-catalog.md`.
7
+ """
8
+
9
+ from importlib.metadata import PackageNotFoundError, version as _pkg_version
10
+
11
+ from . import scenarios
12
+ from .config import (PATHOLOGY_NUMBERS, GraphConfig, MechanismConfig, MediatorConfig, OutcomeConfig,
13
+ PathologyConfig, ScenarioConfig, SegmentConfig, TauConfig, TreatmentConfig, pathology_number,
14
+ pathology_phase)
15
+ from .diagnostics import data_properties
16
+ from .generate import Dataset, generate, generate_pair, generate_split
17
+ from .graph import SampledGraph, graph_properties, sample_graph
18
+ from .io import load_dataset, load_scm, prune_versions, replay, save_dataset
19
+ from .pathologies import apply_pathologies
20
+ from .reference import pehe, segment_dim, t_learner_ridge
21
+ from .scm import SCM, sample_scm
22
+ from .segments import build_segments
23
+ from .truth import refresh_truth, truth_targets
24
+
25
+ try:
26
+ __version__ = _pkg_version("scmgen")
27
+ except PackageNotFoundError: # запуск из дерева исходников без установки
28
+ __version__ = "0.10.0"
29
+
30
+ __all__ = [
31
+ "ScenarioConfig", "GraphConfig", "MechanismConfig", "TauConfig",
32
+ "TreatmentConfig", "OutcomeConfig", "SegmentConfig", "PathologyConfig", "MediatorConfig",
33
+ "Dataset", "generate", "generate_pair", "generate_split", "apply_pathologies",
34
+ "SCM", "sample_scm", "load_scm", "replay", "prune_versions", "PATHOLOGY_NUMBERS", "pathology_number",
35
+ "build_segments", "SampledGraph", "sample_graph", "graph_properties",
36
+ "save_dataset", "load_dataset", "t_learner_ridge", "segment_dim", "pehe",
37
+ "data_properties", "pathology_phase", "refresh_truth", "truth_targets",
38
+ "scenarios", "__version__",
39
+ ]
scmgen/betafn.py ADDED
@@ -0,0 +1,158 @@
1
+ """Бета-распределение на numpy (0.8.0, см. CHANGELOG 0.8.0): ln Γ, регуляризованная неполная
2
+ бета-функция I_x(a, b) и обратная к ней по x. scipy в зависимости не входит (как и для Φ⁻¹ в truth.py).
3
+
4
+ Все функции поэлементны в строгом смысле: результат элемента зависит только от его аргументов. Итерации
5
+ (цепная дробь, уточнение квантиля) идут до сходимости каждого элемента отдельно, сошедшиеся элементы
6
+ замораживаются — поэтому вложенные выборки (`scm.sample(n₁)` — префикс `scm.sample(n₂)`) совпадают бит в бит.
7
+ Область: a, b ∈ [1e-3, 1e4], x, p ∈ [0, 1]; точность закреплена тестами (tests/test_v080.py).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import numpy as np
13
+
14
+ _LANCZOS_G = 7.0
15
+ _LANCZOS = (0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313,
16
+ -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6,
17
+ 1.5056327351493116e-7)
18
+ _HALF_LOG_2PI = 0.5 * np.log(2.0 * np.pi)
19
+ _CF_EPS = 1e-16
20
+ _CF_TINY = 1e-300
21
+ _CF_MAXIT = 2000
22
+ _INV_MAXIT = 200
23
+
24
+
25
+ def gammaln(x) -> np.ndarray:
26
+ """ln Γ(x), x > 0 (Ланцош g = 7, 9 членов; x < 0.5 — через Γ(x + 1) = x Γ(x))."""
27
+ x = np.asarray(x, dtype=float)
28
+ small = x < 0.5
29
+ z = np.where(small, x + 1.0, x) - 1.0
30
+ acc = np.full(x.shape, _LANCZOS[0])
31
+ for i in range(1, 9):
32
+ acc = acc + _LANCZOS[i] / (z + i)
33
+ t = z + _LANCZOS_G + 0.5
34
+ out = _HALF_LOG_2PI + (z + 0.5) * np.log(t) - t + np.log(acc)
35
+ return np.where(small, out - np.log(np.where(small, x, 1.0)), out)
36
+
37
+
38
+ def betaln(a, b) -> np.ndarray:
39
+ """ln B(a, b)."""
40
+ return gammaln(a) + gammaln(b) - gammaln(np.add(a, b))
41
+
42
+
43
+ def _betacf(a: np.ndarray, b: np.ndarray, x: np.ndarray) -> np.ndarray:
44
+ """Цепная дробь неполной бета-функции (модифицированный метод Ленца); одномерные массивы равной длины.
45
+ Каждый элемент итерируется до |Δ − 1| < eps и замораживается."""
46
+ out = np.empty(x.size)
47
+ idx = np.arange(x.size)
48
+ qab, qap, qam = a + b, a + 1.0, a - 1.0
49
+ c = np.ones(x.size)
50
+ d = 1.0 - qab * x / qap
51
+ d = 1.0 / np.where(np.abs(d) < _CF_TINY, _CF_TINY, d)
52
+ h = d.copy()
53
+ for m in range(1, _CF_MAXIT + 1):
54
+ m2 = 2.0 * m
55
+ aa = m * (b - m) * x / ((qam + m2) * (a + m2))
56
+ d = 1.0 + aa * d
57
+ d = 1.0 / np.where(np.abs(d) < _CF_TINY, _CF_TINY, d)
58
+ c = 1.0 + aa / c
59
+ c = np.where(np.abs(c) < _CF_TINY, _CF_TINY, c)
60
+ h = h * d * c
61
+ aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2))
62
+ d = 1.0 + aa * d
63
+ d = 1.0 / np.where(np.abs(d) < _CF_TINY, _CF_TINY, d)
64
+ c = 1.0 + aa / c
65
+ c = np.where(np.abs(c) < _CF_TINY, _CF_TINY, c)
66
+ delta = d * c
67
+ h = h * delta
68
+ done = np.abs(delta - 1.0) < _CF_EPS
69
+ if done.any():
70
+ out[idx[done]] = h[done]
71
+ keep = ~done
72
+ if not keep.any():
73
+ return out
74
+ idx, a, b, x, c, d, h = idx[keep], a[keep], b[keep], x[keep], c[keep], d[keep], h[keep]
75
+ qab, qap, qam = qab[keep], qap[keep], qam[keep]
76
+ out[idx] = h # не сошлось за _CF_MAXIT: вне заявленной области
77
+ return out
78
+
79
+
80
+ def betainc(a, b, x, lnb=None) -> np.ndarray:
81
+ """Регуляризованная неполная бета-функция I_x(a, b) = P(Beta(a, b) ≤ x); x вне [0, 1] обрезается.
82
+ lnb — заранее посчитанный ln B(a, b) (той же формы после broadcast), иначе считается здесь."""
83
+ a, b, x = np.broadcast_arrays(np.asarray(a, dtype=float), np.asarray(b, dtype=float),
84
+ np.asarray(x, dtype=float))
85
+ shape = x.shape
86
+ lnb = betaln(a, b) if lnb is None else np.broadcast_to(np.asarray(lnb, dtype=float), shape)
87
+ a, b, x, lnb = a.ravel(), b.ravel(), np.clip(x.ravel(), 0.0, 1.0), lnb.ravel()
88
+ out = np.where(x >= 1.0, 1.0, 0.0)
89
+ inner = np.flatnonzero((x > 0.0) & (x < 1.0))
90
+ if inner.size:
91
+ a, b, x, lnb = a[inner], b[inner], x[inner], lnb[inner]
92
+ front = np.exp(a * np.log(x) + b * np.log1p(-x) - lnb)
93
+ direct = x < (a + 1.0) / (a + b + 2.0)
94
+ aa, bb, xx = np.where(direct, a, b), np.where(direct, b, a), np.where(direct, x, 1.0 - x)
95
+ val = front * _betacf(aa, bb, xx) / aa
96
+ out[inner] = np.where(direct, val, 1.0 - val)
97
+ return out.reshape(shape)
98
+
99
+
100
+ def _initial_guess(a, b, p):
101
+ """Начальное приближение квантиля (Numerical Recipes, invbetai)."""
102
+ with np.errstate(divide="ignore", invalid="ignore", over="ignore"):
103
+ pp = np.where(p < 0.5, p, 1.0 - p)
104
+ t = np.sqrt(-2.0 * np.log(pp))
105
+ z = (2.30753 + t * 0.27061) / (1.0 + t * (0.99229 + t * 0.04481)) - t
106
+ z = np.where(p < 0.5, -z, z)
107
+ al = (z * z - 3.0) / 6.0
108
+ ia, ib = 1.0 / (2.0 * a - 1.0), 1.0 / (2.0 * b - 1.0)
109
+ hh = 2.0 / (ia + ib)
110
+ w = z * np.sqrt(al + hh) / hh - (ib - ia) * (al + 5.0 / 6.0 - 2.0 / (3.0 * hh))
111
+ big = a / (a + b * np.exp(2.0 * w))
112
+ lna, lnbb = np.log(a / (a + b)), np.log(b / (a + b))
113
+ ta, tb = np.exp(a * lna) / a, np.exp(b * lnbb) / b
114
+ ww = ta + tb
115
+ small = np.where(p < ta / ww, np.power(a * ww * p, 1.0 / a), 1.0 - np.power(b * ww * (1.0 - p), 1.0 / b))
116
+ x = np.where((a >= 1.0) & (b >= 1.0), big, small)
117
+ return np.where(np.isfinite(x), x, 0.5)
118
+
119
+
120
+ def betaincinv(a, b, p) -> np.ndarray:
121
+ """Квантиль бета-распределения: x с I_x(a, b) = p. Шаги Галлея с вилкой [lo, hi] (вне вилки — бисекция),
122
+ каждый элемент — до сходимости шага и замораживается."""
123
+ a, b, p = np.broadcast_arrays(np.asarray(a, dtype=float), np.asarray(b, dtype=float),
124
+ np.asarray(p, dtype=float))
125
+ shape = p.shape
126
+ a, b, p = a.ravel(), b.ravel(), p.ravel()
127
+ out = np.where(p >= 1.0, 1.0, 0.0)
128
+ idx = np.flatnonzero((p > 0.0) & (p < 1.0))
129
+ if not idx.size:
130
+ return out.reshape(shape)
131
+ a, b, p = a[idx], b[idx], p[idx]
132
+ lnb = betaln(a, b)
133
+ a1, b1 = a - 1.0, b - 1.0
134
+ lo, hi = np.zeros(p.size), np.ones(p.size)
135
+ x = np.clip(_initial_guess(a, b, p), 1e-300, 1.0 - 1e-16)
136
+ for _ in range(_INV_MAXIT):
137
+ err = betainc(a, b, x, lnb) - p
138
+ lo = np.where(err < 0.0, x, lo)
139
+ hi = np.where(err > 0.0, x, hi)
140
+ with np.errstate(divide="ignore", invalid="ignore", over="ignore"):
141
+ dens = np.exp(a1 * np.log(x) + b1 * np.log1p(-x) - lnb)
142
+ u = err / dens
143
+ step = u / (1.0 - 0.5 * np.clip(u * (a1 / x - b1 / (1.0 - x)), -1.0, 1.0))
144
+ new = x - step
145
+ bad = ~np.isfinite(new) | (new <= lo) | (new >= hi) | ~np.isfinite(dens) | (dens <= 0.0)
146
+ new = np.where(bad, 0.5 * (lo + hi), new)
147
+ done = (err == 0.0) | (np.abs(new - x) <= 4e-16 * x) | (hi - lo <= 4e-16 * hi)
148
+ x = np.where(err == 0.0, x, new)
149
+ if done.any():
150
+ out[idx[done]] = x[done]
151
+ keep = ~done
152
+ if not keep.any():
153
+ break
154
+ idx, a, b, p, lnb, a1, b1 = idx[keep], a[keep], b[keep], p[keep], lnb[keep], a1[keep], b1[keep]
155
+ lo, hi, x = lo[keep], hi[keep], x[keep]
156
+ else:
157
+ out[idx] = x
158
+ return out.reshape(shape)