Post Analysis Toolkit
post_analysis_toolkit - локальная библиотека для пост-анализа промо, rollout-ов и других квази-экспериментальных задач, где нужен воспроизводимый causal / quasi-causal пайплайн.
Пакет закрывает типовой цикл:
- подготовили панель или временной ряд;
- проверили предпосылки метода;
- посчитали эффект;
- проверили устойчивость результата;
- получили табличные результаты и готовые графики.
Документация в этом файле ориентирована на пользователя пакета.
Что умеет пакет
- классический
Difference-in-Differences - регрессионный
DiD DiDс cluster-robust SE по unit по умолчанию илиHC1при явномcluster_col=NoneDiDсTWFEDiDс лог-трансформацией outcomeDiDс относительной индексацией outcomeSynthetic ControlSynthetic DiD (SDiD)черезdiff-diffCausal ImpactInterrupted Time SeriesTriple DifferenceRegression Discontinuity DesignPropensity Score MatchingDoubly Robust / AIPWEvent Studyс эффектами по временным бинам- проверки предпосылок по каждому методу
- базовые проверки устойчивости
- встроенные
matplotlib-графики
Стандартные ошибки и кластеризация
Для fit_did_regression, fit_event_study и fit_triple_difference по
умолчанию используется односторонняя cluster-robust ошибка. При
cluster_col="auto" (значение по умолчанию) пакет сначала использует
unit_col, а затем ищет стандартную колонку unit, unit_id, pizzeria,
city или cluster. Чтобы явно отключить кластеризацию и использовать HC1,
передайте cluster_col=None. Если в данных нет подходящей колонки, auto
безопасно откатывается к HC1; фактически выбранный режим смотрите в
result.metadata["stderr_type"].
Односторонняя cluster covariance — CR1-style: statsmodels применяет
finite-sample correction, а p-value по умолчанию рассчитывается с нормальной
аппроксимацией (use_t=False). При небольшом числе кластеров можно выбрать
se_type="cr2" или se_type="cr3": это leverage-adjusted варианты
кластерной covariance без внешних зависимостей.
Для регрессионных методов можно явно выбрать se_type: "nonrobust" (обычная
OLS SE без коррекции), "hc1",
"cluster" (один уровень), "cr2", "cr3", "wild_cluster" или
"two_way_cluster" (Cameron–Gelbach–Miller, unit × time). "auto" сохраняет прежнее поведение: кластеризация по
cluster_col, если она задана/найдена, иначе HC1. В two-way режиме второй
уровень по умолчанию берётся из time_col; его можно переопределить
time_cluster_col. wild_cluster использует Rademacher wild cluster
bootstrap для эмпирической covariance; число повторов задаётся
wild_bootstrap_reps (по умолчанию 999), а seed — через random_state.
Это bootstrap-оценка SE, а не отдельный null-imposed bootstrap-тест p-value.
fit_its использует HAC/Newey–West (hac_lags=7 по умолчанию), но также
поддерживает se_type="hc1", "cluster", "cr2", "cr3",
"wild_cluster" и "two_way_cluster" для панельных
данных. fit_its/check_its_assumptions принимают cluster_col и
time_cluster_col, а run_its_robustness по умолчанию перебирает
hac_lags=(0, 3, 7, 14). Для RDD two-way режим требует явного
time_cluster_col; density-регрессия в проверке предпосылок остаётся HC1.
Панельный ITS передаётся в long-формате с одной строкой на unit × date.
Передайте se_type="cluster" и cluster_col с идентификатором юнита:
panel_its = fit_its(
panel_df,
outcome_col="orders_per_unit_day",
time_col="sale_date",
intervention_time="2026-06-14",
se_type="cluster",
cluster_col="pizzeria_id",
)
Для такой панели holdout и rolling-метрики считаются по календарным датам
(среднее по юнитам), а график эффекта также показывает средний ряд по датам.
PSM и Doubly Robust используют IID-оценку по score/differences.
Synthetic Control не имеет модельной SE/p-value и использует placebo-диагностики,
SDiD — variance_method="placebo", "bootstrap" или "jackknife" из
diff-diff, а Causal Impact — байесовские posterior-интервалы. При небольшом
числе кластеров, особенно при одном treated-кластере, кластерные p-value и FPR
могут быть нестабильны; их нужно сопоставлять с placebo и sensitivity.
В ITS hac_lags — максимальный лаг автокорреляции для HAC/Newey–West. Для
ежедневных данных 7 — разумная стартовая настройка при недельной сезонности,
но это не универсальное значение. На практике сравнивайте несколько соседних
значений (например, 0, 3, 7, 14) и проверяйте, меняется ли вывод об
эффекте; run_its_robustness делает такой перебор автоматически.
Для ITS se_type="auto" означает: если передан или автоматически найден
cluster_col, использовать one-way cluster; если кластер не задан — HAC.
При этом значение по умолчанию у fit_its и check_its_assumptions —
se_type="hac", поэтому для автоматического выбора нужно указать
se_type="auto" явно.
MDE и чувствительность
Для всех MDE по умолчанию используются alpha=0.05, power=0.80 и
двусторонний тест. Если метрика и дизайн допускают аналитическую оценку,
базовый ориентир — t-test/формула через корректную стандартную ошибку.
MDE относится к конкретному estimand и спецификации. В рабочем результате нужно отдельно показывать абсолютный MDE в единицах метрики, относительный MDE относительно явно указанного baseline и cumulative MDE за весь post-period.
Режим mde_mode="auto" используется по умолчанию: если у метода есть корректная
стандартная ошибка, выбирается аналитический расчет, иначе — параметрическая
симуляция. mde_mode="parametric" принудительно включает быструю параметрическую
симуляцию.
mde_mode="resampling" включает полноценные симуляции с генерацией данных и
повторным запуском оценивателя. Быстрый proxy доступен только явно как
mde_mode="proxy". Для симуляций число повторов будет задаваться параметром
n_simulations: 500 для отладки, 2000 для рабочего
расчета и 5000+ для финального отчета. Вместе с ним нужно сохранять
random_state и power curve.
Параметрическая симуляция: для каждого кандидатного эффекта пакет
генерирует оценки по схеме delta + Normal(0, uncertainty_scale). Для
регрессионных и observational-методов uncertainty_scale — SE. Для Event Study
это SE конкретного коэффициента конкретного временного бина, поэтому MDE
считается отдельно для каждого бина. Для Synthetic Control и Causal Impact
используется pre-fit или validation RMSE, нормированный на размер post-period;
для ITS при доступной covariance matrix используется delta-method SE среднего
post-period gap, а validation RMSE остаётся fallback.
В режиме resampling пакет создает псевдоданные: берет fitted/counterfactual
значения без эффекта, добавляет ресэмплированные центрированные остатки и
кандидатный эффект, а затем повторно запускает весь поддержанный оцениватель.
Для Synthetic Control заново подбираются веса, для PSM заново строится matching,
для Causal Impact заново обучается state-space модель, для ITS заново
оценивается регрессия, а для SDiD заново подбираются unit/time-веса. Такой режим заметно медленнее и требует явно задать
разумное n_simulations и, при необходимости, mde_effect_grid.
Power curve можно посмотреть в
result.details["mde_power_curve"].
После вызова любой fit_* MDE читается так:
result.summary[["mde_absolute", "mde_relative", "mde_cumulative", "mde_mode_resolved"]]
result.details["mde_power_curve"]
Пример полноценного resampling-MDE для поддержанного метода:
result = fit_synthetic_control(
panel_df,
outcome_col="outcome",
unit_col="unit",
time_col="sale_date",
treated_unit="treated_city",
intervention_time="2026-02-01",
mde_mode="resampling",
n_simulations=200,
mde_effect_grid=[0, 2, 4, 6],
random_state=42,
)
result.summary[["mde_mode_resolved", "mde_absolute"]]
В этой версии для каждого кандидата эффекта создаются псевдоданные и заново
вызывается Synthetic Control. Поэтому resampling значительно медленнее
параметрического режима. Для нестандартного оценивателя можно передать
mde_simulator, который возвращает массив повторных оценок эффекта.
Параметры mde_mode, alpha, power, alternative, n_simulations,
random_state, mde_effect_grid и mde_simulator добавлены в функции оценки эффекта. Вызов с
mde_mode="proxy" дополнительно заполняет proxy_mde_*; при analytic-,
parametric- или resampling-режиме эти legacy-поля остаются пустыми.
8. PSM
Используй, когда:
- treatment бинарный
- есть наблюдаемые ковариаты, через которые можно объяснять селекцию в treatment
- хочется построить matched control на основе propensity score
В текущей версии пакет реализует ATT через nearest-neighbor matching по propensity score.
9. Doubly Robust
Используй, когда:
- treatment бинарный
- есть набор ковариат
- хочется оценку, устойчивую к ошибке в одной из двух моделей: propensity model или outcome model
В текущей версии это AIPW / doubly robust ATE.
Что пакет не делает
- не строит данные из сырых таблиц
- не проверяет бизнес-смысл метрик за тебя
- не знает, был ли contamination доноров
- не умеет автоматически находить “правильный” метод
- не снимает необходимость ручной интерпретации
То есть пакет отвечает за оценивание и diagnostics, а не за бизнес-контекст и data engineering.
Быстрый старт
Установка
В ноутбуке или Python-скрипте:
%pip install post-analysis-toolkit
Чтобы библиотека была доступна во всех ноутбуках персонального Databricks-кластера,
один раз добавьте PyPI-библиотеку
post-analysis-toolkit в разделе Libraries его настроек.
Базовый импорт
from post_analysis_toolkit import (
check_did_assumptions,
check_doubly_robust_assumptions,
check_synthetic_did_assumptions,
fit_did_regression,
fit_event_study,
fit_doubly_robust,
fit_synthetic_control,
fit_synthetic_did,
fit_causal_impact,
fit_its,
fit_psm,
fit_rdd,
fit_triple_difference,
run_synthetic_did_robustness,
)
Как начать, если видишь пакет впервые
Практически всегда работа с пакетом выглядит так:
- Подготовь данные в одном из поддерживаемых форматов:
панель
unit x time, один временной ряд или observational dataset. - Выбери метод под свой дизайн.
- Сначала запусти
check_*_assumptions(...). - Потом запусти
fit_* (...). - Если метод выглядит применимым, запусти
run_*_robustness(...). - Посмотри
summary,detailsиfigures.
Минимальный шаблон:
diag = check_did_assumptions(...)
display(diag.checks)
result = fit_did_regression(...)
display(result.summary)
robust = run_did_robustness(...)
display(robust.summary)
Особенно важно:
- подробное описание параметров теперь находится прямо внутри описания каждой функции, а не в отдельном общем словаре;
- если видишь аргумент вида
*_options, это почти всегда значит “перебор нескольких спецификаций”, а не “одна настройка модели”.
Как читать result-объекты
Почти все функции возвращают один из двух dataclass-объектов:
DiagnosticResult: результат проверки предпосылокMethodResult: результат расчета эффекта или robustness-check
Обычно внутри есть:
summary: компактная итоговая таблицаdetails: более подробные таблицы, например коэффициенты, веса, placebo-результатыfigures: готовыеmatplotlib-графикиmetadata: технические объекты, конфиги, fit-результаты библиотек
Базовые структуры данных
Panel data
Для DiD, Synthetic Control, Causal Impact ожидается длинная панель, обычно такого вида:
import pandas as pd
panel_df = pd.DataFrame(
{
"sale_date": [...],
"unit": [...], # город / пиццерия / страна / магазин
"metric": [...], # outcome
"treated_flag": [...], # 1 для treated, 0 для control
"post_flag": [...], # 1 после старта промо, 0 до
}
)
Минимальные требования:
time_colдолжен парситься в датуoutcome_colдолжен быть числовымtreated_colиpost_colдолжны кодироваться как0/1- для
Synthetic ControlиCausal Impactдолжен существоватьunit_col
Time series data
Для ITS нужен один временной ряд:
ts_df = pd.DataFrame(
{
"sale_date": [...],
"metric": [...],
}
)
Дополнительно можно передавать внешние фичи:
ts_df["weather_temp"] = ...
ts_df["holiday_flag"] = ...
Observational data
Для PSM и Doubly Robust нужен наблюдательный датасет с бинарным treatment:
obs_df = pd.DataFrame(
{
"user_id": [...],
"treatment_flag": [...], # 0/1
"outcome": [...],
"x1": [...],
"x2": [...],
"x3": [...],
}
)
Минимальные требования:
treatment_colбинарный0/1outcome_colчисловой- covariates должны быть числовыми или заранее закодированными
RDD data
Для RDD нужен срез наблюдений с running variable:
rdd_df = pd.DataFrame(
{
"running": [...], # расстояние до порога или исходная шкала
"outcome": [...],
"x1": [...], # optional covariates
}
)
Минимальные требования:
running_colчисловой- известен
cutoff - по обе стороны cutoff есть наблюдения
Triple Difference data
Для Triple Difference нужна панель с третьим бинарным измерением:
ddd_df = pd.DataFrame(
{
"sale_date": [...],
"unit": [...],
"treated_flag": [...],
"post_flag": [...],
"subgroup_flag": [...], # 1 = affected subgroup, 0 = internal control subgroup
"outcome": [...],
}
)
Полностью готовые минимальные примеры
Ниже четыре примера, которые можно буквально скопировать в ноутбук и запустить как первый smoke-test пакета.
1. End-to-end пример: DiD
import pandas as pd
from post_analysis_toolkit import (
check_did_assumptions,
fit_did_regression,
run_did_robustness,
)
did_df = pd.DataFrame(
{
"sale_date": pd.date_range("2026-02-01", periods=12, freq="D").tolist() * 2,
"unit": ["Batumi"] * 12 + ["Tbilisi"] * 12,
"metric": [
10, 11, 10, 12, 11, 10, 15, 16, 15, 17, 16, 15,
9, 10, 9, 11, 10, 9, 10, 11, 10, 12, 11, 10,
],
"treated_flag": [1] * 12 + [0] * 12,
}
)
did_df["post_flag"] = (did_df["sale_date"] >= pd.Timestamp("2026-02-07")).astype(int)
did_diag = check_did_assumptions(
did_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
post_col="post_flag",
intervention_time="2026-02-07",
unit_col="unit",
cluster_col="unit",
twfe=True,
)
print(did_diag.checks)
# Можно сравнить несколько эмпирических FPR-процедур одновременно.
did_diag = check_did_assumptions(
did_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
post_col="post_flag",
intervention_time="2026-02-07",
unit_col="unit",
cluster_col="unit",
twfe=True,
fpr_method=("time_placebo", "block_bootstrap", "placebo_units"),
fpr_placebo_count=200,
fpr_block_length=7,
)
print(did_diag.details["fpr_results"])
# Важно: FPR-процедуры должны получать ту же спецификацию, что и оценка
# эффекта (например, twfe=True, одинаковые covariates/log_transform/relative).
did_result = fit_did_regression(
did_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
post_col="post_flag",
unit_col="unit",
cluster_col="unit",
twfe=True,
)
print(did_result.summary)
did_robust = run_did_robustness(
did_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
post_col="post_flag",
intervention_time="2026-02-07",
unit_col="unit",
cluster_col="unit",
)
print(did_robust.summary)
2. End-to-end пример: Synthetic Control
import pandas as pd
from post_analysis_toolkit import (
check_synthetic_control_assumptions,
fit_synthetic_control,
run_synthetic_control_robustness,
)
dates = pd.date_range("2026-02-01", periods=14, freq="D")
sc_df = pd.DataFrame(
{
"sale_date": list(dates) * 3,
"unit": ["Batumi"] * 14 + ["Tbilisi"] * 14 + ["Kutaisi"] * 14,
"metric": (
[100, 101, 99, 100, 102, 101, 100, 110, 111, 109, 112, 111, 110, 109]
+ [98, 99, 97, 98, 100, 99, 98, 99, 100, 98, 101, 100, 99, 98]
+ [102, 103, 101, 102, 104, 103, 102, 103, 104, 102, 105, 104, 103, 102]
),
}
)
sc_diag = check_synthetic_control_assumptions(
sc_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-08",
fpr_method=("time_placebo", "block_bootstrap", "placebo_units"),
fpr_placebo_count=20,
fpr_block_length=7,
)
print(sc_diag.checks)
print(sc_diag.details["fpr_results"])
sc_result = fit_synthetic_control(
sc_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-08",
)
print(sc_result.summary)
sc_robust = run_synthetic_control_robustness(
sc_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-08",
)
print(sc_robust.summary)
3. End-to-end пример: ITS
import pandas as pd
from post_analysis_toolkit import (
check_its_assumptions,
fit_its,
run_its_robustness,
)
its_df = pd.DataFrame(
{
"sale_date": pd.date_range("2026-01-01", periods=20, freq="D"),
"metric": [
100, 102, 101, 103, 102, 101, 100, 102, 101, 103,
108, 110, 109, 111, 110, 109, 108, 110, 109, 111,
],
}
)
its_diag = check_its_assumptions(
its_df,
outcome_col="metric",
time_col="sale_date",
intervention_time="2026-01-11",
)
print(its_diag.checks)
# Для ITS доступен time placebo; placebo_units
# неприменим, потому что это single-series метод без units.
its_diag = check_its_assumptions(
its_df,
outcome_col="metric",
time_col="sale_date",
intervention_time="2026-01-11",
fpr_method="time_placebo",
fpr_placebo_count=200,
)
print(its_diag.details["fpr_results"])
its_result = fit_its(
its_df,
outcome_col="metric",
time_col="sale_date",
intervention_time="2026-01-11",
)
print(its_result.summary)
its_robust = run_its_robustness(
its_df,
outcome_col="metric",
time_col="sale_date",
intervention_time="2026-01-11",
)
print(its_robust.summary)
# Для panel ITS можно выбрать кластеризацию по unit и два уровня unit × time.
panel_its = fit_its(
panel_df,
outcome_col="outcome",
time_col="sale_date",
intervention_time="2026-02-01",
se_type="cluster",
cluster_col="unit",
)
panel_its_two_way = fit_its(
panel_df,
outcome_col="outcome",
time_col="sale_date",
intervention_time="2026-02-01",
se_type="two_way_cluster",
cluster_col="unit",
time_cluster_col="sale_date",
)
# Для HAC sensitivity автоматически перебираются 0, 3, 7 и 14 лагов.
4. End-to-end пример: PSM
import pandas as pd
from post_analysis_toolkit import (
check_psm_assumptions,
fit_psm,
run_psm_robustness,
)
psm_df = pd.DataFrame(
{
"user_id": list(range(1, 13)),
"treatment_flag": [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
"outcome": [12, 11, 13, 12, 14, 13, 9, 8, 10, 9, 11, 10],
"x1": [30, 32, 31, 29, 33, 34, 30, 31, 29, 28, 32, 33],
"x2": [5, 6, 5, 4, 6, 7, 5, 5, 4, 4, 6, 6],
"x3": [100, 110, 105, 98, 115, 120, 99, 108, 101, 97, 112, 118],
}
)
psm_diag = check_psm_assumptions(
psm_df,
outcome_col="outcome",
treatment_col="treatment_flag",
covariate_cols=["x1", "x2", "x3"],
caliper=0.2,
)
print(psm_diag.checks)
psm_result = fit_psm(
psm_df,
outcome_col="outcome",
treatment_col="treatment_flag",
covariate_cols=["x1", "x2", "x3"],
caliper=0.2,
)
print(psm_result.summary)
psm_robust = run_psm_robustness(
psm_df,
outcome_col="outcome",
treatment_col="treatment_flag",
covariate_cols=["x1", "x2", "x3"],
caliper_options=(None, 0.1, 0.2),
neighbor_options=(1, 2),
)
print(psm_robust.summary)
Что возвращают функции
У пакета два основных result-объекта:
MethodResultDiagnosticResult
Оба описаны в contracts.py
MethodResult
Поля:
method- имя методаsummary- главный итоговыйDataFramedetails- словарь с дополнительными таблицамиfigures- словарьmatplotlib-фигурmetadata- служебные объекты, fit-модели, выбранные доноры и т.д.
DiagnosticResult
Поля:
method- имя диагностикиchecks- таблица проверок предпосылокdetails- вспомогательные таблицыfigures- диагностические графикиmetadata- служебная информация
Как обычно с этим работать
diag = check_did_assumptions(...)
display(diag.checks)
result = fit_did_regression(...)
display(result.summary)
display(result.details["coefficients"])
fig = result.figures["actual_vs_counterfactual"]
display(fig)
Рекомендуемый workflow
Для любого метода лучше идти одинаково:
- Проверить, что метрика и grain корректны.
- Проверить предпосылки через
check_*_assumptions. - Посчитать основной эффект через
fit_*. - Прогнать
run_*_robustness. - Уже после этого писать вывод.
Минимальный шаблон:
diag = check_did_assumptions(...)
display(diag.checks)
result = fit_did_regression(...)
display(result.summary)
robust = run_did_robustness(...)
display(robust.summary)
DiD: пример
Формат данных для функции
Пример для unit x day:
| sale_date | unit | metric | treated_flag | post_flag |
|---|---|---|---|---|
| 2026-02-10 | Batumi | 0.084 | 1 | 0 |
| 2026-02-10 | Tbilisi | 0.121 | 0 | 0 |
| 2026-02-11 | Batumi | 0.079 | 1 | 0 |
| 2026-02-25 | Batumi | 0.116 | 1 | 1 |
| 2026-02-25 | Tbilisi | 0.118 | 0 | 1 |
Обязательные колонки:
sale_dateunitmetrictreated_flagpost_flag
Опционально:
- ковариаты
- веса
- колонка для кластеризации ошибок
1. Проверка предпосылок
from post_analysis_toolkit import check_did_assumptions
did_diag = check_did_assumptions(
df=panel_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
post_col="post_flag",
intervention_time="2026-02-25",
unit_col="unit",
cluster_col="unit",
event_bin_size=7,
)
Что смотреть:
- строку
Корреляция treated/control на предпериоде - строку
Разница наклонов тренда на предпериоде - строку
p-value разницы наклонов на предпериоде - строку
p-value joint-теста лидов в event study - графики
pretrend_triptychиevent_study
2. Оценка эффекта
from post_analysis_toolkit import fit_did_regression
did_result = fit_did_regression(
df=panel_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
post_col="post_flag",
unit_col="unit",
cluster_col="unit",
twfe=True,
log_transform=False,
relative=False,
)
Главные поля в summary:
effect_absolute,effect_relative,effect_cumulativep_valueeffect_cimde_absolute,mde_relative,mde_cumulative,mde_mode_resolveddetails["mde_power_curve"]
Для DiD режим auto по умолчанию считает MDE аналитически через SE interaction-
оценки. Параметрический режим можно включить через mde_mode="parametric", а
полноценный повторный запуск регрессии — через mde_mode="resampling". При
кластеризации ориентируйся также на stderr_type.
3. Robustness
from post_analysis_toolkit import run_did_robustness
did_robust = run_did_robustness(
df=panel_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
post_col="post_flag",
unit_col="unit",
cluster_col="unit",
)
Это прогоняет набор спецификаций:
- с
TWFEи без - с логами и без
- с относительной индексацией и без
Для отдельной динамики эффекта используется Event Study. В его проверке предпосылок FPR считается по joint Wald-тесту всех post-бинов (а не по минимальному p-value отдельного бина):
from post_analysis_toolkit import check_event_study_assumptions, fit_event_study
event_diag = check_event_study_assumptions(
panel_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
intervention_time="2026-02-25",
unit_col="unit",
cluster_col="unit",
fpr_method=("time_placebo", "block_bootstrap", "placebo_units"),
fpr_placebo_count=200,
fpr_block_length=7,
)
event_result = fit_event_study(
panel_df,
outcome_col="metric",
time_col="sale_date",
treated_col="treated_flag",
intervention_time="2026-02-25",
unit_col="unit",
cluster_col="unit",
event_bin_size=7,
)
В event_result.details["event_study_summary"] доступны
joint_pre_p_value и joint_post_p_value; FPR-таблица находится в
event_diag.details["fpr_results"]. MDE по каждому event-бину можно получить
уже на этапе проверки предпосылок:
display(event_diag.details["event_study_mde"])
Для расчёта MDE на этом этапе используются те же параметры, что и в
fit_event_study (mde_mode, power, alternative, n_simulations,
random_state, mde_effect_grid, mde_simulator). По умолчанию mde_mode="auto"
выбирает аналитический MDE по стандартной ошибке каждого event-бина.
Synthetic Control: пример
Формат данных для функции
Пример для unit x day с одним treated unit и несколькими донорами:
| sale_date | unit | metric |
|---|---|---|
| 2026-02-10 | Batumi | 0.084 |
| 2026-02-10 | Tbilisi | 0.121 |
| 2026-02-10 | Kutaisi | 0.097 |
| 2026-02-11 | Batumi | 0.079 |
| 2026-02-11 | Tbilisi | 0.120 |
Обязательные колонки:
sale_dateunitmetric
Что важно:
- одна строка на один
unitв одну дату - donor units не должны быть затронуты treatment
- metric должна быть сопоставима между unit-ами
from post_analysis_toolkit import (
check_synthetic_control_assumptions,
fit_synthetic_control,
run_synthetic_control_robustness,
)
synth_diag = check_synthetic_control_assumptions(
df=panel_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-25",
donor_units=["Tbilisi", "Kutaisi"],
fpr_method=("time_placebo", "block_bootstrap", "placebo_units"),
fpr_placebo_count=20,
fpr_block_length=7,
)
print(synth_diag.details["fpr_results"])
Помимо in-space placebo_units, Synthetic Control поддерживает
time_placebo (фиктивные даты внутри предпериода) и block_bootstrap
(нулевые панели из общих временных блоков). Подробные результаты находятся в
details["time_placebo_results"] и details["block_bootstrap_results"];
план доступных прогонов — в details["fpr_plan"].
synth_result = fit_synthetic_control(
df=panel_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-25",
donor_units=["Tbilisi", "Kutaisi"],
)
synth_robust = run_synthetic_control_robustness(
df=panel_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-25",
donor_units=["Tbilisi", "Kutaisi"],
)
Что смотреть:
donor_countpre_rmsepre_r2pre_corravg_post_effectavg_post_relative_effect- таблицу весов
details["weights"] details["weight_concentration"]с min/max весом, effective donors, числом доноров с положительным весом и долей веса крупнейшего донораdetails["fpr_results"]с in-space FPR и permutation p-value
Проверки устойчивости:
- placebo по каждому донору
- leave-one-out по донорам
- in-space placebo-калибровка FPR в
check_synthetic_control_assumptions time_placeboс фиктивными датами внутри предпериодаblock_bootstrapс нулевыми панелями из общих временных блоков
Для Synthetic Control нет модельного p-value/SE. In-space FPR по очереди
делает каждого донора псевдотритментом, сравнивает post/pre RMSPE-ratio с
остальными донорами и возвращает permutation p-value исходной treated unit.
time_placebo переносит дату вмешательства внутрь предпериода, а
block_bootstrap создаёт нулевые панели, ресэмплируя общие временные блоки.
В обоих случаях значимость всё равно определяется in-space permutation по
RMSPE-ratio, а не обычной модельной SE. Число успешных placebo_units
прогонов не может быть больше размера donor pool; для остальных методов
ограничение задаётся числом допустимых дат или успешных bootstrap-прогонов.
Для содержательной калибровки желательно иметь минимум три донора: при
маленьком donor pool permutation p-value дискретен и FPR получается грубой
диагностикой.
Концентрация donor weights не является автоматическим pass/fail-критерием.
Если top1_weight_share близка к 100% или effective_donors близко к 1,
нужно дополнительно проверить leave_one_out_results и устойчивость к
расширению/очистке donor pool.
Synthetic DiD (SDiD)
Synthetic DiD сочетает DiD с unit- и time-весами synthetic control. В пакетe доступны три функции:
fit_synthetic_did(...)— расчёт эффекта и synthetic counterfactual;check_synthetic_did_assumptions(...)— pre-fit с учётом level shift, proxy-MDE и placebo FPR;run_synthetic_did_robustness(...)— чувствительность к pre-window и placebo датам.
from post_analysis_toolkit import fit_synthetic_did
sdid_result = fit_synthetic_did(
df=panel_df,
outcome_col="revenue",
unit_col="pizzeria",
time_col="sale_date",
treated_col="treated_flag",
intervention_time="2026-02-01",
variance_method="placebo",
)
display(sdid_result.summary)
display(sdid_result.details["unit_weights"])
display(sdid_result.figures["actual_vs_counterfactual"])
treated_col должен быть бинарным и постоянным внутри unit. Основные поля
summary: effect_absolute, effect_relative, effect_cumulative,
effect_ci, effect_relative_ci, effect_cumulative_ci, std_error, p_value, mde_absolute, mde_relative,
mde_cumulative, mde_mode_resolved.
Для SDiD доступны mde_mode="analytic", "parametric", "resampling" и
"proxy". В resampling можно передать mde_simulator; если он не задан,
пакет автоматически строит residual-bootstrap pipeline и повторно вызывает
diff-diff для каждой симулированной панели.
В details["unit_weights"] и details["time_weights"] доступны полные
списки весов. В details["weight_concentration"] пакет дополнительно
показывает min/max, эффективное число доноров и pre-периодов, общее число
pre-периодов, число периодов с положительным time weight, долю effective
pre-периодов и долю веса трёх/пяти самых важных периодов. Это описательные
метрики без автоматически заданных порогов; effective_pre_periods — это
ESS по time weights, а не буквальное число использованных дат.
В details["series"] сохраняются сырые counterfactual/gap и
level-shift-adjusted counterfactual_level_adjusted/gap_level_adjusted.
effect_cumulative и графики SDiD используют adjusted gap, чтобы не включать
допустимую постоянную разницу уровней в оценку инкрементального эффекта.
Для SDiD поддерживаются variance_method="placebo", "bootstrap" и
"jackknife". Режим MDE auto при корректной SE от diff-diff выбирает
аналитический plug-in расчёт; analytic можно задать явно. Это не закрытая
SDiD-формула дизайн-мощности: unit/time weights оцениваются на исходных
данных, а MDE использует итоговую inferential SE. parametric делает быстрые
симуляции оценок с той же SE, proxy использует level-shift-adjusted pre-fit
RMSE. resampling строит псевдопанели, добавляет эффект к treated-юнитам в
post-периоде и заново оценивает SDiD вместе с unit/time-весами на каждом
прогоне; это точнее, но существенно медленнее.
effect_ci — доверительный интервал взвешенного ATT от diff-diff.
effect_relative_ci и effect_cumulative_ci — plug-in преобразования этого
интервала для относительного (со знаком %) и cumulative estimand; дополнительная
неопределённость baseline и level-shift alignment в них не моделируется.
В check_synthetic_did_assumptions(...) параметр fpr_placebo_count задаёт
число placebo-прогонов. Если передать zeta_omega или zeta_lambda, эта же
регуляризация будет использована и в основном fit, и во всех placebo-fit —
это позволяет честно сравнивать эмпирическую FPR между вариантами
zeta_lambda.
Параметр fpr_method принимает одну строку или список методов: time_placebo
(fake intervention даты внутри pre-period), block_bootstrap (ресэмплинг
общих временных блоков без эффекта) и placebo_units (формирование placebo-
treated групп контрольных пиццерий того же размера, что и исходная treated-
группа). Можно передать несколько методов сразу,
например fpr_method=("time_placebo", "block_bootstrap", "placebo_units").
Для block bootstrap длина блока задаётся fpr_block_length (по умолчанию 7).
Сводка по каждому методу находится в details["fpr_results"]; при выборе
нескольких методов добавляются method-specific строки в checks и график
figures["fpr_comparison"]. fpr_placebo_count используется как число
попыток для time placebo/block bootstrap и как максимум контрольных units для
placebo units; фактический знаменатель находится в колонке runs, а размер
placebo-группы — в placebo_group_size.
Практический порядок работы: сначала запустите
check_synthetic_did_assumptions(...) и посмотрите одновременно на FPR и
details["weight_concentration"]. Если FPR заметно выше заданного alpha, а
effective_pre_periods мало и top-3 time weights забирают большую долю веса,
повторите основной fit и проверку с явно заданным большим zeta_lambda:
sdid_result = fit_synthetic_did(..., zeta_lambda=25_000.0)
sdid_checks = check_synthetic_did_assumptions(
..., zeta_lambda=25_000.0,
)
Это не универсальный порог: значение зависит от масштаба outcome и должно
выбираться по sensitivity-таблице вместе с предметной интерпретацией. При
изменении единиц outcome (например, переводе рублей в тысячи) zeta_lambda
нужно масштабировать пропорционально. Если регуляризация зафиксирована, одно и
то же значение следует передавать и в основной fit, и в FPR-проверку.
Не следует выбирать zeta_lambda только для того, чтобы получить FPR около 5%:
регуляризация меняет time-weighted estimand. Сначала задайте сетку значений,
сравните effective pre-periods, концентрацию top-3/top-5 весов, pre-fit и
оценку эффекта, а затем зафиксируйте настройку до финального вывода.
В robustness можно опционально включить нативные проверки diff-diff:
robust = run_synthetic_did_robustness(
panel_df,
outcome_col="revenue",
unit_col="pizzeria",
time_col="sale_date",
treated_col="treated_flag",
intervention_time="2026-02-01",
diff_diff_checks=("in_time_placebo", "zeta_omega_sensitivity"),
diff_diff_placebo_periods=["2025-12-15"],
zeta_lambda_multipliers=(0.1, 1.0, 10.0, 100.0, 1_000.0, 10_000.0, 100_000.0),
)
Доступные значения: in_time_placebo, leave_one_out (требует
variance_method="jackknife") и zeta_omega_sensitivity. По умолчанию
diff_diff_checks=(), поэтому дополнительные нативные прогоны не выполняются.
Параметр zeta_lambda_multipliers запускает отдельные полные SDiD-фиты для
множителей автоматически выбранного zeta_lambda; по умолчанию он пустой,
чтобы не увеличивать время расчёта. Результаты находятся в
details["zeta_lambda_sensitivity"], а графики — в
figures["zeta_lambda_sensitivity"] и
figures["zeta_lambda_weight_concentration"]. В таблице вместе с эффектом
показываются ESS, доля effective pre-периодов, число положительных весов и
накопленная доля top-3/top-5 time weights.
Causal Impact: пример
Формат данных для функции
Формат на вход такой же, как у Synthetic Control.
Пример:
| sale_date | unit | metric |
|---|---|---|
| 2026-02-10 | Batumi | 0.084 |
| 2026-02-10 | Tbilisi | 0.121 |
| 2026-02-10 | Kutaisi | 0.097 |
| 2026-02-11 | Batumi | 0.079 |
| 2026-02-11 | Tbilisi | 0.120 |
Обязательные колонки:
sale_dateunitmetric
Что важно:
- treated unit один
- pre-period должен быть достаточно длинным
- после выравнивания treated и donor рядов не должно быть пропусков
from post_analysis_toolkit import (
check_causal_impact_assumptions,
fit_causal_impact,
run_causal_impact_robustness,
)
ci_diag = check_causal_impact_assumptions(
df=panel_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-25",
donor_units=["Tbilisi", "Kutaisi"],
)
# Для Causal Impact доступны time placebo, block bootstrap и placebo units.
ci_diag = check_causal_impact_assumptions(
df=panel_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-25",
donor_units=["Tbilisi", "Kutaisi"],
fpr_method=("time_placebo", "block_bootstrap", "placebo_units"),
fpr_placebo_count=200,
fpr_block_length=7,
)
print(ci_diag.details["fpr_results"])
ci_result = fit_causal_impact(
df=panel_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-25",
donor_units=["Tbilisi", "Kutaisi"],
)
ci_robust = run_causal_impact_robustness(
df=panel_df,
outcome_col="metric",
unit_col="unit",
time_col="sale_date",
treated_unit="Batumi",
intervention_time="2026-02-25",
donor_units=["Tbilisi", "Kutaisi"],
)
Что смотреть:
- строку
R2 на валидации - строку
RMSE на валидации average_abs_effectaverage_rel_effecteffect_absolute,effect_relative,effect_cumulativeeffect_absolute_ci,effect_relative_ciposterior_tail_areamde_absolute,mde_relative,mde_cumulative,mde_mode_resolveddetails["mde_power_curve"]
Для Causal Impact simulation-режим много раз получает случайную оценку с масштабом, равным RMSE на валидации. Это еще не повторный запуск state-space pipeline для каждого pseudo-dataset; такой callback остается отдельным расширением.
Проверки устойчивости:
- placebo по более ранним датам
- leave-one-out по донорам
ITS: пример
Формат данных для функции
Базовый пример для одного временного ряда:
| sale_date | metric |
|---|---|
| 2026-01-01 | 125.0 |
| 2026-01-02 | 118.0 |
| 2026-01-03 | 133.0 |
| 2026-02-25 | 149.0 |
| 2026-02-26 | 152.0 |
Если есть дополнительные регрессоры:
| sale_date | metric | weather_temp | holiday_flag |
|---|---|---|---|
| 2026-01-01 | 125.0 | 8.3 | 1 |
| 2026-01-02 | 118.0 | 7.9 | 0 |
| 2026-02-25 | 149.0 | 10.4 | 0 |
Обязательные колонки:
sale_datemetric
Что важно:
- одна строка на одну дату
- доп. фичи должны быть на том же дневном grain
- ряд должен быть числовым и упорядочиваемым по времени
from post_analysis_toolkit import (
build_standard_its_features,
check_its_assumptions,
fit_its,
run_its_robustness,
)
its_diag = check_its_assumptions(
df=ts_df,
outcome_col="metric",
time_col="sale_date",
intervention_time="2026-02-25",
)
its_result = fit_its(
df=ts_df,
outcome_col="metric",
time_col="sale_date",
intervention_time="2026-02-25",
)
its_robust = run_its_robustness(
df=ts_df,
outcome_col="metric",
time_col="sale_date",
intervention_time="2026-02-25",
)
По умолчанию ITS использует:
time_indexpost_flagtime_after_intervention- day-of-week dummies
month_start_flagmonth_end_flag
Можно добавить дополнительные фичи:
its_result = fit_its(
df=ts_df,
outcome_col="metric",
time_col="sale_date",
intervention_time="2026-02-25",
extra_feature_cols=["weather_temp", "holiday_flag"],
)
Что смотреть:
avg_abs_effectavg_rel_effecteffect_absolute,effect_relative,effect_cumulative- строку
R2 на валидации - строку
RMSE на валидации - строку
Средний rolling R2 на валидации mde_absolute,mde_relative,mde_cumulative,mde_mode_resolveddetails["mde_power_curve"]
Для ITS simulation-режим много раз получает случайную оценку с масштабом, равным RMSE на валидации. Он относится к выбранному агрегированному постпериодному estimand и не является отдельным MDE для level и slope.
Проверки устойчивости:
- разные длины pre-window
- placebo intervention dates
- разные feature sets
Для проверки эмпирической FPR у ITS доступен fpr_method="time_placebo":
пакет переоценивает модель на нескольких фиктивных датах вмешательства внутри
предпериода. placebo_units для single-series ряда неприменим. Режим
block_bootstrap для ITS не поддерживается; при его передаче будет ValueError.
Число попыток задаётся fpr_placebo_count, а результаты сохраняются в
details["fpr_results"].
Стандартные проверки предпосылок
Во всех checks-таблицах пакет теперь возвращает русские колонки:
проверказначениеориентир
Для DiD
- визуальная параллельность трендов
- pre-period correlation
- slope gap на pre-period
- joint pre-trend test через event study
- отсутствие параллельных изменений
Для Synthetic Control
- достаточно доноров
- хороший pre-fit
- отсутствие contamination доноров
- reasonable donor weights
- in-space placebo FPR и permutation p-value
Для Causal Impact
- достаточно длинный pre-period
- нормальное predictive quality на pre-period
- отсутствие пропусков после выравнивания
- отсутствие contamination контрольных рядов
Для ITS
- достаточно длинный pre-period
- нет сильных дырок в датах
- модель более-менее объясняет pre-period
- residual diagnostics не выглядят катастрофически
- нет явных concurrent changes
Интерпретация результатов
Пакет возвращает числа, но не ставит автоматический ярлык “causal effect proven”.
Практически удобно мыслить так:
- если assumptions слабые, а эффект красивый - не переоценивай вывод
- если assumptions хорошие, но
MDEогромный - отсутствие значимости мало что значит - если robustness разваливается - основной результат надо считать хрупким
- если control-конструкция сомнительна - лучше честный descriptive / benchmark framing
Типичные ошибки
1. Неправильный grain
Например:
- treated на уровне города
- а outcome собран на уровне товара x день без аккуратной агрегации
Сначала выровняй unit анализа.
2. Плохой pre-period
Если pre-period слишком короткий, то:
ITSбудет слабымCausal Impactбудет плохо предсказыватьSynthetic Controlбудет переобучаться
3. Перепутан post_flag
Проверь, что:
- до даты intervention
post_flag = 0 - начиная с даты intervention
post_flag = 1
4. Causal-язык без проверок
Нельзя говорить “акция вызвала рост”, если:
- assumptions не прошли
- donor pool слабый
- robustness разваливается
Рекомендации по использованию в пост-анализах
- Для продаж с нормальным donor pool начинай с
Synthetic ControlилиCausal Impact - Для benchmark-сравнения treated vs control держи
DiD - Если контроль надежно собрать не удалось, переходи к
ITS - Если даже
ITSслабый, оставляй descriptive analysis
Где смотреть готовые примеры
- демо-скрипт: run_post_analysis_toolkit_demo.py
- демо-скрипт: demo.py
- smoke-тесты: test_smoke.py
- Batumi notebook v2: batumi_sales_effect_validation_notebook_v2.ipynb
Публичный API
Пакет экспортирует:
compute_classic_didfit_did_regressionfit_event_studycheck_did_assumptionsrun_did_robustnessfit_triple_differencecheck_triple_difference_assumptionsrun_triple_difference_robustnessfit_rddcheck_rdd_assumptionsrun_rdd_robustnessfit_psmcheck_psm_assumptionsrun_psm_robustnessfit_doubly_robustcheck_doubly_robust_assumptionsrun_doubly_robust_robustnessfit_synthetic_controlcheck_synthetic_control_assumptionsrun_synthetic_control_robustnessfit_synthetic_didcheck_synthetic_did_assumptionsrun_synthetic_did_robustnessfit_causal_impactcheck_causal_impact_assumptionsrun_causal_impact_robustnessbuild_standard_its_featuresfit_itscheck_its_assumptionsrun_its_robustness
Ограничения текущей версии
Synthetic Controlздесь реализован в облегченной форме, без сложного набора pre-treatment predictorsCausal Impactзависит от сторонней библиотеки и требует аккуратного окруженияMDEпо умолчанию выбирает аналитический расчет при наличии SE, а для методов без аналитической формы использует параметрическую симуляцию; режимresamplingпереобучает поддержанный расчет на каждом повторе- contamination и concurrent changes проверяются вручную, а не автоматически
Планируемое развитие
Staggered Synthetic DiD пока не входит в публичный API. В roadmap предусмотрены два последовательных этапа:
- cohort-specific обёртка поверх
fit_synthetic_didс отдельной оценкой по каждой дате начала воздействия и взвешенной агрегацией результатов; - современный staggered DiD estimator с корректными aggregate SE, p-value и confidence interval, а также явной диагностикой неоднородности эффектов.
До отдельной реализации и валидации эти функции не следует считать доступными или поддержанными в текущей версии пакета.
Версия
Текущая версия пакета: 1.0.5
Metadata
Release files for post-analysis-toolkit 1.0.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| post_analysis_toolkit-1.0.9.tar.gz | 238.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| post_analysis_toolkit-1.0.9-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 430.3 kB
Release files / post_analysis_toolkit-1.0.9.tar.gz
| Download URL | post_analysis_toolkit-1.0.9.tar.gz |
|---|---|
| Size | 238.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6d7686d2aaddbeaaf633f5624f0db25584a7f4befa87b6e1092fcd8e09f3c070
|
|
BLAKE2b-256 checksum How to use checksums |
967d0635a566079c1951c66c5d130511d37fa379c1a01d097fa5e6d474ebc005
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / post_analysis_toolkit-1.0.9-py3-none-any.whl
| Download URL | post_analysis_toolkit-1.0.9-py3-none-any.whl |
|---|---|
| Size | 191.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
309d7b77e5fcd444972e3c05f42ca73d4c087d9ba4c34c8c0e262c9e3d270c0e
|
|
BLAKE2b-256 checksum How to use checksums |
37cd79fd87c8cd7fa452da1d46f6423209c4b226fef6b912413e34f03303227a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency log