honestab 0.1.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.
- honestab-0.1.0/LICENSE +21 -0
- honestab-0.1.0/PKG-INFO +258 -0
- honestab-0.1.0/README.md +230 -0
- honestab-0.1.0/pyproject.toml +45 -0
- honestab-0.1.0/setup.cfg +4 -0
- honestab-0.1.0/src/honestab/__init__.py +18 -0
- honestab-0.1.0/src/honestab/bayes.py +36 -0
- honestab-0.1.0/src/honestab/checks.py +43 -0
- honestab-0.1.0/src/honestab/cli.py +187 -0
- honestab-0.1.0/src/honestab/datasets.py +73 -0
- honestab-0.1.0/src/honestab/design.py +96 -0
- honestab-0.1.0/src/honestab/experiment.py +205 -0
- honestab-0.1.0/src/honestab/methods.py +208 -0
- honestab-0.1.0/src/honestab/multiple.py +47 -0
- honestab-0.1.0/src/honestab/result.py +46 -0
- honestab-0.1.0/src/honestab/sequential.py +109 -0
- honestab-0.1.0/src/honestab.egg-info/PKG-INFO +258 -0
- honestab-0.1.0/src/honestab.egg-info/SOURCES.txt +26 -0
- honestab-0.1.0/src/honestab.egg-info/dependency_links.txt +1 -0
- honestab-0.1.0/src/honestab.egg-info/entry_points.txt +2 -0
- honestab-0.1.0/src/honestab.egg-info/requires.txt +13 -0
- honestab-0.1.0/src/honestab.egg-info/top_level.txt +1 -0
- honestab-0.1.0/tests/test_cli.py +35 -0
- honestab-0.1.0/tests/test_design_checks.py +87 -0
- honestab-0.1.0/tests/test_experiment.py +93 -0
- honestab-0.1.0/tests/test_methods.py +135 -0
- honestab-0.1.0/tests/test_sequential.py +45 -0
- honestab-0.1.0/tests/test_validation.py +29 -0
honestab-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sofya Babkina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
honestab-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: honestab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A/B test analysis with methods verified by simulation: CUPED, delta method, sequential testing, SRM
|
|
5
|
+
Author: Sofya Babkina
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sofetizm/honestab
|
|
8
|
+
Keywords: ab-testing,experimentation,statistics,cuped,delta-method,sequential-testing
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: numpy>=1.24
|
|
17
|
+
Requires-Dist: scipy>=1.10
|
|
18
|
+
Requires-Dist: pandas>=2.0
|
|
19
|
+
Requires-Dist: pyyaml>=6
|
|
20
|
+
Provides-Extra: plots
|
|
21
|
+
Requires-Dist: matplotlib>=3.7; extra == "plots"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
25
|
+
Requires-Dist: statsmodels>=0.14; extra == "dev"
|
|
26
|
+
Requires-Dist: matplotlib>=3.7; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# honestab — A/B-тесты без самообмана
|
|
30
|
+
|
|
31
|
+
[](https://github.com/sofetizm/honestab/actions/workflows/ci.yml)
|
|
32
|
+
[](https://colab.research.google.com/github/sofetizm/honestab/blob/main/notebooks/demo.ipynb)
|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
Python-библиотека для анализа A/B-тестов, в которой **каждый метод проверен симуляциями**.
|
|
37
|
+
Она проверяет SRM, применяет CUPED, считает ratio-метрики дельта-методом, делает поправку
|
|
38
|
+
на множественность и поддерживает последовательное тестирование. На выходе — готовое
|
|
39
|
+
решение: катить, не катить или не доверять результатам. В комплекте навык для ИИ-агентов
|
|
40
|
+
(Claude Code, Codex CLI и др.), который анализирует эксперименты с помощью этой библиотеки.
|
|
41
|
+
|
|
42
|
+
## Почему «честный»
|
|
43
|
+
|
|
44
|
+
Корректный статистический метод в A/A-тесте (эффекта нет) должен находить «эффект» в 5%
|
|
45
|
+
случаев — это и есть уровень значимости α = 0.05. Типичные ошибки аналитики ломают это
|
|
46
|
+
обещание. Числа ниже — из тысяч симулированных экспериментов ([`validation/`](validation/VALIDATION.md)):
|
|
47
|
+
|
|
48
|
+
| Ошибка | Что получается | Как надо (honestab) |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| Смотреть на p-value каждый день и остановиться, как только p < 0.05 | **24,5%** ложных срабатываний при 20 проверках | mSPRT: 1,2% |
|
|
51
|
+
| Считать CTR по таблице просмотров | **9,8%** ложных срабатываний | дельта-метод: 4,8% |
|
|
52
|
+
| Проверить 10 метрик и порадоваться значимой | **39,6%** шанс хотя бы одной ложной находки | поправка Holm: 4,8% |
|
|
53
|
+
| Не заметить, что в одной группе потерялась часть пользователей | «Эффект» в **99,8%** A/A-тестов | проверка SRM ловит 83,8% и блокирует решение |
|
|
54
|
+
| Не использовать данные до эксперимента | мощность 57,5% | CUPED: 75,3% при тех же 40 000 пользователях |
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
## Быстрый старт
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install honestab
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import honestab as hb
|
|
66
|
+
|
|
67
|
+
report = hb.analyze(df, [ # df: одна строка на пользователя, колонка group = A/B
|
|
68
|
+
hb.Metric("выручка", column="revenue", covariate="pre_revenue", role="primary"),
|
|
69
|
+
hb.Metric("конверсия", column="converted", kind="proportion"),
|
|
70
|
+
hb.Metric("CTR", numerator="clicks", denominator="views"),
|
|
71
|
+
hb.Metric("сессии", column="sessions", role="guardrail"),
|
|
72
|
+
])
|
|
73
|
+
print(report.to_markdown())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Результат:
|
|
77
|
+
|
|
78
|
+
> **Решение: КАТИТЬ**
|
|
79
|
+
|
|
80
|
+
- Основная метрика «выручка» значимо улучшилась: +11.73% (95% ДИ +5.31% … +18.56%).
|
|
81
|
+
|
|
82
|
+
**Проверка SRM:** пройдена — размеры групп [29957, 30043], ожидалось [30000, 30000], p = 0.726
|
|
83
|
+
|
|
84
|
+
| Метрика | Роль | Метод | Контроль | Тест | Эффект | 95% ДИ | p | p (с поправкой) |
|
|
85
|
+
|---|---|---|---|---|---|---|---|---|
|
|
86
|
+
| выручка ✱ | primary | cuped | 279.1 | 311.9 | +11.73% | [+5.31%; +18.56%] | 0.000249 | — |
|
|
87
|
+
| конверсия | secondary | proportions | 0.1064 | 0.1084 | +1.90% | [-2.69%; +6.72%] | 0.423 | 0.423 |
|
|
88
|
+
| CTR ✱ | secondary | ratio (delta method) | 0.06723 | 0.06935 | +3.16% | [+0.73%; +5.65%] | 0.0106 | 0.0211 |
|
|
89
|
+
| сессии | guardrail | welch | 3.508 | 3.491 | -0.49% | [-1.56%; +0.59%] | 0.368 | — |
|
|
90
|
+
|
|
91
|
+
✱ — статистически значимо при α = 0.05 (вторичные метрики — с поправкой holm).
|
|
92
|
+
|
|
93
|
+
Примечания:
|
|
94
|
+
- выручка: theta = 0.1842; corr(y, x) = 0.566; variance reduced by ~32%
|
|
95
|
+
|
|
96
|
+
Что происходит внутри одного вызова:
|
|
97
|
+
1. **Проверка SRM**: если размеры групп не соответствуют плану, решение — «не доверять», и
|
|
98
|
+
метрики не интерпретируются.
|
|
99
|
+
2. **Метод выбирается по типу метрики**: Welch, CUPED (если есть ковариата до эксперимента),
|
|
100
|
+
z-тест для долей, дельта-метод для отношений, бутстреп для медиан.
|
|
101
|
+
3. **Поправка Holm** для вторичных метрик.
|
|
102
|
+
4. **Guardrail-метрики** блокируют выкатку, если значимо ухудшились.
|
|
103
|
+
5. **Решение с объяснением.** Если эффект не найден, отчёт сообщает MDE: какой эффект
|
|
104
|
+
эксперимент вообще мог заметить.
|
|
105
|
+
|
|
106
|
+
## Командная строка
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
honestab analyze data.csv --config experiment.yaml # отчёт в Markdown (или --json)
|
|
110
|
+
honestab design --kind proportion --baseline 0.05 --mde-rel 0.05 --daily 20000
|
|
111
|
+
honestab design --kind mean --baseline 280 --std 1100 --mde-rel 0.05 --variance-reduction 0.3
|
|
112
|
+
honestab srm 50120 49710
|
|
113
|
+
honestab example demo.csv --revenue-lift 0.05 # синтетический эксперимент + конфиг
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
<details>
|
|
117
|
+
<summary>Пример experiment.yaml</summary>
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
group_col: group
|
|
121
|
+
control: A
|
|
122
|
+
treatment: B
|
|
123
|
+
expected_split: 0.5
|
|
124
|
+
alpha: 0.05
|
|
125
|
+
correction: holm # holm | bh | none
|
|
126
|
+
metrics:
|
|
127
|
+
- {name: revenue per user, column: revenue, covariate: pre_revenue, role: primary}
|
|
128
|
+
- {name: conversion, column: converted, kind: proportion}
|
|
129
|
+
- {name: ctr, numerator: clicks, denominator: views}
|
|
130
|
+
- {name: median time, column: time_spent, kind: median}
|
|
131
|
+
- {name: sessions per user, column: sessions, role: guardrail}
|
|
132
|
+
```
|
|
133
|
+
</details>
|
|
134
|
+
|
|
135
|
+
## Методы
|
|
136
|
+
|
|
137
|
+
| Задача | Функция | Суть |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| Среднее на пользователя | `hb.welch` | t-тест Уэлча; относительный эффект с интервалом Филлера |
|
|
140
|
+
| Снижение дисперсии | `hb.cuped` | CUPED: y − θ(x − x̄), x — метрика до эксперимента; дисперсия × (1 − ρ²) |
|
|
141
|
+
| Конверсия | `hb.proportions` | z-тест для двух долей |
|
|
142
|
+
| CTR, средний чек, выручка на сессию | `hb.ratio` | дельта-метод: учитывает, что события одного пользователя связаны |
|
|
143
|
+
| Медиана, перцентили | `hb.bootstrap` | перцентильный бутстреп |
|
|
144
|
+
| Подглядывание | `hb.msprt` | mSPRT: always-valid p-value и интервалы |
|
|
145
|
+
| Много метрик | `hb.holm`, `hb.benjamini_hochberg` | FWER / FDR |
|
|
146
|
+
| Потеря пользователей | `hb.srm` | χ²-тест размеров групп, порог p < 0.001 |
|
|
147
|
+
| Дизайн | `hb.sample_size`, `hb.sample_size_proportion`, `hb.mde`, `hb.duration_days` | размер выборки, MDE, длительность (с учётом CUPED) |
|
|
148
|
+
| Байесовский взгляд | `hb.bayes.beta_binomial` | P(B лучше A), ожидаемые потери |
|
|
149
|
+
|
|
150
|
+
Несколько решений, которых нет в «учебных» реализациях:
|
|
151
|
+
- **Интервал относительного эффекта по Филлеру.** Обычный дельта-метод для «+x%» иногда
|
|
152
|
+
выдаёт интервал, включающий 0, при p < 0.05, и отчёт противоречит сам себе. Интервал
|
|
153
|
+
Филлера исключает ноль ровно тогда, когда значим тест разности; это проверяется тестами
|
|
154
|
+
на 40 случайных выборках.
|
|
155
|
+
- **CUPED с θ по объединённым группам**, без утечки эффекта в оценку.
|
|
156
|
+
- **Бутстреп кусками**: память ограничена даже на сотнях тысяч пользователей.
|
|
157
|
+
- **Результаты сверены с эталонами**: p-value и интервалы совпадают со scipy и statsmodels,
|
|
158
|
+
дисперсия дельта-метода — с линеаризацией.
|
|
159
|
+
|
|
160
|
+
## Валидация
|
|
161
|
+
|
|
162
|
+
[`validation/run.py`](validation/run.py) прогоняет тысячи симулированных экспериментов и
|
|
163
|
+
проверяет оба обещания каждого метода:
|
|
164
|
+
1. без эффекта он ошибается в 5% случаев;
|
|
165
|
+
2. с эффектом находит его так часто, как обещает формула мощности.
|
|
166
|
+
|
|
167
|
+
Полный отчёт с доверительными интервалами — [`validation/VALIDATION.md`](validation/VALIDATION.md).
|
|
168
|
+
|
|
169
|
+
| Проверка | Результат |
|
|
170
|
+
|---|---|
|
|
171
|
+
| A/A-тесты: Welch, CUPED, z-тест, дельта-метод | 4,2% … 5,0% ложных срабатываний (норма — 5%) |
|
|
172
|
+
| Покрытие 95% доверительных интервалов | 95,0% и 95,1% |
|
|
173
|
+
| Ложные тревоги проверки SRM | 0,0% |
|
|
174
|
+
| Мощность при эффекте +10% к выручке | Welch 57,5%, CUPED 75,3% |
|
|
175
|
+
| Цена безопасного подглядывания | mSPRT находит эффект в 51,6% случаев против 82,3% у одного анализа в конце, но останавливается в среднем на 63% срока |
|
|
176
|
+
|
|
177
|
+
Один неочевидный вывод: если эффект мультипликативный (выручка × 1.1), разброс в тестовой
|
|
178
|
+
группе тоже растёт, и учебная формула размера выборки завышает мощность. В
|
|
179
|
+
`hb.sample_size` для этого есть параметр `std_treatment`.
|
|
180
|
+
|
|
181
|
+

|
|
182
|
+
|
|
183
|
+
## Навык для ИИ-агентов
|
|
184
|
+
|
|
185
|
+
[`skills/ab-test-analysis/`](skills/ab-test-analysis/SKILL.md) учит агента анализировать
|
|
186
|
+
эксперимент по шагам:
|
|
187
|
+
1. уточнить гипотезу и основную метрику;
|
|
188
|
+
2. привести данные к строке на пользователя;
|
|
189
|
+
3. проверить SRM;
|
|
190
|
+
4. выбрать метод;
|
|
191
|
+
5. запустить `honestab`;
|
|
192
|
+
6. написать отчёт с интервалами и MDE.
|
|
193
|
+
|
|
194
|
+
Навык не позволяет агенту объявить победителя по сегменту или «тренду к значимости».
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npx skills add sofetizm/honestab
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Или скопируй папку `skills/ab-test-analysis/` в `~/.claude/skills/` (Claude Code),
|
|
201
|
+
`~/.agents/skills/` (Codex CLI), `.github/skills/` (Copilot), `.cursor/skills/` (Cursor).
|
|
202
|
+
|
|
203
|
+
## Демо
|
|
204
|
+
|
|
205
|
+
[`notebooks/demo.ipynb`](notebooks/demo.ipynb) — полный разбор эксперимента с известным
|
|
206
|
+
истинным эффектом: дизайн и экономия трафика с CUPED → SRM → отчёт → почему CTR нельзя
|
|
207
|
+
считать по просмотрам → подглядывание → байесовский взгляд.
|
|
208
|
+
|
|
209
|
+
## Структура
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
src/honestab/
|
|
213
|
+
methods.py welch, proportions, ratio (дельта-метод), cuped, bootstrap
|
|
214
|
+
design.py размер выборки, MDE, длительность
|
|
215
|
+
checks.py SRM
|
|
216
|
+
multiple.py Holm, Benjamini–Hochberg
|
|
217
|
+
sequential.py mSPRT
|
|
218
|
+
bayes.py Beta-Binomial
|
|
219
|
+
experiment.py Metric, analyze, Report — от таблицы до решения
|
|
220
|
+
datasets.py синтетические эксперименты с известным эффектом
|
|
221
|
+
cli.py командная строка
|
|
222
|
+
validation/ симуляции, отчёт и графики
|
|
223
|
+
skills/ навык для ИИ-агентов
|
|
224
|
+
notebooks/ демо
|
|
225
|
+
tests/ сверка с scipy/statsmodels, свойства методов, CLI, сокращённые симуляции
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Разработка
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pip install -e ".[dev]"
|
|
232
|
+
pytest # быстрые тесты, ~15 секунд
|
|
233
|
+
pytest -m slow # сокращённые симуляции, ~2 минуты
|
|
234
|
+
python validation/run.py # полная валидация, ~10 минут
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Ограничения
|
|
238
|
+
|
|
239
|
+
- Два варианта: контроль и тест. Для A/B/n сравни каждый вариант с контролем и сделай
|
|
240
|
+
поправку на множественность.
|
|
241
|
+
- Методы асимптотические: для групп меньше нескольких сотен пользователей интервалы менее точны.
|
|
242
|
+
- Не учитываются сетевые эффекты (взаимодействие пользователей разных групп) и
|
|
243
|
+
кластерная рандомизация (по городам, магазинам).
|
|
244
|
+
- mSPRT оценивает дисперсию по данным — это приближение, точное начиная с нескольких
|
|
245
|
+
сотен наблюдений в группе (что и показывает валидация).
|
|
246
|
+
|
|
247
|
+
## Литература
|
|
248
|
+
|
|
249
|
+
- Kohavi, Tang, Xu. *Trustworthy Online Controlled Experiments*. Cambridge University Press, 2020.
|
|
250
|
+
- Deng, Xu, Kohavi, Walker. *Improving the Sensitivity of Online Controlled Experiments by
|
|
251
|
+
Utilizing Pre-Experiment Data* (CUPED). WSDM 2013.
|
|
252
|
+
- Deng, Knoblich, Lu. *Applying the Delta Method in Metric Analytics*. KDD 2018.
|
|
253
|
+
- Johari, Koomen, Pekelis, Walsh. *Peeking at A/B Tests* (mSPRT). KDD 2017.
|
|
254
|
+
- Fieller. *Some Problems in Interval Estimation*. JRSS B, 1954.
|
|
255
|
+
|
|
256
|
+
## Лицензия
|
|
257
|
+
|
|
258
|
+
MIT
|
honestab-0.1.0/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# honestab — A/B-тесты без самообмана
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sofetizm/honestab/actions/workflows/ci.yml)
|
|
4
|
+
[](https://colab.research.google.com/github/sofetizm/honestab/blob/main/notebooks/demo.ipynb)
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Python-библиотека для анализа A/B-тестов, в которой **каждый метод проверен симуляциями**.
|
|
9
|
+
Она проверяет SRM, применяет CUPED, считает ratio-метрики дельта-методом, делает поправку
|
|
10
|
+
на множественность и поддерживает последовательное тестирование. На выходе — готовое
|
|
11
|
+
решение: катить, не катить или не доверять результатам. В комплекте навык для ИИ-агентов
|
|
12
|
+
(Claude Code, Codex CLI и др.), который анализирует эксперименты с помощью этой библиотеки.
|
|
13
|
+
|
|
14
|
+
## Почему «честный»
|
|
15
|
+
|
|
16
|
+
Корректный статистический метод в A/A-тесте (эффекта нет) должен находить «эффект» в 5%
|
|
17
|
+
случаев — это и есть уровень значимости α = 0.05. Типичные ошибки аналитики ломают это
|
|
18
|
+
обещание. Числа ниже — из тысяч симулированных экспериментов ([`validation/`](validation/VALIDATION.md)):
|
|
19
|
+
|
|
20
|
+
| Ошибка | Что получается | Как надо (honestab) |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Смотреть на p-value каждый день и остановиться, как только p < 0.05 | **24,5%** ложных срабатываний при 20 проверках | mSPRT: 1,2% |
|
|
23
|
+
| Считать CTR по таблице просмотров | **9,8%** ложных срабатываний | дельта-метод: 4,8% |
|
|
24
|
+
| Проверить 10 метрик и порадоваться значимой | **39,6%** шанс хотя бы одной ложной находки | поправка Holm: 4,8% |
|
|
25
|
+
| Не заметить, что в одной группе потерялась часть пользователей | «Эффект» в **99,8%** A/A-тестов | проверка SRM ловит 83,8% и блокирует решение |
|
|
26
|
+
| Не использовать данные до эксперимента | мощность 57,5% | CUPED: 75,3% при тех же 40 000 пользователях |
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
## Быстрый старт
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install honestab
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import honestab as hb
|
|
38
|
+
|
|
39
|
+
report = hb.analyze(df, [ # df: одна строка на пользователя, колонка group = A/B
|
|
40
|
+
hb.Metric("выручка", column="revenue", covariate="pre_revenue", role="primary"),
|
|
41
|
+
hb.Metric("конверсия", column="converted", kind="proportion"),
|
|
42
|
+
hb.Metric("CTR", numerator="clicks", denominator="views"),
|
|
43
|
+
hb.Metric("сессии", column="sessions", role="guardrail"),
|
|
44
|
+
])
|
|
45
|
+
print(report.to_markdown())
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Результат:
|
|
49
|
+
|
|
50
|
+
> **Решение: КАТИТЬ**
|
|
51
|
+
|
|
52
|
+
- Основная метрика «выручка» значимо улучшилась: +11.73% (95% ДИ +5.31% … +18.56%).
|
|
53
|
+
|
|
54
|
+
**Проверка SRM:** пройдена — размеры групп [29957, 30043], ожидалось [30000, 30000], p = 0.726
|
|
55
|
+
|
|
56
|
+
| Метрика | Роль | Метод | Контроль | Тест | Эффект | 95% ДИ | p | p (с поправкой) |
|
|
57
|
+
|---|---|---|---|---|---|---|---|---|
|
|
58
|
+
| выручка ✱ | primary | cuped | 279.1 | 311.9 | +11.73% | [+5.31%; +18.56%] | 0.000249 | — |
|
|
59
|
+
| конверсия | secondary | proportions | 0.1064 | 0.1084 | +1.90% | [-2.69%; +6.72%] | 0.423 | 0.423 |
|
|
60
|
+
| CTR ✱ | secondary | ratio (delta method) | 0.06723 | 0.06935 | +3.16% | [+0.73%; +5.65%] | 0.0106 | 0.0211 |
|
|
61
|
+
| сессии | guardrail | welch | 3.508 | 3.491 | -0.49% | [-1.56%; +0.59%] | 0.368 | — |
|
|
62
|
+
|
|
63
|
+
✱ — статистически значимо при α = 0.05 (вторичные метрики — с поправкой holm).
|
|
64
|
+
|
|
65
|
+
Примечания:
|
|
66
|
+
- выручка: theta = 0.1842; corr(y, x) = 0.566; variance reduced by ~32%
|
|
67
|
+
|
|
68
|
+
Что происходит внутри одного вызова:
|
|
69
|
+
1. **Проверка SRM**: если размеры групп не соответствуют плану, решение — «не доверять», и
|
|
70
|
+
метрики не интерпретируются.
|
|
71
|
+
2. **Метод выбирается по типу метрики**: Welch, CUPED (если есть ковариата до эксперимента),
|
|
72
|
+
z-тест для долей, дельта-метод для отношений, бутстреп для медиан.
|
|
73
|
+
3. **Поправка Holm** для вторичных метрик.
|
|
74
|
+
4. **Guardrail-метрики** блокируют выкатку, если значимо ухудшились.
|
|
75
|
+
5. **Решение с объяснением.** Если эффект не найден, отчёт сообщает MDE: какой эффект
|
|
76
|
+
эксперимент вообще мог заметить.
|
|
77
|
+
|
|
78
|
+
## Командная строка
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
honestab analyze data.csv --config experiment.yaml # отчёт в Markdown (или --json)
|
|
82
|
+
honestab design --kind proportion --baseline 0.05 --mde-rel 0.05 --daily 20000
|
|
83
|
+
honestab design --kind mean --baseline 280 --std 1100 --mde-rel 0.05 --variance-reduction 0.3
|
|
84
|
+
honestab srm 50120 49710
|
|
85
|
+
honestab example demo.csv --revenue-lift 0.05 # синтетический эксперимент + конфиг
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
<details>
|
|
89
|
+
<summary>Пример experiment.yaml</summary>
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
group_col: group
|
|
93
|
+
control: A
|
|
94
|
+
treatment: B
|
|
95
|
+
expected_split: 0.5
|
|
96
|
+
alpha: 0.05
|
|
97
|
+
correction: holm # holm | bh | none
|
|
98
|
+
metrics:
|
|
99
|
+
- {name: revenue per user, column: revenue, covariate: pre_revenue, role: primary}
|
|
100
|
+
- {name: conversion, column: converted, kind: proportion}
|
|
101
|
+
- {name: ctr, numerator: clicks, denominator: views}
|
|
102
|
+
- {name: median time, column: time_spent, kind: median}
|
|
103
|
+
- {name: sessions per user, column: sessions, role: guardrail}
|
|
104
|
+
```
|
|
105
|
+
</details>
|
|
106
|
+
|
|
107
|
+
## Методы
|
|
108
|
+
|
|
109
|
+
| Задача | Функция | Суть |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| Среднее на пользователя | `hb.welch` | t-тест Уэлча; относительный эффект с интервалом Филлера |
|
|
112
|
+
| Снижение дисперсии | `hb.cuped` | CUPED: y − θ(x − x̄), x — метрика до эксперимента; дисперсия × (1 − ρ²) |
|
|
113
|
+
| Конверсия | `hb.proportions` | z-тест для двух долей |
|
|
114
|
+
| CTR, средний чек, выручка на сессию | `hb.ratio` | дельта-метод: учитывает, что события одного пользователя связаны |
|
|
115
|
+
| Медиана, перцентили | `hb.bootstrap` | перцентильный бутстреп |
|
|
116
|
+
| Подглядывание | `hb.msprt` | mSPRT: always-valid p-value и интервалы |
|
|
117
|
+
| Много метрик | `hb.holm`, `hb.benjamini_hochberg` | FWER / FDR |
|
|
118
|
+
| Потеря пользователей | `hb.srm` | χ²-тест размеров групп, порог p < 0.001 |
|
|
119
|
+
| Дизайн | `hb.sample_size`, `hb.sample_size_proportion`, `hb.mde`, `hb.duration_days` | размер выборки, MDE, длительность (с учётом CUPED) |
|
|
120
|
+
| Байесовский взгляд | `hb.bayes.beta_binomial` | P(B лучше A), ожидаемые потери |
|
|
121
|
+
|
|
122
|
+
Несколько решений, которых нет в «учебных» реализациях:
|
|
123
|
+
- **Интервал относительного эффекта по Филлеру.** Обычный дельта-метод для «+x%» иногда
|
|
124
|
+
выдаёт интервал, включающий 0, при p < 0.05, и отчёт противоречит сам себе. Интервал
|
|
125
|
+
Филлера исключает ноль ровно тогда, когда значим тест разности; это проверяется тестами
|
|
126
|
+
на 40 случайных выборках.
|
|
127
|
+
- **CUPED с θ по объединённым группам**, без утечки эффекта в оценку.
|
|
128
|
+
- **Бутстреп кусками**: память ограничена даже на сотнях тысяч пользователей.
|
|
129
|
+
- **Результаты сверены с эталонами**: p-value и интервалы совпадают со scipy и statsmodels,
|
|
130
|
+
дисперсия дельта-метода — с линеаризацией.
|
|
131
|
+
|
|
132
|
+
## Валидация
|
|
133
|
+
|
|
134
|
+
[`validation/run.py`](validation/run.py) прогоняет тысячи симулированных экспериментов и
|
|
135
|
+
проверяет оба обещания каждого метода:
|
|
136
|
+
1. без эффекта он ошибается в 5% случаев;
|
|
137
|
+
2. с эффектом находит его так часто, как обещает формула мощности.
|
|
138
|
+
|
|
139
|
+
Полный отчёт с доверительными интервалами — [`validation/VALIDATION.md`](validation/VALIDATION.md).
|
|
140
|
+
|
|
141
|
+
| Проверка | Результат |
|
|
142
|
+
|---|---|
|
|
143
|
+
| A/A-тесты: Welch, CUPED, z-тест, дельта-метод | 4,2% … 5,0% ложных срабатываний (норма — 5%) |
|
|
144
|
+
| Покрытие 95% доверительных интервалов | 95,0% и 95,1% |
|
|
145
|
+
| Ложные тревоги проверки SRM | 0,0% |
|
|
146
|
+
| Мощность при эффекте +10% к выручке | Welch 57,5%, CUPED 75,3% |
|
|
147
|
+
| Цена безопасного подглядывания | mSPRT находит эффект в 51,6% случаев против 82,3% у одного анализа в конце, но останавливается в среднем на 63% срока |
|
|
148
|
+
|
|
149
|
+
Один неочевидный вывод: если эффект мультипликативный (выручка × 1.1), разброс в тестовой
|
|
150
|
+
группе тоже растёт, и учебная формула размера выборки завышает мощность. В
|
|
151
|
+
`hb.sample_size` для этого есть параметр `std_treatment`.
|
|
152
|
+
|
|
153
|
+

|
|
154
|
+
|
|
155
|
+
## Навык для ИИ-агентов
|
|
156
|
+
|
|
157
|
+
[`skills/ab-test-analysis/`](skills/ab-test-analysis/SKILL.md) учит агента анализировать
|
|
158
|
+
эксперимент по шагам:
|
|
159
|
+
1. уточнить гипотезу и основную метрику;
|
|
160
|
+
2. привести данные к строке на пользователя;
|
|
161
|
+
3. проверить SRM;
|
|
162
|
+
4. выбрать метод;
|
|
163
|
+
5. запустить `honestab`;
|
|
164
|
+
6. написать отчёт с интервалами и MDE.
|
|
165
|
+
|
|
166
|
+
Навык не позволяет агенту объявить победителя по сегменту или «тренду к значимости».
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npx skills add sofetizm/honestab
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Или скопируй папку `skills/ab-test-analysis/` в `~/.claude/skills/` (Claude Code),
|
|
173
|
+
`~/.agents/skills/` (Codex CLI), `.github/skills/` (Copilot), `.cursor/skills/` (Cursor).
|
|
174
|
+
|
|
175
|
+
## Демо
|
|
176
|
+
|
|
177
|
+
[`notebooks/demo.ipynb`](notebooks/demo.ipynb) — полный разбор эксперимента с известным
|
|
178
|
+
истинным эффектом: дизайн и экономия трафика с CUPED → SRM → отчёт → почему CTR нельзя
|
|
179
|
+
считать по просмотрам → подглядывание → байесовский взгляд.
|
|
180
|
+
|
|
181
|
+
## Структура
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
src/honestab/
|
|
185
|
+
methods.py welch, proportions, ratio (дельта-метод), cuped, bootstrap
|
|
186
|
+
design.py размер выборки, MDE, длительность
|
|
187
|
+
checks.py SRM
|
|
188
|
+
multiple.py Holm, Benjamini–Hochberg
|
|
189
|
+
sequential.py mSPRT
|
|
190
|
+
bayes.py Beta-Binomial
|
|
191
|
+
experiment.py Metric, analyze, Report — от таблицы до решения
|
|
192
|
+
datasets.py синтетические эксперименты с известным эффектом
|
|
193
|
+
cli.py командная строка
|
|
194
|
+
validation/ симуляции, отчёт и графики
|
|
195
|
+
skills/ навык для ИИ-агентов
|
|
196
|
+
notebooks/ демо
|
|
197
|
+
tests/ сверка с scipy/statsmodels, свойства методов, CLI, сокращённые симуляции
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Разработка
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
pip install -e ".[dev]"
|
|
204
|
+
pytest # быстрые тесты, ~15 секунд
|
|
205
|
+
pytest -m slow # сокращённые симуляции, ~2 минуты
|
|
206
|
+
python validation/run.py # полная валидация, ~10 минут
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Ограничения
|
|
210
|
+
|
|
211
|
+
- Два варианта: контроль и тест. Для A/B/n сравни каждый вариант с контролем и сделай
|
|
212
|
+
поправку на множественность.
|
|
213
|
+
- Методы асимптотические: для групп меньше нескольких сотен пользователей интервалы менее точны.
|
|
214
|
+
- Не учитываются сетевые эффекты (взаимодействие пользователей разных групп) и
|
|
215
|
+
кластерная рандомизация (по городам, магазинам).
|
|
216
|
+
- mSPRT оценивает дисперсию по данным — это приближение, точное начиная с нескольких
|
|
217
|
+
сотен наблюдений в группе (что и показывает валидация).
|
|
218
|
+
|
|
219
|
+
## Литература
|
|
220
|
+
|
|
221
|
+
- Kohavi, Tang, Xu. *Trustworthy Online Controlled Experiments*. Cambridge University Press, 2020.
|
|
222
|
+
- Deng, Xu, Kohavi, Walker. *Improving the Sensitivity of Online Controlled Experiments by
|
|
223
|
+
Utilizing Pre-Experiment Data* (CUPED). WSDM 2013.
|
|
224
|
+
- Deng, Knoblich, Lu. *Applying the Delta Method in Metric Analytics*. KDD 2018.
|
|
225
|
+
- Johari, Koomen, Pekelis, Walsh. *Peeking at A/B Tests* (mSPRT). KDD 2017.
|
|
226
|
+
- Fieller. *Some Problems in Interval Estimation*. JRSS B, 1954.
|
|
227
|
+
|
|
228
|
+
## Лицензия
|
|
229
|
+
|
|
230
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "honestab"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A/B test analysis with methods verified by simulation: CUPED, delta method, sequential testing, SRM"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Sofya Babkina" }]
|
|
13
|
+
keywords = ["ab-testing", "experimentation", "statistics", "cuped", "delta-method", "sequential-testing"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
]
|
|
20
|
+
dependencies = ["numpy>=1.24", "scipy>=1.10", "pandas>=2.0", "pyyaml>=6"]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
plots = ["matplotlib>=3.7"]
|
|
24
|
+
dev = ["pytest>=8", "ruff>=0.6", "statsmodels>=0.14", "matplotlib>=3.7"]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
honestab = "honestab.cli:main"
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/sofetizm/honestab"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
markers = ["slow: large simulations (~minutes)"]
|
|
38
|
+
addopts = "-m 'not slow'"
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
line-length = 110
|
|
42
|
+
target-version = "py310"
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E", "F", "I", "B", "UP"]
|
honestab-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""honestab — A/B test analysis with methods verified by simulation."""
|
|
2
|
+
from . import bayes, checks, datasets, design, methods, multiple, sequential
|
|
3
|
+
from .checks import srm
|
|
4
|
+
from .design import duration_days, mde, sample_size, sample_size_proportion
|
|
5
|
+
from .experiment import Metric, Report, analyze
|
|
6
|
+
from .methods import bootstrap, cuped, proportions, ratio, welch
|
|
7
|
+
from .multiple import benjamini_hochberg, holm
|
|
8
|
+
from .result import EffectResult
|
|
9
|
+
from .sequential import msprt
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"EffectResult", "Metric", "Report", "analyze", "bayes", "benjamini_hochberg", "bootstrap",
|
|
15
|
+
"checks", "cuped", "datasets", "design", "duration_days", "holm", "mde", "methods", "msprt",
|
|
16
|
+
"multiple", "proportions", "ratio", "sample_size", "sample_size_proportion", "sequential",
|
|
17
|
+
"srm", "welch",
|
|
18
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Bayesian view of a conversion experiment (Beta-Binomial).
|
|
2
|
+
|
|
3
|
+
Gives "probability that B is better" and "expected loss if we choose B". Useful for
|
|
4
|
+
communicating results; it does **not** make peeking safe and does not replace the
|
|
5
|
+
pre-registered frequentist decision.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class BayesResult:
|
|
16
|
+
prob_treatment_better: float
|
|
17
|
+
expected_loss_treatment: float
|
|
18
|
+
expected_loss_control: float
|
|
19
|
+
rel_effect_ci: tuple[float, float]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def beta_binomial(successes_control: int, n_control: int, successes_treatment: int,
|
|
23
|
+
n_treatment: int, prior: tuple[float, float] = (1.0, 1.0),
|
|
24
|
+
draws: int = 200_000, seed: int | None = 0, ci: float = 0.95) -> BayesResult:
|
|
25
|
+
a, b = prior
|
|
26
|
+
rng = np.random.default_rng(seed)
|
|
27
|
+
pc = rng.beta(a + successes_control, b + n_control - successes_control, draws)
|
|
28
|
+
pt = rng.beta(a + successes_treatment, b + n_treatment - successes_treatment, draws)
|
|
29
|
+
rel = pt / pc - 1
|
|
30
|
+
q = (1 - ci) / 2
|
|
31
|
+
return BayesResult(
|
|
32
|
+
prob_treatment_better=float(np.mean(pt > pc)),
|
|
33
|
+
expected_loss_treatment=float(np.mean(np.maximum(pc - pt, 0))),
|
|
34
|
+
expected_loss_control=float(np.mean(np.maximum(pt - pc, 0))),
|
|
35
|
+
rel_effect_ci=(float(np.quantile(rel, q)), float(np.quantile(rel, 1 - q))),
|
|
36
|
+
)
|