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.
- scmgen/__init__.py +39 -0
- scmgen/betafn.py +158 -0
- scmgen/config.py +768 -0
- scmgen/diagnostics.py +45 -0
- scmgen/generate.py +608 -0
- scmgen/graph.py +700 -0
- scmgen/io.py +474 -0
- scmgen/mechanisms.py +419 -0
- scmgen/outcomes.py +259 -0
- scmgen/pathologies.py +1108 -0
- scmgen/reference.py +57 -0
- scmgen/scenarios/__init__.py +38 -0
- scmgen/scenarios/ctl-binary-rd.yaml +76 -0
- scmgen/scenarios/ctl-const-neg.yaml +76 -0
- scmgen/scenarios/ctl-const-pos.yaml +76 -0
- scmgen/scenarios/ctl-linear-cate.yaml +76 -0
- scmgen/scenarios/ctl-null.yaml +76 -0
- scmgen/scenarios/obs-cluster.yaml +83 -0
- scmgen/scenarios/obs-confounding.yaml +76 -0
- scmgen/scenarios/obs-hidden-conf.yaml +82 -0
- scmgen/scenarios/obs-noise-conf.yaml +84 -0
- scmgen/scenarios/obs-overlap.yaml +82 -0
- scmgen/scenarios/obs-targeted.yaml +82 -0
- scmgen/scenarios/rct-bad-controls.yaml +82 -0
- scmgen/scenarios/rct-beta-clean.yaml +76 -0
- scmgen/scenarios/rct-beta-het-seg.yaml +76 -0
- scmgen/scenarios/rct-beta-hetero-seg.yaml +82 -0
- scmgen/scenarios/rct-beta-indep.yaml +76 -0
- scmgen/scenarios/rct-beta-zoi-both.yaml +76 -0
- scmgen/scenarios/rct-beta-zoi.yaml +76 -0
- scmgen/scenarios/rct-clean-disc.yaml +79 -0
- scmgen/scenarios/rct-clean.yaml +76 -0
- scmgen/scenarios/rct-cluster-assign.yaml +83 -0
- scmgen/scenarios/rct-cluster.yaml +82 -0
- scmgen/scenarios/rct-covariate-shift.yaml +82 -0
- scmgen/scenarios/rct-drift-seg.yaml +82 -0
- scmgen/scenarios/rct-drift-tau.yaml +95 -0
- scmgen/scenarios/rct-drift.yaml +82 -0
- scmgen/scenarios/rct-dup-rows.yaml +82 -0
- scmgen/scenarios/rct-fewplacebo-nl.yaml +76 -0
- scmgen/scenarios/rct-fewplacebo.yaml +76 -0
- scmgen/scenarios/rct-graph-blocks-nl.yaml +76 -0
- scmgen/scenarios/rct-graph-blocks.yaml +76 -0
- scmgen/scenarios/rct-graph-clones-nl.yaml +76 -0
- scmgen/scenarios/rct-graph-clones.yaml +76 -0
- scmgen/scenarios/rct-graph-deep.yaml +76 -0
- scmgen/scenarios/rct-graph-dense.yaml +76 -0
- scmgen/scenarios/rct-graph-layered.yaml +76 -0
- scmgen/scenarios/rct-graph-sf-disc.yaml +79 -0
- scmgen/scenarios/rct-graph-sf-nl.yaml +76 -0
- scmgen/scenarios/rct-graph-sf.yaml +76 -0
- scmgen/scenarios/rct-graph-sparse.yaml +76 -0
- scmgen/scenarios/rct-het-seg-disc.yaml +78 -0
- scmgen/scenarios/rct-het-seg.yaml +82 -0
- scmgen/scenarios/rct-hidden-modifier.yaml +82 -0
- scmgen/scenarios/rct-indep.yaml +76 -0
- scmgen/scenarios/rct-mediation-binary.yaml +76 -0
- scmgen/scenarios/rct-mediation-customer.yaml +186 -0
- scmgen/scenarios/rct-mediation-partial.yaml +76 -0
- scmgen/scenarios/rct-missing-mar.yaml +83 -0
- scmgen/scenarios/rct-missing-seg.yaml +82 -0
- scmgen/scenarios/rct-missing-sentinel.yaml +83 -0
- scmgen/scenarios/rct-missing-ty.yaml +86 -0
- scmgen/scenarios/rct-money-het.yaml +76 -0
- scmgen/scenarios/rct-money-null.yaml +82 -0
- scmgen/scenarios/rct-nl-clean.yaml +76 -0
- scmgen/scenarios/rct-nl-het-seg.yaml +82 -0
- scmgen/scenarios/rct-nl-noise-y-seg.yaml +82 -0
- scmgen/scenarios/rct-nl-zeroinfl.yaml +76 -0
- scmgen/scenarios/rct-noise-y-seg.yaml +82 -0
- scmgen/scenarios/rct-noisey-beta.yaml +82 -0
- scmgen/scenarios/rct-noisey-bin.yaml +82 -0
- scmgen/scenarios/rct-noisey-diff-y.yaml +85 -0
- scmgen/scenarios/rct-noisey-diff.yaml +85 -0
- scmgen/scenarios/rct-noisey-ziln.yaml +82 -0
- scmgen/scenarios/rct-noncompliance.yaml +82 -0
- scmgen/scenarios/rct-outliers.yaml +82 -0
- scmgen/scenarios/rct-quantized.yaml +84 -0
- scmgen/scenarios/rct-select-seg-t.yaml +83 -0
- scmgen/scenarios/rct-select-seg.yaml +82 -0
- scmgen/scenarios/rct-t-noise.yaml +82 -0
- scmgen/scenarios/rct-zeroinfl-smalltau.yaml +76 -0
- scmgen/scm.py +577 -0
- scmgen/segments.py +144 -0
- scmgen/truth.py +1042 -0
- scmgen-0.10.0.dist-info/METADATA +155 -0
- scmgen-0.10.0.dist-info/RECORD +89 -0
- scmgen-0.10.0.dist-info/WHEEL +5 -0
- 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)
|