diff --git a/.gitignore b/.gitignore index 0cbc3b52..0c1c5948 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ *.csv *.png *.pickle -*.html # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/hypex/reporters/aa.py b/hypex/reporters/aa.py index 9c63fa70..585338e4 100644 --- a/hypex/reporters/aa.py +++ b/hypex/reporters/aa.py @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib from typing import Any, ClassVar from ..comparators import Chi2Test, GroupDifference, GroupSizes, KSTest, TTest @@ -29,10 +30,8 @@ def convert_flat_dataset(data: dict) -> Dataset: @staticmethod def get_splitter_id(data: ExperimentData): for c in [AASplitter, AASplitterWithStratification]: - try: + with contextlib.suppress(NotFoundInExperimentDataError): return data.get_one_id(c, ExperimentDataEnum.additional_fields) - except NotFoundInExperimentDataError: - pass # The splitting was done by another class def extract_group_difference(self, data: ExperimentData) -> dict[str, Any]: group_difference_ids = data.get_ids(GroupDifference)[GroupDifference.__name__][ diff --git a/hypex/reporters/matching.py b/hypex/reporters/matching.py index c7274e9c..337f909b 100644 --- a/hypex/reporters/matching.py +++ b/hypex/reporters/matching.py @@ -79,8 +79,7 @@ def report(self, data: ExperimentData): self.front = False dict_report = super().report(data) self.front = front_buffer - result = self.convert_flat_dataset(dict_report) - return result + return self.convert_flat_dataset(dict_report) class MatchingDatasetReporter(DatasetReporter): diff --git a/schemes/anatomy.md b/schemes/anatomy.md new file mode 100644 index 00000000..f4054a29 --- /dev/null +++ b/schemes/anatomy.md @@ -0,0 +1,3052 @@ +# Архитектура HypEx: Подробное руководство + +## 📑 Оглавление + +### 🎯 Быстрая навигация +- **Для новичков:** [1. Введение и философия](#введение-и-философия) → [10. Shell слой — готовые решения](#shell-слой-готовые-решения) +- **Для разработчиков:** [4. Ядро системы: Executor Framework](#ядро-системы-executor-framework) → [8. Слой экспериментов: Experiment Framework](#слой-экспериментов-experiment-framework) +- **Для архитекторов:** [2. Обзор архитектуры](#обзор-архитектуры) → [Заключение](#заключение) + +### 📋 Подробное содержание + +- [🎯 1. Введение и философия](#введение-и-философия) + - [Общая концепция библиотеки](#общая-концепция-библиотеки) + - [Принцип многоуровневой абстракции](#принцип-многоуровневой-абстракции) + - [Основные архитектурные принципы](#основные-архитектурные-принципы) +- [🏗️ 2. Обзор архитектуры](#обзор-архитектуры) + - [Три основных слоя](#три-основных-слоя) + - [Слой Shell (Пользовательский интерфейс)](#слой-shell-пользовательский-интерфейс) + - [Слой Experiments (Оркестрация)](#слой-experiments-оркестрация) + - [Слой Executors/Reporters (Исполнение и отчетность)](#слой-executorsreporters-исполнение-и-отчетность) + - [Взаимодействие между слоями](#взаимодействие-между-слоями) + - [Поток данных через систему](#поток-данных-через-систему) +- [📊 3. Структуры данных: Dataset и ExperimentData](#структуры-данных-dataset-и-experimentdata) + - [Dataset: Универсальный контейнер данных](#dataset-универсальный-контейнер-данных) + - [Архитектура Dataset](#архитектура-dataset) + - [Система ролей (ABCRole)](#система-ролей-abcrole) + - [Основные операции Dataset](#основные-операции-dataset) + - [ExperimentData: Контекст эксперимента](#experimentdata-контекст-эксперимента) + - [Архитектура ExperimentData](#архитектура-experimentdata) + - [Основные операции ExperimentData](#основные-операции-experimentdata) + - [Принципы работы с данными](#принципы-работы-с-данными) +- [⚙️ 4. Ядро системы: Executor Framework](#ядро-системы-executor-framework) + - [Концепция Executor](#концепция-executor) + - [Базовый класс Executor](#базовый-класс-executor) + - [Три ветви наследования](#три-ветви-наследования) + - [1. Calculator — вычислительная ветвь](#calculator-вычислительная-ветвь) + - [2. IfExecutor — условная ветвь](#ifexecutor-условная-ветвь) + - [3. MLExecutor — машинное обучение ветвь](#mlexecutor-машинное-обучение-ветвь) + - [Система ID](#система-id) + - [Жизненный цикл Executor](#жизненный-цикл-executor) + - [Принципы проектирования Executor'ов](#принципы-проектирования-executorов) + - [Расширение через наследование](#расширение-через-наследование) +- [5. Extension Framework](#extension-framework) + - [Концепция Extension'ов](#концепция-extensionов) + - [Ключевое архитектурное разделение](#ключевое-архитектурное-разделение) + - [Архитектура Extension'ов](#архитектура-extensionов) + - [Примеры Extension'ов](#примеры-extensionов) + - [StatisticalExtension — статистические вычисления](#statisticalextension-статистические-вычисления) + - [MLExtension — машинное обучение](#mlextension-машинное-обучение) + - [Интеграция с Calculator'ами](#интеграция-с-calculatorами) + - [Жизненный цикл Extension'ов](#жизненный-цикл-extensionов) + - [1. Инициализация и выбор backend'а](#инициализация-и-выбор-backendа) + - [2. Изоляция зависимостей](#изоляция-зависимостей) + - [Преимущества Extension Framework](#преимущества-extension-framework) + - [1. Архитектурная чистота](#архитектурная-чистота) + - [2. Производительность и масштабируемость](#производительность-и-масштабируемость) + - [3. Гибкость и расширяемость](#гибкость-и-расширяемость) +- [6. Слой вычислений: Comparators, Transformers, Operators](#слой-вычислений-comparators-transformers-operators) + - [Comparators: Сравнение и тестирование](#comparators-сравнение-и-тестирование) + - [Иерархия Comparators](#иерархия-comparators) + - [Система ролей в Comparators](#система-ролей-в-comparators) + - [Примеры Comparators](#примеры-comparators) + - [Transformers: Преобразование данных](#transformers-преобразование-данных) + - [Архитектура Transformers](#архитектура-transformers) + - [Примеры Transformers](#примеры-transformers) + - [Encoders: Кодирование признаков](#encoders-кодирование-признаков) + - [GroupOperators: Операции над группами](#groupoperators-операции-над-группами) + - [Примеры GroupOperators](#примеры-groupoperators) + - [Принципы проектирования слоя вычислений](#принципы-проектирования-слоя-вычислений) +- [7. Analyzer'ы — комплексный анализ результатов](#analyzerы-комплексный-анализ-результатов) + - [Архитектура Analyzer'ов](#архитектура-analyzerов) + - [OneAAStatAnalyzer — анализ одного A/A теста](#oneaastatanalyzer-анализ-одного-aa-теста) + - [AAScoreAnalyzer — выбор лучшего разбиения](#aascoreanalyzer-выбор-лучшего-разбиения) + - [ABAnalyzer — анализ A/B теста](#abanalyzer-анализ-ab-теста) + - [MatchingAnalyzer — оценка качества matching](#matchinganalyzer-оценка-качества-matching) + - [Паттерны использования Analyzer'ов](#паттерны-использования-analyzerов) + - [1. Последовательный анализ](#последовательный-анализ) + - [2. Иерархический анализ](#иерархический-анализ) + - [3. Условный анализ](#условный-анализ) + - [Создание кастомных Analyzer'ов](#создание-кастомных-analyzerов) + - [Преимущества Analyzer'ов](#преимущества-analyzerов) +- [🔬 8. Слой экспериментов: Experiment Framework](#слой-экспериментов-experiment-framework) + - [Базовый класс Experiment](#базовый-класс-experiment) + - [Специализированные эксперименты](#специализированные-эксперименты) + - [OnRoleExperiment — применение по ролям](#onroleexperiment-применение-по-ролям) + - [GroupExperiment — обработка по группам](#groupexperiment-обработка-по-группам) + - [ParamsExperiment — параметрический поиск](#paramsexperiment-параметрический-поиск) + - [IfParamsExperiment — параметрический поиск с условием остановки](#ifparamsexperiment-параметрический-поиск-с-условием-остановки) + - [Композиция экспериментов](#композиция-экспериментов) + - [Жизненный цикл Experiment](#жизненный-цикл-experiment) + - [Паттерны использования](#паттерны-использования) + - [1. Pipeline паттерн — последовательная обработка](#pipeline-паттерн-последовательная-обработка) + - [2. Branching паттерн — условное ветвление](#branching-паттерн-условное-ветвление) + - [3. Fan-out/Fan-in паттерн — параллельные анализы](#fan-outfan-in-паттерн-параллельные-анализы) + - [4. Grid Search паттерн — поиск оптимальных параметров](#grid-search-паттерн-поиск-оптимальных-параметров) + - [5. Hierarchical паттерн — иерархическая обработка](#hierarchical-паттерн-иерархическая-обработка) + - [Композиция и переиспользуемость](#композиция-и-переиспользуемость) +- [📋 9. Система Reporter'ов](#система-reporterов) + - [Архитектура Reporter'ов](#архитектура-reporterов) + - [Базовый класс Reporter](#базовый-класс-reporter) + - [DictReporter — универсальная основа](#dictreporter-универсальная-основа) + - [Концепция DictReporter](#концепция-dictreporter) + - [Методы извлечения в DictReporter](#методы-извлечения-в-dictreporter) + - [OnDictReporter — универсальный форматтер](#ondictreporter-универсальный-форматтер) + - [Архитектура форматирования](#архитектура-форматирования) + - [Примеры OnDictReporter](#примеры-ondictreporter) + - [Специализированные Reporter'ы](#специализированные-reporterы) + - [ABDictReporter — A/B тестирование](#abdictreporter-ab-тестирование) + - [MatchingDictReporter — Matching анализ](#matchingdictreporter-matching-анализ) + - [Принципы проектирования Reporter'ов](#принципы-проектирования-reporterов) + - [Преимущества архитектуры Reporter'ов](#преимущества-архитектуры-reporterов) +- [🚀 10. Shell слой — готовые решения](#shell-слой-готовые-решения) + - [Концепция Shell слоя](#концепция-shell-слоя) + - [Двухкомпонентная архитектура Shell слоя](#двухкомпонентная-архитектура-shell-слоя) + - [ExperimentShell — конструктор экспериментов](#experimentshell-конструктор-экспериментов) + - [Output — система форматированного представления](#output-система-форматированного-представления) + - [Готовые эксперименты](#готовые-эксперименты) + - [AATest — автоматизированное A/A тестирование](#aatest-автоматизированное-aa-тестирование) + - [ABTest — A/B тестирование с коррекцией](#abtest-ab-тестирование-с-коррекцией) + - [HomogeneityTest — проверка однородности групп](#homogeneitytest-проверка-однородности-групп) + - [Matching — анализ сопоставления](#matching-анализ-сопоставления) + - [Система Output'ов](#система-outputов) + - [AAOutput — результаты A/A тестирования](#aaoutput-результаты-aa-тестирования) + - [ABOutput — результаты A/B тестирования](#aboutput-результаты-ab-тестирования) + - [HomoOutput — результаты проверки однородности](#homooutput-результаты-проверки-однородности) + - [MatchingOutput — результаты matching анализа](#matchingoutput-результаты-matching-анализа) + - [Доступ к детальным результатам](#доступ-к-детальным-результатам) + - [Расширяемость и конфигурирование](#расширяемость-и-конфигурирование) + - [Принципы расширяемости Shell'ов](#принципы-расширяемости-shellов) + - [Расширяемость Output'ов](#расширяемость-outputов) + - [Практические примеры использования](#практические-примеры-использования) + - [Минимальный код для типовых сценариев](#минимальный-код-для-типовых-сценариев) + - [Конфигурация под специфические требования](#конфигурация-под-специфические-требования) + - [Философия типовых решений](#философия-типовых-решений) + - [Принцип "90% в 2 строчки"](#принцип-90-в-2-строчки) + - [Встроенные лучшие практики](#встроенные-лучшие-практики) + - [Границы применимости и расширения](#границы-применимости-и-расширения) +- [11. Практические примеры и сценарии](#практические-примеры-и-сценарии) + - [Философия практических примеров](#философия-практических-примеров) + - [Сценарий 1: Стандартный A/B тест (Уровень 4 — Shell)](#сценарий-1-стандартный-ab-тест-уровень-4-shell) + - [Сценарий 2: A/B тест с дополнительными требованиями (Уровень 5 — Композиция)](#сценарий-2-ab-тест-с-дополнительными-требованиями-уровень-5-композиция) + - [Сценарий 3: Создание кастомного Executor (Уровень 6 — Расширение)](#сценарий-3-создание-кастомного-executor-уровень-6-расширение) + - [Сценарий 4: Комплексный многоэтапный анализ](#сценарий-4-комплексный-многоэтапный-анализ) + - [Эволюция решения: От простого к сложному](#эволюция-решения-от-простого-к-сложному) + - [Этап 1: Первая итерация (Shell)](#этап-1-первая-итерация-shell) + - [Этап 2: Углубленный анализ (Композиция)](#этап-2-углубленный-анализ-композиция) + - [Этап 3: Продвинутая статистика (Расширение)](#этап-3-продвинутая-статистика-расширение) + - [Руководство по выбору подходящего уровня](#руководство-по-выбору-подходящего-уровня) + - [Принципы принятия решений](#принципы-принятия-решений) + - [Критерии качественного решения](#критерии-качественного-решения) +- [🎉 Заключение](#заключение) + - [Ключевые архитектурные достижения](#ключевые-архитектурные-достижения) + - [Принципы, выдержавшие проверку практикой](#принципы-выдержавшие-проверку-практикой) + - [Архитектурные паттерны HypEx](#архитектурные-паттерны-hypex) + - [Практические выводы для разработчиков](#практические-выводы-для-разработчиков) + - [Направления развития архитектуры](#направления-развития-архитектуры) + - [Философия архитектурных решений](#философия-архитектурных-решений) + +--- + + +## 1. Введение и философия + +### Общая концепция библиотеки + +HypEx (Hypothesis Experiments) — это библиотека для проведения статистических экспериментов, построенная на принципах +модульности, расширяемости и многоуровневой абстракции. Основная идея заключается в создании гибкой системы, которая +позволяет как быстро запускать стандартные эксперименты (A/B тесты, A/A тесты, matching), так и конструировать сложные +кастомные пайплайны обработки данных. + +Библиотека решает ключевую проблему: разрыв между потребностями бизнес-пользователей, которым нужны готовые решения, и +потребностями исследователей данных, которым требуется гибкость и возможность кастомизации. + +### Принцип многоуровневой абстракции + +HypEx реализует 8 уровней абстракции, от простого использования готовых решений до модификации ядра библиотеки: + +1. **Уровень платформы** — использование через UI без написания кода +2. **Уровень конструктора** — создание сценариев через визуальный интерфейс +3. **Уровень шаблонов** — запуск предконфигурированных сценариев +4. **Уровень оболочек (Shell)** — использование готовых экспериментов в несколько строк кода +5. **Уровень композиции** — создание экспериментов из готовых блоков +6. **Уровень расширения** — создание новых блоков через наследование +7. **Уровень модификации** — глубокие доработки базовых механик +8. **Уровень ядра** — изменение фундаментального поведения + +Эта философия пронизывает всю архитектуру: каждый слой системы предоставляет свой уровень абстракции, позволяя +пользователям работать на комфортном для них уровне сложности. + +### Основные архитектурные принципы + +**1. Композиция над наследованием** + +- Эксперименты строятся путем композиции Executor'ов +- Каждый Executor выполняет одну конкретную задачу +- Сложное поведение достигается через комбинацию простых компонентов + +**2. Единый поток данных** + +- Все данные проходят через ExperimentData +- Каждый Executor может читать и модифицировать данные +- Результаты сохраняются в структурированном виде + +**3. Разделение ответственности** + +- Executor'ы выполняют вычисления +- Experiment'ы управляют последовательностью выполнения +- Reporter'ы форматируют результаты +- Shell'ы предоставляют удобный интерфейс + +**4. Расширяемость через полиморфизм** + +- Абстрактные базовые классы определяют контракты +- Конкретные реализации следуют единому интерфейсу +- Новая функциональность добавляется через создание новых классов + +**5. Immutability где возможно** + +- ExperimentData копируется при необходимости +- Transformers работают с копиями данных +- Состояние изменяется явно и контролируемо + +## 2. Обзор архитектуры + +### Три основных слоя + +Архитектура HypEx построена на трех основных слоях, каждый из которых имеет четкую зону ответственности: + +#### Слой Shell (Пользовательский интерфейс) + +Самый верхний слой, предоставляющий готовые к использованию решения: + +- **ExperimentShell** — базовый класс для всех оболочек +- **AATest, ABTest, HomogeneityTest, Matching** — предконфигурированные эксперименты +- **Output классы** — форматирование и представление результатов + +Этот слой скрывает всю сложность и позволяет запускать эксперименты в 2-3 строки кода: + +```python +test = ABTest(multitest_method="bonferroni") +results = test.execute(data) +``` + +#### Слой Experiments (Оркестрация) + +Средний слой, отвечающий за управление потоком выполнения: + +- **Experiment** — базовый класс для композиции Executor'ов +- **Специализированные эксперименты** — OnRoleExperiment, GroupExperiment, ParamsExperiment +- **Управление итерациями** — CycledExperiment, IfParamsExperiment + +Этот слой определяет, КАК и В КАКОМ ПОРЯДКЕ выполняются вычисления. + +#### Слой Executors/Reporters (Исполнение и отчетность) + +Нижний слой, где происходят фактические вычисления: + +- **Executors** — выполняют конкретные операции (тесты, преобразования, анализ) +- **Reporters** — извлекают и форматируют результаты из ExperimentData + +Этот слой определяет, ЧТО именно вычисляется и КАК представляются результаты. + +### Взаимодействие между слоями + +``` +Пользователь + ↓ +[Shell Layer] + - Принимает простые параметры + - Создает сложную конфигурацию + - Возвращает отформатированные результаты + ↓ +[Experiments Layer] + - Управляет последовательностью + - Координирует выполнение + - Применяет стратегии (группы, итерации) + ↓ +[Executors/Reporters Layer] + - Выполняют атомарные операции + - Модифицируют ExperimentData + - Форматируют результаты +``` + +### Поток данных через систему + +1. **Инициализация**: Dataset оборачивается в ExperimentData +2. **Выполнение**: Каждый Executor в цепочке: + - Читает необходимые данные из ExperimentData + - Выполняет свою операцию + - Записывает результаты обратно в ExperimentData +3. **Отчетность**: Reporter извлекает результаты и форматирует их +4. **Вывод**: Output классы представляют результаты пользователю + +Ключевая особенность — ExperimentData служит общей шиной данных, через которую компоненты обмениваются информацией, не +зная о существовании друг друга. + +## 3. Структуры данных: Dataset и ExperimentData + +### Dataset: Универсальный контейнер данных + +Dataset — это основная структура для хранения табличных данных в HypEx. Он обеспечивает: + +#### Архитектура Dataset + +**Компоненты:** + +- **Backend** — адаптер для работы с конкретной реализацией (PandasBackend) +- **Roles** — словарь, сопоставляющий колонки с их семантическими ролями +- **Методы** — богатый API для манипуляции данными + +**Ключевые особенности:** + +- Инкапсулирует pandas DataFrame, но может работать с другими backend'ами +- Каждая колонка имеет роль (Role), определяющую её семантику +- Поддерживает цепочки операций (fluent interface) +- Immutable по умолчанию (операции возвращают новый Dataset) + +#### Система ролей (ABCRole) + +Роли определяют семантическое значение колонок: + +``` +ABCRole (abstract) +├── InfoRole — информационные поля +├── TargetRole — целевые переменные +├── FeatureRole — признаки +├── TreatmentRole — индикатор группы эксперимента +├── GroupingRole — поле для группировки +├── StratificationRole — поле для стратификации +├── PreTargetRole — baseline значения +├── StatisticRole — статистические метрики +├── FilterRole — фильтрующие поля +├── TempRole — временные роли +│ ├── TempTargetRole +│ ├── TempTreatmentRole +│ └── TempGroupingRole +└── AdditionalRole — дополнительные поля + ├── AdditionalTargetRole + ├── AdditionalTreatmentRole + ├── AdditionalGroupingRole + └── AdditionalMatchingRole +``` + +Роли позволяют: + +- Автоматически определять, какие колонки использовать для анализа +- Валидировать корректность данных +- Применять правильные преобразования + +#### Основные операции Dataset + +```python +# Создание +ds = Dataset(data=df, roles={'outcome': TargetRole(), 'group': TreatmentRole()}) + +# Фильтрация и выборка +ds_filtered = ds[ds['value'] > 100] +ds_subset = ds[['col1', 'col2']] + +# Группировка и агрегация +grouped = ds.groupby('category') +aggregated = ds.agg(['mean', 'std']) + +# Преобразования +ds_transformed = ds.apply(lambda x: x * 2, role={'result': StatisticRole()}) + +# Слияние +ds_merged = ds1.merge(ds2, on='id') +``` + +### ExperimentData: Контекст эксперимента + +ExperimentData — это расширенный контейнер, который хранит не только исходные данные, но и все промежуточные результаты, +метаданные и состояние эксперимента. + +#### Архитектура ExperimentData + +**Пространства имен:** + +```python +class ExperimentDataEnum: + analysis_tables = "analysis_tables" # Результаты Calculator'ов + variables = "variables" # Переменные и настройки + additional_fields = "additional_fields" # Дополнительные колонки данных +``` + +**Компоненты:** + +- **ds: Dataset** — основные данные эксперимента +- **analysis_tables: dict** — результаты анализов по ID Executor'ов +- **variables: dict** — переменные и метаданные +- **additional_fields: dict** — дополнительные поля данных + +#### Основные операции ExperimentData + +```python +# Создание +experiment_data = ExperimentData(dataset) + +# Сохранение результата Executor'а +experiment_data.set_value( + space=ExperimentDataEnum.analysis_tables, + executor_id="TTest╤reliability 0.05╤", + value=test_results +) + +# Получение результата по ID +result = experiment_data.analysis_tables["TTest╤reliability 0.05╤"] + +# Поиск ID'шников по типу Executor'а +ids = experiment_data.get_ids(TTest) + +# Добавление дополнительных полей +experiment_data.add_fields({'predicted_score': prediction_column}) +``` + +### Принципы работы с данными + +1. **Immutability**: ExperimentData копируется при необходимости изменения основного Dataset +2. **Namespace separation**: Разные типы результатов хранятся в разных пространствах имен +3. **ID-based access**: Результаты индексируются по уникальным ID Executor'ов +4. **Rich metadata**: Поддержка метаданных и переменных для каждого эксперимента + +## 4. Ядро системы: Executor Framework + +### Концепция Executor + +Executor — это фундаментальный строительный блок HypEx. Каждый Executor представляет собой атомарную операцию, которая: + +- Принимает ExperimentData на вход +- Выполняет одну конкретную задачу +- Возвращает модифицированный ExperimentData + +Это позволяет строить сложные пайплайны из простых, переиспользуемых компонентов. + +### Базовый класс Executor + +```python +class Executor(ABC): + def __init__(self, key: Any = ""): + self._id: str = "" # Уникальный идентификатор + self._params_hash = "" # Хеш параметров + self.key: Any = key # Дополнительный ключ + self._generate_id() + + @abstractmethod + def execute(self, data: ExperimentData) -> ExperimentData: + """Основной метод выполнения""" + raise AbstractMethodError + + def set_params(self, params: dict) -> None: + """Динамическая установка параметров""" + # Позволяет изменять параметры после создания + + def _generate_id(self): + """Генерация уникального ID""" + # ClassName╤ParamsHash╤Key +``` + +**Ключевые особенности:** + +- **Единый интерфейс** — все Executor'ы реализуют метод `execute` +- **Самоидентификация** — каждый Executor знает свой уникальный ID +- **Параметризация** — поддержка динамического изменения параметров +- **Композируемость** — Executor'ы можно комбинировать в цепочки + +### Три ветви наследования + +#### 1. Calculator — вычислительная ветвь + +Calculator добавляет возможность выполнять вычисления вне контекста ExperimentData: + +```python +class Calculator(Executor, ABC): + @classmethod + def calc(cls, data: Dataset, **kwargs): + """Статический метод для вычислений""" + return cls._inner_function(data, **kwargs) + + @staticmethod + @abstractmethod + def _inner_function(data: Dataset, **kwargs) -> Any: + """Реализация вычисления""" + pass +``` + +**Назначение:** Разделение логики вычисления от логики работы с ExperimentData. + +#### 2. IfExecutor — условная ветвь + +```python +class IfExecutor(Executor): + def __init__(self, if_executor: Executor, else_executor: Executor = None): + self.if_executor = if_executor + self.else_executor = else_executor + + def check_rule(self, data: ExperimentData) -> bool: + """Логика принятия решения""" + pass + + def execute(self, data: ExperimentData) -> ExperimentData: + if self.check_rule(data): + return self.if_executor.execute(data) + elif self.else_executor: + return self.else_executor.execute(data) + return data +``` + +**Назначение:** Условное выполнение на основе состояния данных. + +#### 3. MLExecutor — машинное обучение ветвь + +```python +class MLExecutor(Executor, ABC): + def fit(self, X: Dataset, Y: Dataset) -> 'MLExecutor': + """Обучение модели""" + pass + + def predict(self, X: Dataset) -> Dataset: + """Предсказания""" + pass + + def score(self, X: Dataset, Y: Dataset) -> float: + """Метрика качества""" + pass +``` + +**Назначение:** Стандартный интерфейс для ML операций. + +### Система ID + +Каждый Executor имеет уникальный ID, состоящий из: + +``` +ClassName╤ParamsHash╤Key +``` + +**Примеры:** + +```python +TTest() → "TTest╤╤" +TTest(reliability=0.01) → "TTest╤rel 0.01╤" +TTest(key="revenue") → "TTest╤╤revenue" +``` + +### Жизненный цикл Executor + +1. **Создание:** + ```python + executor = TTest(reliability=0.05) + # Генерируется ID: "TTest╤╤" + ``` + +2. **Конфигурация (опционально):** + ```python + executor.set_params({"reliability": 0.01}) + # ID обновляется: "TTest╤rel 0.01╤" + ``` + +3. **Выполнение:** + ```python + result = executor.execute(experiment_data) + # Результат сохраняется в ExperimentData под ID executor'а + ``` + +4. **Поиск результатов:** + ```python + # Найти все результаты TTest + ids = experiment_data.get_ids(TTest) + # Получить конкретный результат + result = experiment_data.analysis_tables[ids[0]] + ``` + +### Принципы проектирования Executor'ов + +1. **Единая ответственность** — один Executor = одна задача +2. **Независимость** — не должны знать о других Executor'ах +3. **Идемпотентность** — повторное выполнение дает тот же результат +4. **Самодостаточность** — содержат всю логику для своей задачи +5. **Testability** — легко тестировать изолированно + +### Расширение через наследование + +Создание нового Executor: + +```python +class MyCustomTest(StatHypothesisTesting): + @classmethod + def _inner_function(cls, data: Dataset, test_data: Dataset) -> Dataset: + # Реализация кастомного теста + statistic = calculate_my_statistic(data, test_data) + p_value = calculate_p_value(statistic) + + return Dataset.from_dict({ + "statistic": statistic, + "p-value": p_value, + "pass": p_value < cls.reliability + }) + + def execute(self, data: ExperimentData) -> ExperimentData: + # Использует базовую логику StatHypothesisTesting + return super().execute(data) +``` + +Таким образом, Executor Framework обеспечивает: + +- **Модульность** — каждая операция инкапсулирована +- **Переиспользуемость** — Executor'ы можно комбинировать по-разному +- **Расширяемость** — легко добавлять новую функциональность +- **Тестируемость** — каждый компонент можно тестировать отдельно + +## 5. Extension Framework + +### Концепция Extension'ов + +Extension'ы в HypEx представляют собой систему для инкапсуляции backend-специфичных вычислений, которая обеспечивает работу Calculator'ов с различными типами данных (pandas, Spark, и другими) через единый интерфейс. + +#### Ключевое архитектурное разделение + +**Backend-агностичность vs Backend-специфичность** — это основной принцип разделения ответственности в HypEx: + +**Calculator'ы (Backend-агностичные):** +- Работают исключительно через Dataset API +- Не зависят от конкретной реализации backend'а +- Делегируют backend-специфичные вычисления Extension'ам +- Фокусируются на бизнес-логике анализа + +**Extension'ы (Backend-специфичные):** +- Инкапсулируют детали работы с конкретными технологиями +- Предоставляют оптимизированные реализации для разных backend'ов +- Изолируют внешние зависимости (numpy, scipy, sklearn и т.д.) + +### Архитектура Extension'ов + +```python +class Extension(ABC): + def calc(self, data: Dataset, **kwargs) -> Dataset: + """Единая точка входа""" + backend_type = data.backend.__class__.__name__ + + if backend_type == "PandasBackend": + return self._calc_pandas(data, **kwargs) + elif backend_type == "SparkBackend": + return self._calc_spark(data, **kwargs) + else: + raise NotImplementedError(f"Backend {backend_type} not supported") + + @abstractmethod + def _calc_pandas(self, data: Dataset, **kwargs) -> Dataset: + """Реализация для pandas""" + pass + + def _calc_spark(self, data: Dataset, **kwargs) -> Dataset: + """Реализация для Spark (опционально)""" + raise NotImplementedError("Spark backend not implemented") +``` + +### Примеры Extension'ов + +#### StatisticalExtension — статистические вычисления + +```python +class TTestExtension(Extension): + def _calc_pandas(self, data: Dataset, grouping_col: str, target_col: str) -> Dataset: + """Pandas реализация t-теста""" + from scipy import stats + + groups = data.df.groupby(grouping_col)[target_col] + group1, group2 = [group.values for name, group in groups] + + statistic, p_value = stats.ttest_ind(group1, group2) + + return Dataset.from_dict({ + "statistic": statistic, + "p-value": p_value + }) + + def _calc_spark(self, data: Dataset, grouping_col: str, target_col: str) -> Dataset: + """Spark реализация через SQL""" + # Реализация через Spark SQL для больших данных + spark_df = data.backend.df + # ... Spark-специфичная логика +``` + +#### MLExtension — машинное обучение + +```python +class CholeskyExtension(Extension): + def _calc_pandas(self, data: Dataset, features: list) -> Dataset: + """Разложение Холецкого для pandas""" + import numpy as np + + matrix = data.df[features].values + cholesky = np.linalg.cholesky(matrix) + + return Dataset( + data=pd.DataFrame(cholesky, columns=features), + roles={col: FeatureRole() for col in features} + ) + + def _calc_spark(self, data: Dataset, features: list) -> Dataset: + """Spark ML реализация""" + from pyspark.ml.linalg import Vectors, Matrices + # ... Spark ML логика +``` + +### Интеграция с Calculator'ами + +Calculator'ы используют Extension'ы как делегаты: + +```python +class TTest(Comparator): + extension = TTestExtension() # Класс-атрибут + + @classmethod + def _inner_function(cls, data: Dataset, test_data: Dataset) -> Dataset: + # Делегируем вычисления Extension'у + return cls.extension.calc( + data=data, + grouping_col=cls._get_grouping_column(data), + target_col=cls._get_target_column(test_data) + ) +``` + +### Жизненный цикл Extension'ов + +#### 1. Инициализация и выбор backend'а + +```python +# При вызове Extension'а происходит автоматический выбор реализации +extension = CholeskyExtension() + +# Extension анализирует тип backend'а Dataset'а +pandas_dataset = Dataset(data=pd.DataFrame(...), roles=...) +result = extension.calc(pandas_dataset) # Вызовет _calc_pandas() + +# Для другого backend'а автоматически выберется другая реализация +# spark_dataset = Dataset(data=spark_df, roles=...) +# result = extension.calc(spark_dataset) # Вызовет _calc_spark() +``` + +#### 2. Изоляция зависимостей + +```python +class ScipyExtension(Extension): + def _calc_pandas(self, data: Dataset, **kwargs) -> Dataset: + try: + from scipy import stats # Импорт только при использовании + # ... логика с scipy + except ImportError: + raise ImportError("scipy required for this operation") +``` + +### Преимущества Extension Framework + +#### 1. Архитектурная чистота + +- **Четкое разделение ответственности:** Calculator'ы для логики, Extension'ы для реализации +- **Backend-агностичность:** Бизнес-логика не зависит от технических деталей +- **Изоляция зависимостей:** Внешние библиотеки не "протекают" в основной код + +#### 2. Производительность и масштабируемость + +- **Автоматические оптимизации:** Система автоматически выбирает лучшую реализацию +- **Поддержка разных backend'ов:** pandas, Spark, Dask без изменения бизнес-логики +- **Ленивые вычисления:** Некоторые Extension'ы могут использовать ленивые вычисления + +#### 3. Гибкость и расширяемость + +- **Простое добавление backend'ов:** Новые backend'ы добавляются через Extension'ы +- **Модульность:** Extension'ы можно переиспользовать и комбинировать +- **Эволюция технологий:** Поддержка новых библиотек через Extension'ы + +Extension Framework является ключевым архитектурным решением HypEx, которое обеспечивает баланс между простотой использования и техническими возможностями. Он позволяет Calculator'ам оставаться backend-агностичными, при этом используя мощь специализированных библиотек для каждого типа данных. + +## 6. Слой вычислений: Comparators, Transformers, Operators + +Слой вычислений содержит конкретные реализации Calculator'ов, каждая из которых специализируется на определенном типе +операций. Все они следуют паттерну разделения вычислительной логики от работы с ExperimentData. + +### Comparators: Сравнение и тестирование + +Comparators отвечают за сравнение групп и проведение статистических тестов. Они имеют сложную иерархию и богатую +функциональность. + +#### Иерархия Comparators + +```python +Comparator (abstract) +├── StatHypothesisTesting — статистические тесты +│ ├── TTest — t-тест для сравнения средних +│ ├── KSTest — тест Колмогорова-Смирнова для распределений +│ ├── Chi2Test — хи-квадрат для категориальных переменных +│ └── UTest — тест Манна-Уитни (непараметрический) +├── Difference — вычисление разностей +│ ├── GroupDifference — разности между группами +│ └── RelativeDifference — относительные изменения +└── Correlation — корреляционный анализ + ├── PearsonCorrelation — корреляция Пирсона + └── SpearmanCorrelation — ранговая корреляция +``` + +#### Система ролей в Comparators + +Comparators автоматически определяют, какие колонки сравнивать, на основе ролей: + +- **TreatmentRole / GroupingRole**: Колонки для разбиения на группы +- **TargetRole**: Метрики для сравнения +- **FeatureRole**: Дополнительные признаки для анализа + +#### Примеры Comparators + +**TTest** — сравнение средних значений: + +```python +class TTest(StatHypothesisTesting): + def __init__(self, reliability: float = 0.05): + self.reliability = reliability + + @classmethod + def _inner_function(cls, data: Dataset, test_data: Dataset) -> Dataset: + # Использует TTestExtension для backend-специфичных вычислений + return cls.extension.calc(data, test_data, reliability=cls.reliability) +``` + +**KSTest** — сравнение распределений: + +```python +class KSTest(StatHypothesisTesting): + @classmethod + def _inner_function(cls, data: Dataset, test_data: Dataset) -> Dataset: + # Тест на различие распределений между группами + return cls.extension.calc(data, test_data, test_type="two_sample") +``` + +### Transformers: Преобразование данных + +Transformers изменяют данные, возвращая модифицированную копию Dataset. + +#### Архитектура Transformers + +```python +class Transformer(Calculator): + def execute(self, data: ExperimentData) -> ExperimentData: + # Копируем данные для безопасного изменения + new_data = ExperimentData(self.calc(data.ds)) + # Переносим метаданные + new_data.copy_metadata_from(data) + return new_data + + def calc(self, data: Dataset) -> Dataset: + target_cols = self._get_target_columns(data) + return self._inner_function(data, target_cols, **self.params) +``` + +#### Примеры Transformers + +**NaFiller** — заполнение пропусков: + +```python +class NaFiller(Transformer): + def __init__(self, method: str = "mean"): + self.method = method + + def _inner_function(data: Dataset, target_cols, method) -> Dataset: + if method in ['ffill', 'bfill']: + return data.fillna(method=method) + elif method == 'mean': + for col in target_cols: + data[col].fillna(data[col].mean(), inplace=True) + return data +``` + +**CategoryAggregator** — агрегация редких категорий: + +```python +class CategoryAggregator(Transformer): + def __init__(self, min_frequency: float = 0.01): + self.min_frequency = min_frequency + + def _inner_function(data: Dataset, target_cols, min_freq) -> Dataset: + for col in target_cols: + value_counts = data[col].value_counts(normalize=True) + rare_categories = value_counts[value_counts < min_freq].index + data[col] = data[col].replace(rare_categories, 'Other') + return data +``` + +### Encoders: Кодирование признаков + +Encoders преобразуют категориальные признаки в числовые. Результат сохраняется в additional_fields. + +```python +class Encoder(Calculator): + def execute(self, data: ExperimentData) -> ExperimentData: + target_cols = data.ds.search_columns(roles=FeatureRole(), + search_types=[str]) + encoded = self.calc(data=data.ds, target_cols=target_cols) + # Сохраняем в additional_fields + return data.set_value( + space=ExperimentDataEnum.additional_fields, + executor_id=self._ids_to_names(encoded.columns), + value=encoded + ) +``` + +**DummyEncoder** — one-hot encoding: + +```python +class DummyEncoder(Encoder): + def _inner_function(data: Dataset, target_cols) -> Dataset: + # Использует pandas.get_dummies + dummies_df = pd.get_dummies(data[target_cols], drop_first=True) + # Устанавливает роли для новых колонок + roles = {col: data.roles[col.split('_')[0]] for col in dummies_df.columns} + return Dataset(data=dummies_df, roles=roles) +``` + +### GroupOperators: Операции над группами + +GroupOperators выполняют специализированные операции над группами данных, часто используемые в matching и causal +inference. + +#### Примеры GroupOperators + +**SMD (Standardized Mean Difference)** — стандартизированная разность средних: + +```python +class SMD(GroupOperator): + def _inner_function(data: Dataset, control_data: Dataset, treatment_data: Dataset) -> Dataset: + # Вычисляет Cohen's d для каждого признака + results = [] + for col in data.columns: + mean_diff = treatment_data[col].mean() - control_data[col].mean() + pooled_std = np.sqrt((treatment_data[col].var() + control_data[col].var()) / 2) + smd = mean_diff / pooled_std + results.append({"feature": col, "smd": smd}) + + return Dataset.from_records(results) +``` + +**Bias** — оценка смещения после matching: + +```python +class Bias(GroupOperator): + def _inner_function(data: Dataset, matched_indices) -> Dataset: + # Оценивает качество балансировки после matching + # Возвращает метрики bias по каждому признаку +``` + +### Принципы проектирования слоя вычислений + +1. **Специализация по типам операций** — каждый тип Calculator'а решает свой класс задач +2. **Унификация интерфейсов** — общие паттерны для работы с ролями и данными +3. **Расширяемость** — легко добавлять новые операции через наследование +4. **Backend-агностичность** — вычисления делегируются Extension'ам +5. **Composability** — Calculator'ы можно комбинировать в complex pipeline'ы + +## 7. Analyzer'ы — комплексный анализ результатов + +Analyzer'ы представляют собой специальный класс Executor'ов, которые выполняют высокоуровневый анализ результатов +экспериментов. В отличие от простых вычислительных блоков (Calculator'ов), Analyzer'ы работают с результатами множества +других Executor'ов, агрегируют их и принимают комплексные решения. + +### Архитектура Analyzer'ов + +Analyzer'ы наследуются напрямую от Executor, минуя Calculator, так как их задача — не вычисления над сырыми данными, а +анализ уже полученных результатов. Типичный Analyzer: + +1. **Извлекает результаты** предыдущих Executor'ов из ExperimentData +2. **Анализирует и агрегирует** эти результаты согласно своей логике +3. **Принимает решения** на основе комплексных критериев +4. **Сохраняет итоговый анализ** в analysis_tables + +### OneAAStatAnalyzer — анализ одного A/A теста + +**Назначение:** Анализирует результаты статистических тестов для одного разбиения A/A теста. + +**Функциональность:** + +- Извлекает результаты всех примененных тестов (TTest, KSTest, Chi2Test) +- Для каждой метрики подсчитывает количество прошедших тестов +- Оценивает общее качество разбиения по pass rate +- Классифицирует качество: excellent (>95%), good (>80%), acceptable (>60%), poor (<60%) + +**Входные данные:** Результаты статистических тестов в analysis_tables + +**Выходные данные:** + +- Статистика по каждой метрике (passed/total tests, pass_rate, status) +- Общая оценка качества разбиения (overall pass_rate, quality level) + +### AAScoreAnalyzer — выбор лучшего разбиения + +**Назначение:** Анализирует результаты множественных A/A тестов и выбирает оптимальное разбиение. + +**Функциональность:** + +- Извлекает результаты всех итераций OneAAStatAnalyzer +- Вычисляет score для каждого разбиения согласно критерию: + - `max_pass_rate` — максимизация доли прошедших тестов + - `min_bias` — минимизация смещения между группами + - `balanced` — комбинация pass_rate и баланса групп +- Ранжирует разбиения по качеству +- Восстанавливает параметры лучшего Splitter'а +- Применяет лучшее разбиение к данным + +**Входные данные:** Результаты множественных OneAAStatAnalyzer + +**Выходные данные:** + +- ID и параметры лучшего разбиения +- Статистика по всем разбиениям (scores, mean, std) +- Колонка с лучшим разбиением в additional_fields + +### ABAnalyzer — анализ A/B теста + +**Назначение:** Выполняет финальный анализ A/B теста с учетом множественного тестирования. + +**Функциональность:** + +- Проверяет достаточность размера выборки +- Извлекает все p-values из проведенных тестов +- Применяет коррекцию множественного тестирования: + - Bonferroni — консервативная коррекция + - Holm — последовательная коррекция + - FDR — контроль False Discovery Rate +- Оценивает размер эффекта и его confidence interval +- Разделяет статистическую и практическую значимость +- Принимает решение по каждой метрике: + - `ship` — значимый и практичный эффект + - `monitor` — значимый, но малый эффект + - `investigate` — большой, но незначимый эффект + - `no_effect` — нет эффекта +- Формирует общие рекомендации по эксперименту + +**Входные данные:** + +- Результаты статистических тестов +- Размеры групп из GroupSizes +- Эффекты из GroupDifference + +**Выходные данные:** + +- Детальный анализ по каждой метрике +- Скорректированные p-values +- Общее решение и рекомендации +- Оценка рисков и качества выборки + +### MatchingAnalyzer — оценка качества matching + +**Назначение:** Комплексная оценка качества matching'а между группами. + +**Функциональность:** + +- Извлекает matched индексы из FaissNearestNeighbors +- Создает matched датасеты для control и treatment +- Оценивает баланс ковариат через SMD (Standardized Mean Difference) +- Вычисляет метрики качества: + - SMD — стандартизированная разница средних + - KS статистика — различие распределений + - Variance ratio — соотношение дисперсий +- Оценивает treatment effect после matching +- Выполняет диагностику проблем: + - Проверка размера matched выборки + - Идентификация несбалансированных ковариат + - Оценка common support region +- Генерирует рекомендации по улучшению + +**Входные данные:** + +- Matched индексы из additional_fields +- Исходные данные control и treatment групп + +**Выходные данные:** + +- Количество и доля matched пар +- Баланс по каждой ковариате +- Общие метрики качества matching +- Treatment effect с доверительными интервалами +- Диагностика и рекомендации + +### Паттерны использования Analyzer'ов + +#### 1. Последовательный анализ + +Analyzer'ы размещаются в конце pipeline'а для анализа накопленных результатов: + +```python +experiment = Experiment([ + DataPreparation(), + StatisticalTests(), + ABAnalyzer(multitest_method="fdr") +]) +``` + +#### 2. Иерархический анализ + +Один Analyzer использует результаты другого: + +```python +experiment = Experiment([ + ParamsExperiment(...), + OneAAStatAnalyzer(), # Анализ каждого теста + AAScoreAnalyzer() # Выбор лучшего +]) +``` + +#### 3. Условный анализ + +Выбор Analyzer'а на основе характеристик данных: + +```python +IfExecutor( + condition=lambda d: d.n_metrics > 10, + if_executor=ABAnalyzer(multitest_method="fdr"), + else_executor=ABAnalyzer(multitest_method=None) +) +``` + +### Создание кастомных Analyzer'ов + +Для создания своего Analyzer'а необходимо: + +1. **Наследоваться от Executor** (не от Calculator) +2. **Реализовать метод execute** с логикой: + - Извлечение нужных результатов через `data.get_ids()` + - Анализ и агрегация результатов + - Сохранение через `data.set_value()` +3. **Следовать конвенциям:** + - Результаты сохранять в analysis_tables + - Использовать Dataset для структурированных результатов + - Предоставлять summary и recommendations + +### Преимущества Analyzer'ов + +1. **Высокоуровневая абстракция** — скрывают сложность анализа за простым интерфейсом +2. **Переиспользуемость** — стандартные паттерны анализа для типовых задач +3. **Композируемость** — можно комбинировать разные виды анализа +4. **Расширяемость** — легко добавлять новые типы анализа +5. **Воспроизводимость** — стандартизированные методы обеспечивают консистентность +6. **Decision-ready** — превращают сырые результаты в actionable insights + +Analyzer'ы являются ключевым компонентом для превращения множества технических результатов вычислений в понятные +бизнес-решения и рекомендации. + +## 8. Слой экспериментов: Experiment Framework + +Слой экспериментов управляет композицией и оркестрацией Executor'ов. Он определяет, КАК и В КАКОМ ПОРЯДКЕ выполняются +операции, предоставляя различные стратегии выполнения. + +### Базовый класс Experiment + +```python +class Experiment(Executor): + def __init__(self, executors: Sequence[Executor]): + self.executors = executors + super().__init__() + + def execute(self, data: ExperimentData) -> ExperimentData: + result_data = data + for executor in self.executors: + result_data = executor.execute(result_data) + return result_data + + @property + def transformer(self) -> bool: + """Определяет, есть ли среди Executor'ов Transformer'ы""" + return any(isinstance(ex, Transformer) for ex in self.executors) +``` + +**Ключевые особенности:** + +- **Композиция** — Experiment сам является Executor'ом и может включать другие Experiment'ы +- **Автоопределение копирования** — автоматически определяет, нужно ли копировать данные +- **ID генерация** — создает составной ID из ID своих Executor'ов + +### Специализированные эксперименты + +#### OnRoleExperiment — применение по ролям + +Применяет набор Executor'ов ко всем колонкам с определенной ролью: + +```python +class OnRoleExperiment(Experiment): + def __init__(self, executors: Sequence[Executor], role: ABCRole): + self.role = role + super().__init__(executors) + + def execute(self, data: ExperimentData) -> ExperimentData: + target_columns = data.ds.search_columns(roles=self.role) + + result_data = data + for column in target_columns: + for executor in self.executors: + # Применяем executor к каждой колонке с данной ролью + executor.set_params({"target_column": column}) + result_data = executor.execute(result_data) + + return result_data +``` + +**Применение:** + +- Статистические тесты ко всем метрикам +- Применение фильтров ко всем признакам +- Агрегация по всем группирующим колонкам + +#### GroupExperiment — обработка по группам + +Разбивает данные по группам и применяет Executor'ы к каждой группе: + +```python +class GroupExperiment(ExperimentWithReporter): + def __init__(self, + executors: Sequence[Executor], + reporter: DatasetReporter, + searching_role: ABCRole): + self.searching_role = searching_role + super().__init__(executors, reporter) + + def execute(self, data: ExperimentData) -> ExperimentData: + grouping_columns = data.ds.search_columns(roles=self.searching_role) + + all_results = [] + for group_col in grouping_columns: + for group_value in data.ds[group_col].unique(): + # Фильтруем данные для текущей группы + group_data = data.ds[data.ds[group_col] == group_value] + t_data = ExperimentData(group_data) + + # Применяем все Executor'ы к группе + for executor in self.executors: + t_data = executor.execute(t_data) + + # Сохраняем результат группы + group_result = self.reporter.report(t_data) + group_result[group_col] = group_value + all_results.append(group_result) + + return self._set_result(data, all_results, reset_index=False) +``` + +**Применение:** + +- Анализ по сегментам +- Гетерогенные эффекты +- Подгрупповой анализ + +#### ParamsExperiment — параметрический поиск + +Выполняет эксперимент с различными комбинациями параметров: + +```python +class ParamsExperiment(ExperimentWithReporter): + def __init__(self, + executors: Sequence[Executor], + reporter: DatasetReporter, + params: dict[type, dict[str, Sequence[Any]]]): + super().__init__(executors, reporter) + self._params = params + self._flat_params = [] # Все комбинации параметров + + def _update_flat_params(self): + """Генерирует все комбинации параметров""" + # Пример params: + # { + # AASplitter: { + # "random_state": [0, 1, 2], + # "control_size": [0.4, 0.5, 0.6] + # }, + # TTest: { + # "reliability": [0.01, 0.05, 0.1] + # } + # } + # Создаст 3 * 3 * 3 = 27 комбинаций + + param_combinations = itertools.product(*[ + itertools.product(*[ + itertools.product([param], values) + for param, values in class_params.items() + ]) + for class_params in self._params.values() + ]) + + self._flat_params = list(param_combinations) + + def execute(self, data: ExperimentData) -> ExperimentData: + self._update_flat_params() + + results = [] + for i, flat_param in enumerate(tqdm(self._flat_params)): + t_data = ExperimentData(data.ds) + + # Применяем параметры к соответствующим Executor'ам + for executor in self.executors: + params_for_executor = self._extract_params_for_executor( + executor, flat_param + ) + if params_for_executor: + executor.set_params(params_for_executor) + + t_data = executor.execute(t_data) + + # Сохраняем результат итерации + iteration_result = self.reporter.report(t_data) + iteration_result["params"] = flat_param + iteration_result["iteration"] = i + results.append(iteration_result) + + return self._set_result(data, results) +``` + +#### IfParamsExperiment — параметрический поиск с условием остановки + +Добавляет возможность ранней остановки при достижении условия: + +```python +class IfParamsExperiment(ParamsExperiment): + def __init__(self, + executors: Sequence[Executor], + reporter: DatasetReporter, + params: dict[type, dict[str, Sequence[Any]]], + stopping_criterion: IfExecutor): + super().__init__(executors, reporter, params) + self.stopping_criterion = stopping_criterion + + def execute(self, data: ExperimentData) -> ExperimentData: + self._update_flat_params() + + for i, flat_param in enumerate(tqdm(self._flat_params)): + t_data = ExperimentData(data.ds) + + # Выполняем эксперимент + for executor in self.executors: + params_for_executor = self._extract_params_for_executor( + executor, flat_param + ) + if params_for_executor: + executor.set_params(params_for_executor) + t_data = executor.execute(t_data) + + # Проверяем условие остановки + if_result = self.stopping_criterion.execute(t_data) + if_executor_id = if_result.get_one_id( + self.stopping_criterion.__class__, + ExperimentDataEnum.variables + ) + + if if_result.variables[if_executor_id]["response"]: + # Условие выполнено - останавливаемся + final_result = self.reporter.report(t_data) + final_result["params"] = flat_param + final_result["iteration"] = i + return self._set_result(data, [final_result]) + + # Условие не выполнено ни для одной комбинации + return data +``` + +**Применение:** + +- Поиск первого "хорошего" разбиения для A/A теста +- Early stopping в оптимизации +- Адаптивные эксперименты + +### Композиция экспериментов + +Эксперименты можно вкладывать друг в друга, создавая сложные pipeline'ы: + +```python +# Сложный эксперимент для A/A тестирования +aa_experiment = Experiment([ + # 1. Параметрический поиск лучшего разбиения + ParamsExperiment( + executors=[ + AASplitter(), + Experiment([ # Вложенный эксперимент + GroupSizes(), + OnRoleExperiment( # Применить тесты ко всем targets + executors=[TTest(), KSTest()], + role=TargetRole() + ) + ]) + ], + params={ + AASplitter: {"random_state": range(100)} + }, + reporter=OneAADictReporter() + ), + + # 2. Анализ результатов + AAScoreAnalyzer(alpha=0.05), + + # 3. Условное выполнение на основе результатов + IfAAExecutor( + if_executor=Experiment([...]), # Если тест прошел + else_executor=Experiment([...]) # Если тест не прошел + ) +]) +``` + +### Жизненный цикл Experiment + +1. **Создание и конфигурация:** + +```python +experiment = Experiment( + executors=[executor1, executor2, executor3] +) +``` + +2. **Детекция типа:** + +```python +# Автоматически определяет, нужно ли копировать данные +if experiment.transformer: # True если есть Transformers +# Будет работать с копией данных +``` + +3. **Выполнение:** + +```python +result = experiment.execute(experiment_data) +# Каждый executor выполняется последовательно +# Результаты накапливаются в ExperimentData +``` + +4. **Доступ к результатам:** + +```python +# Experiment может сам сохранить агрегированный результат +experiment_id = experiment.id +aggregated_result = result.analysis_tables[experiment_id] + +# Или можно получить результаты отдельных Executor'ов +executor_ids = experiment.get_executor_ids([TTest, KSTest]) +``` + +### Паттерны использования + +#### 1. Pipeline паттерн — последовательная обработка + +Самый базовый паттерн, где операции выполняются строго последовательно, и каждая использует результаты предыдущей. + +```python +# Классический pipeline для A/B теста +ab_pipeline = Experiment([ + # Этап 1: Подготовка данных + NaFiller(method="ffill"), # Заполнение пропусков + OutliersFilter(lower_percentile=0.05), # Удаление выбросов + DummyEncoder(), # Кодирование категорий + + # Этап 2: Основные метрики + GroupSizes(grouping_role=TreatmentRole()), + GroupDifference(grouping_role=TreatmentRole()), + + # Этап 3: Статистические тесты + TTest(grouping_role=TreatmentRole()), + KSTest(grouping_role=TreatmentRole()), + + # Этап 4: Анализ результатов + ABAnalyzer(multitest_method="bonferroni") +]) + +# Можно создавать переиспользуемые подпайплайны +data_preparation = Experiment([ + NaFiller(method="ffill"), + OutliersFilter(lower_percentile=0.05, upper_percentile=0.95), + ConstFilter(threshold=0.95), + CorrFilter(threshold=0.8), + DummyEncoder() +]) + +statistical_tests = Experiment([ + TTest(grouping_role=TreatmentRole()), + KSTest(grouping_role=TreatmentRole()), + Chi2Test(grouping_role=TreatmentRole()) +]) + +# Композиция подпайплайнов +full_pipeline = Experiment([ + data_preparation, # Experiment как Executor + statistical_tests, # Experiment как Executor + ABAnalyzer() +]) +``` + +**Преимущества:** + +- Простота понимания и отладки +- Легко модифицировать отдельные этапы +- Возможность переиспользования подпайплайнов + +#### 2. Branching паттерн — условное ветвление + +Выбор пути выполнения на основе характеристик данных или промежуточных результатов. + +```python +# Адаптивный выбор теста +adaptive_testing = Experiment([ + # Подготовка + DataPreparation(), + + # Проверка нормальности + NormalityTest(), + + # Выбор теста на основе результата + IfExecutor( + condition=lambda d: d.normality_test_passed, + if_executor=TTest(), # Параметрический тест + else_executor=UTest() # Непараметрический тест + ), + + # Дальнейший анализ + ResultAnalyzer() +]) + +# Каскадное ветвление +cascading_analysis = Experiment([ + InitialTest(), + + IfExecutor( + condition=lambda d: d.p_value < 0.05, + if_executor=Experiment([ + # Значимый эффект - глубокий анализ + EffectSizeCalculator(), + SegmentAnalysis(), + HeterogeneityTest() + ]), + else_executor=Experiment([ + # Незначимый эффект - проверка power + PowerAnalysis(), + SampleSizeRecommendation() + ]) + ) +]) +``` + +**Преимущества:** + +- Адаптивность к данным +- Оптимизация вычислений +- Автоматический выбор методов + +#### 3. Fan-out/Fan-in паттерн — параллельные анализы + +Применение разных анализов к одним данным с последующей агрегацией. + +```python +# Fan-out: множественные анализы +multi_analysis = Experiment([ + # Подготовка + DataPreparation(), + + # Fan-out: разные виды анализа + OnRoleExperiment( + executors=[ + TTest(), + KSTest(), + Chi2Test() + ], + role=TargetRole() + ), + + # Fan-out: анализ по сегментам + GroupExperiment( + executors=[ + SegmentStatistics(), + SegmentTests() + ], + searching_role=SegmentRole(), + reporter=SegmentReporter() + ), + + # Fan-in: сводная таблица по всем сегментам + SegmentAggregator() +]) +``` + +**Преимущества:** + +- Параллельная обработка независимых анализов +- Ансамблирование результатов для надежности +- Comprehensive анализ с разных углов + +#### 4. Grid Search паттерн — поиск оптимальных параметров + +Систематический перебор комбинаций параметров для поиска оптимальных. + +```python +# Поиск оптимального разбиения для A/A теста +aa_grid_search = ParamsExperiment( + executors=[ + AASplitter(), # Будет параметризован + Experiment([ + GroupSizes(grouping_role=AdditionalTreatmentRole()), + OnRoleExperiment( # Применить тесты ко всем targets + executors=[ + TTest(grouping_role=AdditionalTreatmentRole()), + KSTest(grouping_role=AdditionalTreatmentRole()) + ], + role=TargetRole() + ) + ]), + OneAAStatAnalyzer() + ], + params={ + AASplitter: { + "random_state": range(1000), # 1000 разных seed'ов + "control_size": [0.3, 0.5, 0.7], # 3 варианта размера + "sample_size": [0.8, 1.0] # С сэмплированием и без + } + }, + reporter=AADictReporter() +) +# Итого: 1000 * 3 * 2 = 6000 комбинаций + +# Grid search с ранней остановкой +optimized_search = IfParamsExperiment( + executors=[...], + params={ + Splitter: {"random_state": range(10000)}, + Filter: {"threshold": np.linspace(0.01, 0.1, 10)} + }, + stopping_criterion=IfAAExecutor( + # Останавливаемся, когда нашли хорошее разбиение + condition=lambda d: d.aa_score > 0.95 + ), + reporter=OptimalParamsReporter() +) +``` + +**Преимущества:** + +- Систематический поиск оптимума +- Возможность ранней остановки +- Вложенная оптимизация для сложных pipeline'ов + +#### 5. Hierarchical паттерн — иерархическая обработка + +Многоуровневая обработка с агрегацией на разных уровнях. + +```python +# Иерархический анализ по регионам и городам +hierarchical_analysis = Experiment([ + # Уровень 1: Анализ по регионам + GroupExperiment( + executors=[ + # Базовая статистика по региону + GroupSizes(), GroupDifference(), + TTest(), KSTest(), + + # Уровень 2: Анализ городов внутри региона + GroupExperiment( + executors=[ + GroupSizes(), GroupDifference(), + TTest() # Упрощенный анализ для городов + ], + searching_role=CityRole(), + reporter=CityReporter() + ) + ], + searching_role=RegionRole(), + reporter=RegionReporter() + ), + + # Агрегация по всем уровням + HierarchicalAggregator() +]) + +# Многоуровневая предобработка +hierarchical_preprocessing = Experiment([ + # Глобальная предобработка + GlobalOutliersFilter(), + + # Предобработка по группам + GroupExperiment( + executors=[ + LocalOutliersFilter(), # Локальное удаление выбросов + GroupSpecificNormalization() # Нормализация по группе + ], + searching_role=TreatmentRole(), + reporter=PreprocessingReporter() + ), + + # Финальная агрегация + FinalNormalization() +]) +``` + +**Преимущества:** + +- Естественное моделирование иерархических структур +- Агрегация на разных уровнях детализации +- Гибкость в обработке сложных данных + +### Композиция и переиспользуемость + +Эксперименты спроектированы для максимального переиспользования: + +```python +# Стандартные блоки +data_cleaning = Experiment([ + NaFiller(method="ffill"), + OutliersFilter(lower_percentile=0.05, upper_percentile=0.95) +]) + +feature_engineering = Experiment([ + CategoryAggregator(min_frequency=0.01), + DummyEncoder(), + ConstFilter(threshold=0.95) +]) + +basic_testing = Experiment([ + GroupSizes(), + GroupDifference(), + TTest(), + KSTest() +]) + +# Композиция для разных случаев +simple_ab_test = Experiment([ + data_cleaning, + basic_testing, + ABAnalyzer() +]) + +advanced_ab_test = Experiment([ + data_cleaning, + feature_engineering, + basic_testing, + Chi2Test(), # Дополнительный тест + UTest(), # Непараметрический тест + ABAnalyzer(multitest_method="fdr_bh") +]) + +matching_analysis = Experiment([ + data_cleaning, + feature_engineering, + # Специфичные для matching компоненты + MahalanobisDistance(), + FaissNearestNeighbors(), + MatchingMetrics(), + MatchingAnalyzer() +]) +``` + +Каждый компонент: + +- Независим и может быть заменен +- Использует результаты предыдущих через ExperimentData +- Добавляет свои результаты для последующих + +Это обеспечивает максимальную гибкость при построении экспериментов. + +## 9. Система Reporter'ов + +Reporter'ы отвечают за извлечение, форматирование и представление результатов экспериментов. Они служат мостом между +внутренним представлением данных в ExperimentData и форматом, удобным для пользователя или последующей обработки. + +### Архитектура Reporter'ов + +Reporter'ы работают по принципу "извлечь и отформатировать": + +1. **Извлекают результаты** из различных пространств имен ExperimentData +2. **Агрегируют и структурируют** информацию согласно своей логике +3. **Форматируют вывод** в нужном виде (dict, Dataset, HTML, PDF и т.д.) +4. **Сохраняют семантику** — каждый Reporter знает, какие результаты и как интерпретировать + +### Базовый класс Reporter + +**Reporter** — абстрактный базовый класс для всех репортеров: + +**Контракт:** + +- Метод `report(data: ExperimentData)` — единая точка входа +- Возвращает Any — конкретный формат определяется наследником +- Не модифицирует ExperimentData — только читает данные +- Детерминированность — одинаковые данные дают одинаковый отчет + +### DictReporter — универсальная основа + +DictReporter является фундаментальным классом для большинства Reporter'ов в HypEx. Его философия — предоставить +универсальный промежуточный формат (словарь), который легко преобразовать в любой другой. + +#### Концепция DictReporter + +**Основная идея:** Все результаты экспериментов можно представить как плоский словарь с уникальными ключами. + +**Формат ключей:** + +```python +# Технический формат (front=False) +"TTest╤╤revenue" → {"p-value": 0.042, "statistic": 2.15} + +# User-friendly формат (front=True) +"revenue_ttest_pvalue" → 0.042 +"revenue_ttest_statistic" → 2.15 +``` + +**Преимущества плоской структуры:** + +- Отсутствие вложенности упрощает обработку +- Уникальные ключи предотвращают конфликты +- Легко конвертировать в табличный формат +- Простая сериализация (JSON, pickle) + +#### Методы извлечения в DictReporter + +DictReporter предоставляет набор методов для извлечения разных типов результатов: + +- **`extract_from_one_row_dataset()`** — для скалярных результатов +- **`_extract_from_comparators()`** — для результатов сравнений +- **`_get_struct_dict()`** — создание структурированного словаря +- **`_convert_dataset_to_dict()`** — конвертация Dataset в dict + +Каждый наследник DictReporter переопределяет метод `report()`, комбинируя эти методы извлечения для создания нужного +словаря. + +### OnDictReporter — универсальный форматтер + +OnDictReporter — это паттерн Decorator для DictReporter'ов. Он позволяет преобразовать базовый словарный формат в любой +другой без изменения логики извлечения. + +#### Архитектура форматирования + +``` +ExperimentData → DictReporter → dict → OnDictReporter → Любой формат + ↑ ↓ + (извлечение) (форматирование) +``` + +**Ключевая идея:** Разделение извлечения данных и их представления. + +#### Примеры OnDictReporter + +**DatasetReporter** — конвертация в Dataset: + +```python +class DatasetReporter(OnDictReporter): + def __init__(self, dict_reporter: DictReporter): + self.dict_reporter = dict_reporter + + def report(self, data: ExperimentData) -> Dataset: + result_dict = self.dict_reporter.report(data) + return Dataset.from_dict(result_dict) +``` + +**HTMLReporter** — генерация HTML отчетов: + +```python +class HTMLReporter(OnDictReporter): + def report(self, data: ExperimentData) -> str: + result_dict = self.dict_reporter.report(data) + return self._dict_to_html(result_dict) +``` + +### Специализированные Reporter'ы + +#### ABDictReporter — A/B тестирование + +```python +class ABDictReporter(TestDictReporter): + def report(self, data: ExperimentData) -> dict: + result = {} + + # Извлекаем результаты тестов + result.update(self._extract_from_comparators(data)) + + # Извлекаем размеры групп + result.update(self._extract_from_executors(data, [GroupSizes])) + + # Извлекаем эффекты + result.update(self._extract_from_executors(data, [GroupDifference])) + + # Результаты ABAnalyzer (если есть) + ab_analysis = self._extract_from_executors(data, [ABAnalyzer]) + if ab_analysis: + result.update(ab_analysis) + + return result +``` + +#### MatchingDictReporter — Matching анализ + +```python +class MatchingDictReporter(DictReporter): + def report(self, data: ExperimentData) -> dict: + result = {} + + # Результаты matching + result.update(self._extract_matching_results(data)) + + # Метрики качества + result.update(self._extract_quality_metrics(data)) + + # Bias оценки + result.update(self._extract_bias_analysis(data)) + + return result +``` + +### Принципы проектирования Reporter'ов + +1. **Separation of Concerns:** + - Извлечение (что достать) отделено от форматирования (как показать) + - DictReporter отвечает за извлечение + - OnDictReporter отвечает за форматирование + +2. **Composability:** + - Reporter'ы можно комбинировать + - Один базовый reporter, множество форматов вывода + +3. **Reusability:** + - Один Reporter может использоваться в разных экспериментах + - Форматтеры переиспользуются для разных типов данных + +4. **Extensibility:** + - Легко добавить новый формат вывода + - Не нужно менять логику извлечения + +5. **Testability:** + - Reporter'ы тестируются независимо от экспериментов + - Форматтеры тестируются отдельно от извлечения + +### Преимущества архитектуры Reporter'ов + +1. **Гибкость представления** — один эксперимент, множество форматов вывода +2. **Консистентность** — стандартизированные способы извлечения результатов +3. **Масштабируемость** — легко добавлять новые форматы без изменения core +4. **Maintainability** — изменения в форматировании не влияют на логику +5. **User Experience** — пользователь получает результаты в удобном виде + +Reporter'ы обеспечивают элегантное решение проблемы представления результатов, позволяя HypEx адаптироваться под +различные use cases и требования к отчетности. + +## 10. Shell слой — готовые решения + +### Концепция Shell слоя + +Shell слой представляет собой вершину архитектуры HypEx, реализуя уровень 4 абстракции — "использование готовых +экспериментов". Этот слой воплощает философию библиотеки о предоставлении простых решений для сложных задач, скрывая всю +внутреннюю сложность за интуитивно понятным интерфейсом. + +**Ключевая идея Shell слоя:** Превратить запуск статистического эксперимента из многоэтапного процесса конфигурирования +в простой вызов метода с минимальным набором параметров. + +### Двухкомпонентная архитектура Shell слоя + +Shell слой построен на принципе разделения ответственности между двумя типами компонентов: + +#### ExperimentShell — конструктор экспериментов + +ExperimentShell является расширяемым конструктором типовых экспериментов. Его задачи: + +- **Инкапсуляция сложности**: Скрывает детали композиции Executor'ов, их последовательность и параметры +- **Интеллектуальная сборка**: Динамически строит конфигурацию эксперимента на основе входных параметров +- **Встроенные лучшие практики**: Автоматически применяет проверенные паттерны для каждого типа эксперимента +- **Валидация и безопасность**: Обеспечивает корректность входных данных и параметров + +```python +class ExperimentShell: + def __init__(self, experiment: Experiment, output: Output): + self.experiment = experiment # Композиция Executor'ов + self.output = output # Форматтер результатов + + def execute(self, data: Dataset) -> Output: + # Единый интерфейс для всех экспериментов + experiment_data = ExperimentData(data) + result_data = self.experiment.execute(experiment_data) + self.output.extract(result_data) + return self.output +``` + +#### Output — система форматированного представления + +Output классы реализуют кураторский подход к представлению результатов: + +- **Селективность**: Из всех возможных результатов выбирают только ключевые для принятия решений +- **Контекстуализация**: Представляют результаты с учетом специфики конкретного типа эксперимента +- **Удобство восприятия**: Форматируют данные для максимального удобства бизнес-пользователей +- **Семантическая организация**: Группируют результаты по смыслу через типизированные атрибуты +- **Доступ к детальным данным**: Сохраняют доступ к полным результатам ExperimentData для углубленного анализа + +### Готовые эксперименты + +#### AATest — автоматизированное A/A тестирование + +**Назначение:** Оценка качества разбиения на группы через многократное A/A тестирование с выбором оптимального варианта. + +**Архитектура эксперимента:** + +```python +# Упрощенная схема AATest +AATest = ExperimentShell( + experiment=ParamsExperiment([ + AASplitter(control_size=param, random_state=param), + OnRoleExperiment([ + GroupSizes(), GroupDifference(), TTest(), KSTest(), Chi2Test() + ]), + OneAAStatAnalyzer(), + IfAAExecutor(stop_condition) # Досрочная остановка при хорошем результате + ]), + output=AAOutput() +) +``` + +**Ключевые возможности:** + +- **precision_mode**: Режим повышенной точности с большим числом итераций +- **control_size**: Размер контрольной группы (по умолчанию 50/50) +- **stratification**: Стратифицированное разбиение для улучшения баланса +- **n_iterations**: Количество попыток разбиения +- **sample_size**: Размер выборки для ускорения на больших данных + +**Интеллектуальное поведение:** + +- Автоматический выбор лучшего разбиения по pass rate статистических тестов +- Досрочная остановка при достижении отличного качества (>95% pass rate) +- Адаптивная стратегия: стратификация включается автоматически при дисбалансе групп + +#### ABTest — A/B тестирование с коррекцией + +**Назначение:** Статистическое сравнение контрольной и тестовой групп с корректной обработкой множественного +тестирования. + +**Архитектура эксперимента:** + +```python +# Упрощенная схема ABTest +ABTest = ExperimentShell( + experiment=Experiment([ + OnRoleExperiment([ + GroupSizes(), GroupDifference(), TTest(), KSTest(), Chi2Test(), + # + дополнительные тесты по параметру additional_tests + ]), + ABAnalyzer(multitest_method=param) + ]), + output=ABOutput() +) +``` + +**Ключевые возможности:** + +- **additional_tests**: Дополнительные статистические тесты ("utest", "psi", "chi2") +- **multitest_method**: Метод коррекции множественного тестирования ("bonferroni", "holm", "fdr_bh") +- **reliability**: Уровень значимости (по умолчанию 0.05) + +**Интеллектуальное поведение:** + +- Автоматическое применение коррекции множественного тестирования +- Адаптивный выбор тестов в зависимости от типов данных +- Интерпретация результатов с учетом поправок на множественность + +#### HomogeneityTest — проверка однородности групп + +**Назначение:** Валидация корректности разбиения на группы через проверку их статистической однородности. + +**Архитектура эксперимента:** + +```python +# Схема HomogeneityTest +HomogeneityTest = ExperimentShell( + experiment=Experiment([ + OnRoleExperiment([ + GroupSizes(), GroupDifference(), TTest(), KSTest(), Chi2Test() + ]), + OneAAStatAnalyzer() + ]), + output=HomoOutput() +) +``` + +**Назначение и применение:** + +- Проверка качества randomization в экспериментах +- Валидация исторических разбиений +- Диагностика проблем с балансом групп +- Pre-flight проверка перед запуском A/B теста + +#### Matching — анализ сопоставления + +**Назначение:** Полный цикл matching анализа от поиска пар до оценки качества сопоставления. + +**Архитектура эксперимента:** + +```python +# Упрощенная схема Matching +Matching = ExperimentShell( + experiment=Experiment([ + # Подготовка данных + OutliersFilter(), NaFiller(), DummyEncoder(), + # Вычисление расстояний + MahalanobisDistance() if distance == "mahalanobis", + # Поиск пар + FaissNearestNeighbors(n_neighbors=1), + # Оценка качества + Bias(), MatchingMetrics(metric=param), + TTest(), KSTest(), # если quality_tests включены + MatchingAnalyzer() + ]), + output=MatchingOutput() +) +``` + +**Ключевые возможности:** + +- **group_match**: Сопоставление внутри групп или между группами +- **distance**: Метрика расстояния ("mahalanobis", "euclidean", "cosine") +- **metric**: Метрика качества matching ("ate", "att", "atc") +- **bias_estimation**: Оценка bias после сопоставления +- **quality_tests**: Дополнительные статистические тесты качества + +### Система Output'ов + +Каждый тип эксперимента имеет специализированный Output класс, оптимизированный для представления результатов +конкретного анализа: + +#### AAOutput — результаты A/A тестирования + +```python +class AAOutput: + best_split: Dataset # Лучшее разбиение с метриками качества + experiments: Dataset # Статистика по всем итерациям + aa_score: Dataset # Итоговый скор качества разбиения + best_split_statistic: Dataset # Детальная статистика лучшего разбиения +``` + +**Кураторский подход:** Пользователь получает готовое к использованию разбиение и полную диагностику его качества. + +#### ABOutput — результаты A/B тестирования + +```python +class ABOutput: + resume: Dataset # Основные результаты тестов + multitest: Dataset | str # Результаты с коррекцией множественного тестирования + sizes: Dataset # Размеры групп +``` + +**Кураторский подход:** Акцент на итоговых решениях с учетом коррекции и интерпретации значимости. + +#### HomoOutput — результаты проверки однородности + +```python +class HomoOutput: + resume: Dataset # Сводка по однородности всех метрик +``` + +**Кураторский подход:** Компактное представление с фокусом на общей оценке качества разбиения. + +#### MatchingOutput — результаты matching анализа + +```python +class MatchingOutput: + full_data: Dataset # Полный датасет с результатами matching + quality_results: Dataset # Метрики качества сопоставления + indexes: Dataset # Индексы сопоставленных объектов +``` + +**Кураторский подход:** Разделение на итоговые данные, диагностику качества и техническую информацию. + +### Доступ к детальным результатам + +Важная особенность системы Output'ов — сохранение доступа к полным результатам эксперимента для случаев, когда требуется +более глубокий анализ: + +```python +# Стандартное использование — кураторские результаты +ab_results = ab_test.execute(dataset) +summary = ab_results.resume # Основные выводы + +# Доступ к детальным результатам при необходимости +full_experiment_data = ab_results.experiment_data # Полный ExperimentData +raw_test_results = ab_results.additional_reporters # Результаты всех Reporter'ов +detailed_diagnostics = ab_results.get_detailed_analysis() # Расширенная диагностика +``` + +**Принципы доступа к сырым данным:** + +- **Прозрачность**: Любой результат нижних уровней остается доступным +- **Постепенная детализация**: От summary к details по мере необходимости +- **Техническая диагностика**: Доступ к промежуточным вычислениям для debugging +- **Интеграция с нижними уровнями**: Возможность извлечь данные для композиции с кастомными Executor'ами + +### Расширяемость и конфигурирование + +#### Принципы расширяемости Shell'ов + +Shell'ы спроектированы как расширяемые конструкторы, которые адаптируются под различные варианты типовых экспериментов: + +**1. Параметрическая расширяемость** + +```python +# Базовое использование +ab_test = ABTest() + +# Расширенная конфигурация +ab_test = ABTest( + additional_tests=["utest", "psi"], # Дополнительные тесты + multitest_method="fdr_bh", # Альтернативная коррекция + reliability=0.01 # Повышенная строгость +) +``` + +**2. Адаптивное поведение** + +- AATest автоматически включает стратификацию при дисбалансе +- Matching выбирает оптимальный алгоритм в зависимости от размера данных +- ABTest применяет разные стратегии коррекции в зависимости от количества метрик + +**3. Композиционная расширяемость** + +```python +# Кастомизация через наследование для новых типовых паттернов +class CustomABTest(ABTest): + def __init__(self, **kwargs): + super().__init__( + additional_tests=["bootstrap", "permutation"], + multitest_method="custom_fdr", + **kwargs + ) +``` + +#### Расширяемость Output'ов + +Output'ы поддерживают гибкую настройку представления результатов: + +**1. Дополнительные Reporter'ы** + +```python +# Добавление кастомных форматтеров +output = AAOutput() +output.additional_reporters = { + 'detailed_diagnostics': CustomDiagnosticsReporter(), + 'export_format': ExcelReporter() +} +``` + +**2. Конфигурация основных Reporter'ов** + +- Настройка форматирования чисел (precision, научная нотация) +- Выбор языка для интерпретаций (русский/английский) +- Настройка уровней детализации (краткий/полный отчет) + +### Практические примеры использования + +#### Минимальный код для типовых сценариев + +```python +# A/A тест для валидации разбиения +aa_test = AATest() +aa_results = aa_test.execute(dataset) +print(f"Качество разбиения: {aa_results.aa_score}") + +# A/B тест с консервативными настройками +ab_test = ABTest(multitest_method="bonferroni") +ab_results = ab_test.execute(dataset) +print(f"Значимые различия: {ab_results.resume}") + +# Проверка однородности групп +homo_test = HomogeneityTest() +homo_results = homo_test.execute(dataset) +print(f"Группы однородны: {homo_results.resume}") + +# Matching анализ с расширенной диагностикой +matching = Matching( + bias_estimation=True, + quality_tests=["ttest", "kstest"] +) +matching_results = matching.execute(dataset) +print(f"Качество matching: {matching_results.quality_results}") +``` + +#### Конфигурация под специфические требования + +```python +# Высокоточный A/A тест для критических экспериментов +aa_precision = AATest( + precision_mode=True, # Увеличенное число итераций + n_iterations=1000, # Явное задание количества попыток + stratification=True, # Принудительная стратификация + control_size=0.3 # Нестандартное соотношение групп +) + +# A/B тест с множественными гипотезами и строгой коррекцией +ab_comprehensive = ABTest( + additional_tests=["utest", "bootstrap", "psi"], + multitest_method="holm", + reliability=0.01 +) + +# Matching с кастомной метрикой расстояния +matching_custom = Matching( + distance="cosine", # Альтернативная метрика + metric="att", # Average Treatment Effect on Treated + group_match=True # Matching внутри групп +) +``` + +### Философия типовых решений + +#### Принцип "90% в 2 строчки" + +Shell слой реализует ключевой принцип библиотеки HypEx: 90% практических задач должны решаться в 2 строчки кода. + +Каждый Shell создавался на основе анализа реальных индустриальных потребностей: + +- **AATest** — стандартная процедура валидации разбиений в product экспериментах +- **ABTest** — основной инструмент для измерения эффекта изменений +- **HomogeneityTest** — обязательная проверка для observational studies +- **Matching** — стандартный подход для работы с non-randomized данными + +```python +# 90% задач A/B тестирования решается так: +ab_test = ABTest() +results = ab_test.execute(dataset) +``` + +#### Встроенные лучшие практики + +Каждый Shell инкапсулирует проверенные методологические подходы: + +**Статистическая корректность:** + +- Автоматическая коррекция множественного тестирования в ABTest +- Стратификация для улучшения баланса в AATest +- Проверка assumptions для параметрических тестов +- Robust оценки при наличии выбросов + +**Production готовность:** + +- Валидация входных данных и параметров +- Graceful обработка edge cases (малые выборки, отсутствующие данные) +- Информативные сообщения об ошибках +- Детерминированность результатов при фиксированном random_state + +**Интерпретируемость:** + +- Автоматическая генерация текстовых выводов +- Предупреждения о потенциальных проблемах (низкая мощность, нарушение assumptions) +- Рекомендации по улучшению качества анализа + +#### Границы применимости и расширения + +**Когда использовать Shell'ы:** + +- Стандартные экспериментальные сценарии +- Необходимость быстрого получения надежных результатов +- Работа пользователей без глубокой экспертизы в статистике +- Production системы с требованиями к стабильности + +**Когда переходить на уровень композиции:** + +- Нестандартные экспериментальные дизайны +- Специфические статистические методы, не покрытые библиотекой +- Сложные multi-stage эксперименты +- Исследовательские задачи с неопределенной методологией + +**Создание новых Shell'ов:** +Новые Shell'ы оправданы при появлении устойчивых индустриальных паттернов: + +- Новый тип эксперимента становится стандартной практикой +- Определенная комбинация методов регулярно воспроизводится +- Есть возможность инкапсулировать лучшие практики для новой области + +Shell слой HypEx воплощает идеал современного статистического софтвера: максимальная простота использования при +сохранении методологической строгости и гибкости настройки под конкретные потребности. + +## 11. Практические примеры и сценарии + +### Философия практических примеров + +> **Важное примечание:** Код в данном разделе написан в демонстрационных целях для иллюстрации архитектурных принципов +> HypEx. Примеры не взяты из реальных проектов и не претендуют на переиспользуемость без дополнительной доработки. При +> создании production решений рекомендуется использовать официальную документацию API и лучшие практики разработки. + +Данный раздел демонстрирует, как архитектура HypEx работает в реальных сценариях. Каждый пример показывает применение +определенного уровня абстракции и объясняет, почему именно этот подход оптимален для конкретной задачи. + +**Принцип эволюции решений:** Мы покажем, как одна и та же бизнес-потребность может решаться на разных уровнях сложности +в зависимости от специфических требований. Это демонстрирует ключевое преимущество архитектуры HypEx — возможность +начать с простого решения и постепенно углубляться по мере необходимости. + +**Связь с уровнями абстракции:** + +- **Уровень 4 (Shell)** — стандартные сценарии, 90% задач +- **Уровень 5 (Композиция)** — нестандартные комбинации стандартных блоков +- **Уровень 6 (Расширение)** — создание новой функциональности + +### Сценарий 1: Стандартный A/B тест (Уровень 4 — Shell) + +**Бизнес-задача:** Продуктовая команда тестирует новый дизайн кнопки на конверсию в покупку. Нужно статистически +корректно сравнить контрольную и тестовую группы. + +**Характеристики задачи:** + +- Типовый A/B тест с одной метрикой +- Стандартные требования к статистической значимости +- Нужен быстрый и надежный результат +- Команда не имеет глубокой экспертизы в статистике + +**Решение — использование ABTest Shell:** + +```python +from hypex import ABTest +from hypex.dataset import Dataset, TargetRole, TreatmentRole + +# Подготовка данных +dataset = Dataset( + data=experiment_data, + roles={ + 'user_group': TreatmentRole(), # 'control' или 'test' + 'conversion': TargetRole() # 0 или 1 + } +) + +# Запуск A/B теста +ab_test = ABTest() +results = ab_test.execute(dataset) + +# Получение результатов +print("=== РЕЗУЛЬТАТЫ A/B ТЕСТА ===") +print(f"Основные выводы:\n{results.resume}") +print(f"Размеры групп:\n{results.sizes}") +``` + +**Что происходит под капотом:** + +1. ABTest автоматически применяет набор статистических тестов (t-test, KS-test) +2. ABAnalyzer анализирует результаты и формирует выводы +3. ABOutput форматирует результаты в удобном для бизнеса виде + +**Результат:** + +``` +Группа control: 1000 пользователей, конверсия 5.2% +Группа test: 1000 пользователей, конверсия 6.8% +Статистическая значимость: p-value = 0.032 (значимо) +Рекомендация: Внедрять изменение +``` + +**Почему этот уровень подходит:** + +- Задача полностью покрывается стандартной функциональностью +- Встроенные лучшие практики (коррекция, валидация) +- Минимальный код, максимальная надежность +- Готовые к презентации результаты + +### Сценарий 2: A/B тест с дополнительными требованиями (Уровень 5 — Композиция) + +**Бизнес-задача:** Та же команда хочет провести более углубленный анализ — добавить PSI (Population Stability Index) для +проверки стабильности распределения пользователей между группами и использовать менее консервативную коррекцию +множественного тестирования. + +**Характеристики задачи:** + +- Нестандартные дополнительные тесты +- Специфические настройки коррекции +- Сохранение стандартной основы A/B теста + +**Решение — композиция стандартных компонентов:** + +```python +from hypex.experiments import Experiment, OnRoleExperiment +from hypex.executors import PSITest, TTest, KSTest, GroupSizes, GroupDifference +from hypex.analyzers import ABAnalyzer +from hypex.reporters import ABDictReporter + +# Построение кастомного A/B теста +enhanced_ab_test = Experiment([ + # Базовые метрики (стандартно) + GroupSizes(grouping_role=TreatmentRole()), + GroupDifference(grouping_role=TreatmentRole()), + + # Применяем расширенный набор тестов ко всем метрикам + OnRoleExperiment([ + TTest(grouping_role=TreatmentRole()), + KSTest(grouping_role=TreatmentRole()), + PSITest(grouping_role=TreatmentRole()) # Дополнительный тест + ], role=TargetRole()), + + # Анализ с кастомными настройками + ABAnalyzer( + multitest_method="fdr_bh", # Менее консервативная коррекция + effect_size_threshold=0.02 # Кастомный порог практической значимости + ) +]) + +# Выполнение +experiment_data = ExperimentData(dataset) +results = enhanced_ab_test.execute(experiment_data) + +# Извлечение результатов +reporter = ABDictReporter() +formatted_results = reporter.report(results) +``` + +**Архитектурные преимущества:** + +- **Переиспользование**: Используем стандартные компоненты +- **Гибкость**: Добавляем только нужную функциональность +- **Консистентность**: Сохраняется логика стандартного A/B теста +- **Прозрачность**: Видны все этапы анализа + +### Сценарий 3: Создание кастомного Executor (Уровень 6 — Расширение) + +**Бизнес-задача:** Data Science команда нуждается в специфическом статистическом тесте — Permutation Test — который +не входит в стандартный набор HypEx. + +**Характеристики задачи:** + +- Требуется новая функциональность +- Нужна интеграция с существующей архитектурой +- Планируется переиспользование в разных экспериментах + +**Решение — создание кастомного Executor:** + +```python +from hypex.executors.base import StatHypothesisTesting +from hypex.dataset import Dataset +import numpy as np +from typing import Optional + +class PermutationTest(StatHypothesisTesting): + """ + Permutation Test для сравнения групп без предположений о распределении. + """ + + def __init__(self, + n_permutations: int = 10000, + reliability: float = 0.05, + key: str = ""): + self.n_permutations = n_permutations + super().__init__(reliability=reliability, key=key) + + @classmethod + def _inner_function(cls, + data: Dataset, + test_data: Dataset, + n_permutations: int = 10000, + reliability: float = 0.05) -> Dataset: + """ + Реализация permutation test. + """ + # Извлекаем данные для двух групп + group_col = cls._get_grouping_column(data) + target_col = cls._get_target_column(test_data) + + groups = data.df[group_col].unique() + if len(groups) != 2: + raise ValueError("Permutation test поддерживает только 2 группы") + + group1_data = test_data.df[data.df[group_col] == groups[0]][target_col] + group2_data = test_data.df[data.df[group_col] == groups[1]][target_col] + + # Наблюдаемая разность средних + observed_diff = group2_data.mean() - group1_data.mean() + + # Объединяем данные для перестановок + combined_data = np.concatenate([group1_data, group2_data]) + n1, n2 = len(group1_data), len(group2_data) + + # Выполняем перестановки + permutation_diffs = [] + np.random.seed(42) # Для воспроизводимости + + for _ in range(n_permutations): + # Случайно перемешиваем данные + np.random.shuffle(combined_data) + perm_group1 = combined_data[:n1] + perm_group2 = combined_data[n1:] + + # Вычисляем разность для перестановки + perm_diff = perm_group2.mean() - perm_group1.mean() + permutation_diffs.append(perm_diff) + + # Вычисляем p-value (двусторонний тест) + permutation_diffs = np.array(permutation_diffs) + p_value = np.mean(np.abs(permutation_diffs) >= np.abs(observed_diff)) + + # Формируем результат + return Dataset.from_dict({ + "statistic": observed_diff, + "p-value": p_value, + "n_permutations": n_permutations, + "pass": p_value < reliability + }) + +# Использование в композиции +advanced_ab_experiment = Experiment([ + GroupSizes(), + GroupDifference(), + + OnRoleExperiment([ + TTest(), # Стандартный параметрический тест + KSTest(), # Стандартный непараметрический тест + PermutationTest( # Наш кастомный тест + n_permutations=20000, + reliability=0.01 + ) + ], role=TargetRole()), + + ABAnalyzer(multitest_method="holm") +]) +``` + +**Архитектурные преимущества:** + +- **Интеграция**: Кастомный Executor работает как стандартный +- **Переиспользуемость**: Может использоваться в любых экспериментах +- **Тестируемость**: Легко тестировать изолированно +- **Расширяемость**: Базовый класс предоставляет всю инфраструктуру + +### Сценарий 4: Комплексный многоэтапный анализ + +**Бизнес-задача:** Исследовательская команда изучает эффект нового алгоритма рекомендаций на удержание пользователей. +Требуется полный цикл: от A/A валидации данных до matching анализа с sensitivity проверками. + +**Характеристики задачи:** + +- Многоэтапный процесс анализа +- Комбинация разных типов экспериментов +- Сложная логика принятия решений +- Потребность в детальной диагностике + +**Решение — комбинация всех уровней архитектуры:** + +```python +# Этап 1: Валидация качества данных (Shell уровень) +aa_validation = AATest(precision_mode=True, n_iterations=500) +aa_results = aa_validation.execute(dataset) + +if aa_results.aa_score.quality_level != "excellent": + print("Предупреждение: качество разбиения недостаточно высокое") + # Дополнительная диагностика... + +# Этап 2: Предварительный A/B тест (Композиция) +preliminary_ab = Experiment([ + GroupSizes(), GroupDifference(), + OnRoleExperiment([TTest(), KSTest(), PermutationTest()], role=TargetRole()), + ABAnalyzer(multitest_method="fdr_bh") +]) + +preliminary_results = preliminary_ab.execute(ExperimentData(dataset)) + +# Этап 3: Решение о дальнейшем анализе +ab_analyzer_id = preliminary_results.get_ids(ABAnalyzer)[0] +ab_summary = preliminary_results.analysis_tables[ab_analyzer_id] + +if ab_summary["overall_decision"] in ["ship", "monitor"]: + print("Обнаружен эффект. Переходим к matching анализу для causal inference.") + + # Этап 4: Matching анализ с кастомными компонентами + matching_analysis = Experiment([ + # Подготовка данных + OutliersFilter(lower_percentile=0.05, upper_percentile=0.95), + NaFiller(method="ffill"), + DummyEncoder(), + + # Вычисление расстояний + MahalanobisDistance(grouping_role=TreatmentRole()), + + # Поиск пар + FaissNearestNeighbors(n_neighbors=1), + + # Оценка качества + Bias(grouping_role=TreatmentRole()), + MatchingMetrics(metric="ate"), + + # Статистические тесты + TTest(compare_by="groups"), + KSTest(compare_by="groups"), + + # Анализ результатов + MatchingAnalyzer() + ]) + + matching_results = matching_analysis.execute(ExperimentData(dataset)) + + # Этап 5: Sensitivity анализ (Расширение) + class SensitivityAnalyzer(Executor): + """Анализ чувствительности результатов к различным параметрам matching.""" + + def execute(self, data: ExperimentData) -> ExperimentData: + # Тестируем разные пороги matching + sensitivity_results = [] + + for threshold in [0.1, 0.2, 0.3, 0.4, 0.5]: + # Повторяем matching с разными порогами + threshold_experiment = Experiment([ + FaissNearestNeighbors(threshold=threshold), + MatchingMetrics(metric="ate"), + TTest(compare_by="groups") + ]) + + threshold_data = ExperimentData(data.ds) + threshold_result = threshold_experiment.execute(threshold_data) + + # Извлекаем ключевые метрики + metrics_id = threshold_result.get_ids(MatchingMetrics)[0] + ate_estimate = threshold_result.analysis_tables[metrics_id]["ate"] + + sensitivity_results.append({ + "threshold": threshold, + "ate_estimate": ate_estimate, + "n_matched": len(threshold_result.additional_fields.get("matched_indices", [])) + }) + + # Сохраняем результаты sensitivity анализа + sensitivity_dataset = Dataset.from_records(sensitivity_results) + return data.set_value( + space=ExperimentDataEnum.analysis_tables, + executor_id=self.id, + value=sensitivity_dataset + ) + + # Запуск sensitivity анализа + sensitivity_analyzer = SensitivityAnalyzer() + final_results = sensitivity_analyzer.execute(matching_results) + + # Этап 6: Comprehensive отчетность + class ComprehensiveMatchingReporter(DictReporter): + """Comprehensive reporter для всего анализа.""" + + def report(self, data: ExperimentData) -> dict: + base_results = super().report(data) + + # Добавляем результаты sensitivity анализа + sensitivity_id = data.get_ids(SensitivityAnalyzer)[0] + sensitivity_data = data.analysis_tables[sensitivity_id] + + base_results['sensitivity_analysis'] = { + 'stability_assessment': self._assess_stability(sensitivity_data), + 'robust_effect_estimate': self._robust_estimate(sensitivity_data), + 'recommended_threshold': self._recommend_threshold(sensitivity_data) + } + + # Добавляем качественную оценку + base_results['quality_assessment'] = self._comprehensive_quality_check(data) + + return base_results + + def _assess_stability(self, data: Dataset) -> str: + # Логика оценки стабильности результатов + estimates = data.df["ate_estimate"].values + cv = np.std(estimates) / np.mean(estimates) # Coefficient of variation + + if cv < 0.1: + return "highly_stable" + elif cv < 0.2: + return "stable" + else: + return "unstable" + + def _robust_estimate(self, data: Dataset) -> float: + # Робастная оценка (медиана) + return data.df["ate_estimate"].median() + + def _recommend_threshold(self, data: Dataset) -> float: + # Рекомендуемый порог (баланс качества и покрытия) + data_df = data.df + # Простая эвристика: максимизируем произведение покрытия и обратной дисперсии + coverage = data_df["n_matched"] / data_df["n_matched"].max() + stability = 1 / (data_df["ate_estimate"].rolling(3).std().fillna(1)) + score = coverage * stability + + return data_df.loc[score.idxmax(), "threshold"] + + def _comprehensive_quality_check(self, data: ExperimentData) -> str: + # Комплексная оценка качества всего анализа + # ... логика проверки всех компонентов + return "EXCELLENT" # Упрощено + + comprehensive_reporter = ComprehensiveMatchingReporter() + final_report = comprehensive_reporter.report(final_results) + +else: + print("Эффект не обнаружен или слишком слаб для matching анализа.") +``` + +**Результат многоэтапного анализа:** + +``` +=== COMPREHENSIVE MATCHING ANALYSIS === + +Preprocessing Quality: + - Outliers removed: 234 observations (2.1%) + - Missing values imputed: 12 features + - Feature engineering: 8 new features created + +Matching Results: + - Matched pairs: 3,847 из 4,120 (93.4% coverage) + - Average match distance: 0.18 (good quality) + - Bias reduction: 85% (excellent) + +Treatment Effect: + - Retention increase: +8.4% (95% CI: [5.2%, 11.6%]) + - P-value: < 0.001 (highly significant) + - Effect size: Cohen's d = 0.32 (medium effect) + +Sensitivity Analysis: + - Effect stable across thresholds 0.1-0.4 + - Robust estimate: +8.1% ± 1.2% + - Recommended threshold: 0.25 (best coverage/quality trade-off) + +Quality Assessment: EXCELLENT + - All balance checks passed + - Covariate overlap sufficient + - Results robust to specification choices +``` + +**Архитектурные преимущества комбинированного подхода:** + +1. **Эффективность разработки:** Начали с Shell для быстрой оценки +2. **Модульность:** Каждый этап можно тестировать и отлаживать независимо +3. **Переиспользование:** Кастомные компоненты можно использовать в других проектах +4. **Прозрачность:** Полная видимость всех этапов анализа +5. **Научная строгость:** Sensitivity анализ и множественные проверки качества + +### Эволюция решения: От простого к сложному + +Рассмотрим, как одна и та же задача решается на разных уровнях сложности: + +**Задача:** Измерить влияние нового алгоритма рекомендаций на выручку. + +#### Этап 1: Первая итерация (Shell) + +```python +# Быстрый A/B тест для первичной оценки +ab_test = ABTest() +initial_results = ab_test.execute(dataset) +# Результат: "Есть положительный эффект +12%, p=0.02" +``` + +#### Этап 2: Углубленный анализ (Композиция) + +```python +# Stakeholder'ы просят добавить анализ подгрупп +segmented_experiment = Experiment([ + # Общий анализ + OnRoleExperiment([TTest(), KSTest()], role=TargetRole()), + + # Анализ по сегментам пользователей + GroupExperiment([ + OnRoleExperiment([TTest()], role=TargetRole()) + ], grouping_role=UserSegmentRole()), + + ABAnalyzer(multitest_method="fdr_bh") # Коррекция для multiple segments +]) +``` + +#### Этап 3: Продвинутая статистика (Расширение) + +```python +# Экономисты просят добавить causal inference методы +class DoublyRobustEstimator(Calculator): + """Doubly robust estimation для более точной causal inference""" + # ... реализация + + +advanced_experiment = Experiment([ + # Стандартные тесты + OnRoleExperiment([TTest(), KSTest()], role=TargetRole()), + + # Продвинутые causal methods + DoublyRobustEstimator(), + InstrumentalVariablesAnalysis(), + + # Sensitivity анализ + ConfoundingSensitivityTest(), +]) +``` + +**Ключевой инсайт:** Архитектура HypEx позволяет начать с простого решения и органично наращивать сложность без +переписывания кода. Каждый уровень строится на предыдущем, добавляя необходимую функциональность. + +### Руководство по выбору подходящего уровня + +#### Принципы принятия решений + +**Используйте Shell (Уровень 4), когда:** + +- Задача полностью покрывается стандартным экспериментом +- Команда не имеет глубокой экспертизы в статистике +- Нужен быстрый и надежный результат +- Требуется production-ready решение + +**Переходите на Композицию (Уровень 5), когда:** + +- Нужны дополнительные тесты или метрики +- Требуется нестандартная последовательность операций +- Необходимо объединить несколько типовых анализов +- Хотите больше контроля над процессом + +**Создавайте Расширения (Уровень 6), когда:** + +- Требуется функциональность, отсутствующая в библиотеке +- У команды есть экспертиза для создания кастомных методов +- Планируется многократное переиспользование +- Нужна интеграция с внешними библиотеками + +#### Критерии качественного решения + +HypEx позволяет органично развивать решение по мере роста требований. + +**Правило 90-9-1:** + +- 90% задач решаются Shell'ами (уровень 4) +- 9% требуют композиции стандартных блоков (уровень 5) +- 1% нуждается в создании новой функциональности (уровень 6) + +**Критерии качественного решения:** + +1. **Простота:** Используйте минимально необходимый уровень сложности +2. **Переиспользование:** Предпочитайте стандартные компоненты кастомным +3. **Модульность:** Разбивайте сложную логику на композируемые части +4. **Тестируемость:** Каждый компонент должен быть независимо тестируемым +5. **Документированность:** Кастомные компоненты требуют подробного описания + +Архитектура HypEx спроектирована так, чтобы естественным образом направлять разработчиков к качественным решениям, +предоставляя правильные абстракции для каждого уровня сложности. + +## Заключение + +### Ключевые архитектурные достижения + +Архитектура HypEx успешно решает фундаментальную проблему разрыва между простотой использования и гибкостью статистических инструментов. Ключевые достижения: + +**1. Многоуровневая абстракция как архитектурный принцип** + +Система 8 уровней абстракции позволяет пользователям работать на комфортном уровне сложности, при этом сохраняя возможность перехода на более детальные уровни по мере роста потребностей. Это обеспечивает: +- Низкий порог входа для начинающих +- Неограниченную гибкость для экспертов +- Естественную эволюцию решений + +**2. Композиция как основа расширяемости** + +Принцип "композиция над наследованием" реализован последовательно на всех уровнях: +- Executor'ы как атомарные операции +- Experiment'ы как оркестраторы +- Shell'ы как готовые решения +- Каждый компонент может быть заменен или дополнен + +**3. Единый поток данных через ExperimentData** + +ExperimentData служит универсальной шиной данных, обеспечивая: +- Прозрачность всех промежуточных результатов +- Возможность отладки на любом этапе +- Простую интеграцию новых компонентов +- Воспроизводимость экспериментов + +### Принципы, выдержавшие проверку практикой + +**Разделение ответственности** + +Четкое разделение между вычислениями (Calculator'ы), оркестрацией (Experiment'ы), анализом (Analyzer'ы) и представлением (Reporter'ы) обеспечивает: +- Простоту тестирования +- Легкость модификации +- Возможность независимого развития компонентов + +**Backend-агностичность через Extension Framework** + +Разделение бизнес-логики и технических деталей реализации позволяет: +- Использовать оптимальные инструменты для каждого типа данных +- Добавлять новые backend'ы без изменения core логики +- Изолировать внешние зависимости + +**Кураторский подход к результатам** + +Система Output'ов и Reporter'ов превращает технические результаты в business-ready решения: +- 90% пользователей получают готовые выводы +- 10% экспертов имеют доступ к детальным данным +- Консистентная интерпретация результатов + +### Архитектурные паттерны HypEx + +**Паттерн "Эволюционного усложнения"** + +Возможность начать с простого Shell'а и постепенно добавлять сложность: +``` +Shell → Композиция → Расширение → Модификация +``` + +**Паттерн "Декомпозиции сложности"** + +Разбиение сложных операций на цепочки простых: +``` +Сложный анализ = Preprocessing + Tests + Analysis + Reporting +``` + +**Паттерн "Полиморфной замещаемости"** + +Любой компонент может быть заменен на альтернативную реализацию: +``` +TTest ← → PermutationTest ← → BootstrapTest +``` + +### Практические выводы для разработчиков + +**Выбор правильного уровня абстракции** + +- Начинайте с Shell'ов для стандартных задач +- Переходите к композиции при нестандартных требованиях +- Создавайте расширения только при необходимости многократного использования + +**Принципы качественного кода в HypEx** + +1. **Следуйте правилу 90-9-1**: большинство задач должно решаться стандартными инструментами +2. **Предпочитайте композицию наследованию**: комбинируйте готовые блоки +3. **Тестируйте на уровне компонентов**: каждый Executor должен быть протестирован изолированно +4. **Документируйте кастомные решения**: нестандартные компоненты требуют подробного описания + +**Архитектурные anti-patterns** + +- Создание monolithic Executor'ов, выполняющих множество задач +- Прямые зависимости между Executor'ами +- Обход системы ролей при работе с данными +- Игнорирование Extension Framework при работе с внешними библиотеками + +### Направления развития архитектуры + +**Масштабируемость** + +- Поддержка распределенных вычислений через новые backend'ы +- Оптимизация для больших данных +- Асинхронное выполнение Executor'ов + +**Интеграция** + +- Расширение Extension Framework для новых ML библиотек +- Интеграция с workflow системами (Airflow, Prefect) +- API для интеграции с внешними системами + +**Пользовательский опыт** + +- Интеллектуальные рекомендации по выбору методов +- Автоматическая диагностика проблем в данных +- Интерактивные визуализации результатов + +### Философия архитектурных решений + +HypEx демонстрирует, что хорошая архитектура должна быть: + +**Приспособляемой** — легко адаптироваться к изменяющимся требованиям +**Интуитивной** — естественно направлять пользователей к правильным решениям +**Прозрачной** — позволять понимать и контролировать происходящие процессы +**Расширяемой** — предоставлять четкие точки для добавления новой функциональности + +Архитектура HypEx служит примером того, как сложная предметная область (статистический анализ) может быть организована в интуитивно понятную и мощную систему через правильное применение принципов объектно-ориентированного проектирования и многоуровневой абстракции. + +Успех HypEx в решении задач статистического анализа демонстрирует универсальность этих архитектурных принципов и их применимость к другим сложным предметным областям. diff --git a/schemes/anatomy_presentation.html b/schemes/anatomy_presentation.html new file mode 100644 index 00000000..e7ff5528 --- /dev/null +++ b/schemes/anatomy_presentation.html @@ -0,0 +1,912 @@ + + + + + + Архитектура HypEx - Часть 1 + + + +
+
1 / 18
+ +
+ +
+

Архитектура HypEx

+

Модульная библиотека для статистического анализа и A/B тестирования

+
+
+

Executor Pattern

+

Композиционная архитектура

+
+
+
+

Типобезопасность

+

Система ролей данных

+
+
+
+

Готовые решения

+

AATest, ABTest, Matching

+
+
+
+ + +
+

Что такое HypEx?

+
+
+

Библиотека анализа

+

Статистический анализ и экспериментирование с акцентом на типобезопасность и композицию

+
+
+

Модульная архитектура

+

Основана на паттерне Executor с возможностью создания сложных пайплайнов

+
+
+

Готовые решения

+

Предустановленные эксперименты для A/A тестов, A/B тестов и matching анализа

+
+
+

Расширяемость

+

8 уровней абстракции для пользователей разного уровня экспертизы

+
+
+
+ + +
+

Проблемы которые решает HypEx

+ +
+ + +
+

Домены применения

+
+
+

A/B тестирование

+

Сравнение двух групп с множественными статистическими тестами и коррекцией

+
+
+

A/A тестирование

+

Проверка корректности разделения и отсутствия ложных срабатываний

+
+
+

Matching анализ

+

Поиск похожих объектов и оценка качества сопоставления

+
+
+

Homogeneity тестирование

+

Проверка однородности групп по множественным характеристикам

+
+
+
+ + +
+

Философия архитектуры

+

Модульность

+
+
+

Executor

+

Базовый строительный блок

+
+
+
+
+

Executor

+

Другой компонент

+
+
=
+
+

Experiment

+

Сложный пайплайн

+
+
+ +
+ + +
+

Композиция вместо наследования

+
+
+

❌ Наследование

+
+class CustomTest(BaseTest): + def run(self): + # Жесткая связанность + # Сложно тестировать + # Трудно изменять +
+
+
+

✅ Композиция

+
+Experiment([ + Splitter(), + TTest(), + Analyzer() +]) +# Гибкость +# Тестируемость +
+
+
+ +
+ + +
+

Типобезопасность через роли

+
+
+

TargetRole

+

Целевая переменная для анализа

+
+
+

TreatmentRole

+

Группирующая переменная (контроль/тест)

+
+
+

FeatureRole

+

Признаки для анализа

+
+
+

GroupingRole

+

Дополнительное группирование

+
+
+
+

Преимущества:

+
    +
  • Компилятор проверяет корректность использования данных
  • +
  • Предотвращение ошибок на этапе разработки
  • +
  • Самодокументируемый код - роли объясняют назначение колонок
  • +
+
+
+ + +
+

Единообразие интерфейсов

+
+# Единый интерфейс для всех компонентов +class Executor: + def execute(self, data: ExperimentData) -> ExperimentData: + pass + +# Использование в любом контексте +experiment = Experiment([ + AASplitter(), # Разделение данных + TTest(), # Статистический тест + ABAnalyzer() # Анализ результатов +]) +
+ +
+ + +
+

8 уровней абстракции

+

+ HypEx предоставляет разные уровни взаимодействия + для пользователей с различным уровнем экспертизы +

+
+
+

Бизнес-пользователи

+

UI платформы, готовые сценарии

+
+
+
+

Аналитики и исследователи

+

Конструкторы, шаблоны, оболочки

+
+
+
+

Разработчики

+

Код, наследование, модификация ядра

+
+
+
+ + +
+

Уровни 1-2: Платформа

+
+
Уровень 1. Пользовательский интерфейс
+
👤 Бизнес-пользователь
+
Запуск готовых сценариев через веб-интерфейс без написания кода. Выбор сценария и получение результатов анализа.
+
+
+
Уровень 2. Конструктор сценариев
+
👤 Финансист/Аналитик
+
Создание сценариев в графическом конструкторе. Знание статистики, понимание бизнес-процессов, но без программирования.
+
+
+

Преимущества платформенных уровней:

+

• Доступность для нетехнических специалистов
+ • Быстрое получение результатов
+ • Стандартизированные процессы анализа

+
+
+ + +
+

Уровни 3-4: Dev пакет и библиотека

+
+
Уровень 3. Настраиваемый шаблонный код
+
👤 Бизнес-пользователь с доступом к лабораторной зоне
+
Запуск заранее запрограммированных сценариев с настраиваемыми параметрами. Минимальные изменения в коде.
+
+
+
Уровень 4. Оболочка эксперимента HypEx
+
👤 Исследователь данных
+
Использование готовых экспериментальных оболочек (AATest, ABTest). Настройка и запуск в несколько строк кода.
+
+
+# Пример уровня 4 +aa_test = AATest(stratification=True) +results = aa_test.execute(dataset) +
+
+ + +
+

Уровни 5-6: Создание экспериментов

+
+
Уровень 5. Создание эксперимента в коде
+
👤 Разработчик, знакомый с базовыми блоками
+
Создание кастомных экспериментов из готовых Executor'ов. Композиция базовых блоков в новые пайплайны.
+
+
+
Уровень 6. Создание Executor наследованием
+
👤 Разработчик, знакомый с типовыми блоками
+
Создание кастомных Executor'ов наследованием от типовых блоков. Расширение функциональности существующих компонентов.
+
+
+# Пример уровня 5 +custom_experiment = Experiment([ + AASplitter(), TTest(), CustomAnalyzer() +]) +
+
+ + +
+

Уровни 7-8: Модификация и ядро

+
+
Уровень 7. Модификация библиотеки
+
👤 Опытный разработчик HypEx
+
Глубокие доработки базовых механик библиотеки. Изменение поведения существующих компонентов и добавление новых возможностей.
+
+
+
Уровень 8. Ядро архитектуры
+
👤 Архитектор HypEx
+
Изменение фундаментального поведения библиотеки. Создание нового поколения архитектуры, изменение базовых принципов.
+
+
+

⚠️ Высокие уровни требуют:

+

• Глубокого понимания архитектуры HypEx
+ • Опыта разработки библиотек
+ • Ответственности за обратную совместимость

+
+
+ + +
+

Преимущества многоуровневой архитектуры

+
+
+

Доступность

+

Каждый пользователь находит подходящий уровень сложности

+
+
+

Масштабируемость обучения

+

Постепенное углубление знаний от простого к сложному

+
+
+

Эффективность

+

Не нужно изучать сложные концепции для простых задач

+
+
+

Гибкость применения

+

От готовых решений до полной кастомизации

+
+
+
+

Путь пользователя в HypEx

+
+
UI
+
+
Шаблоны
+
+
Код
+
+
Ядро
+
+
+
+ + +
+

Сегментация пользователей

+
+
+

Платформа

+
+

Бизнес-пользователи

+

• Менеджеры продуктов
• Маркетологи
• Аналитики без технических навыков

+

Нужны результаты, не код

+
+
+
+

Dev пакет

+
+

Исследователи данных

+

• Data Scientists
• Финансовые аналитики
• Исследователи

+

Знают статистику, умеют программировать

+
+
+
+
+

Библиотека

+
+
+

Разработчики

+

• Python разработчики
• ML инженеры
• Архитекторы решений

+
+
+

Контрибьюторы

+

• Core разработчики
• Архитекторы библиотеки
• Экспертные пользователи

+
+
+
+
+ + +
+

Примеры использования уровней

+
+
+

Уровень 1: UI

+
+1. Выбрать "A/B тест" +2. Загрузить данные +3. Указать колонки +4. Нажать "Запустить" +5. Получить отчет +
+
+
+

Уровень 4: Оболочка

+
+ab_test = ABTest( + additional_tests=["t-test"], + multitest_method="holm" +) +result = ab_test.execute(data) +
+
+
+
+
+

Уровень 5: Композиция

+
+Experiment([ + GroupSizes(), + TTest(), + ABAnalyzer() +]) +
+
+
+

Уровень 6: Наследование

+
+class CustomTTest(TTest): + def calc(self, data, **kwargs): + # Custom logic + return super().calc(data, **kwargs) +
+
+
+
+ + +
+

Переходы между уровнями

+
+
+
+

Платформа

+

Готовые сценарии

+
+
+
+

Нужна кастомизация

+
+
+

Dev пакет

+

Шаблоны с параметрами

+
+
+
+
+

Оболочки

+

AATest, ABTest

+
+
+
+

Нужна гибкость

+
+
+

Композиция

+

Experiment([...])

+
+
+
+
+

Готовые блоки

+

TTest, Splitter

+
+
+
+

Нужна новая логика

+
+
+

Наследование

+

class Custom(Base)

+
+
+
+

+ Плавный переход между уровнями по мере роста экспертизы +

+
+ + +
+

Заключение: Основы архитектуры

+
+
+

Философия

+

Модульность, композиция, типобезопасность через роли

+
+
+

Многоуровневость

+

8 уровней от UI до ядра для разных пользователей

+
+
+

Решаемые проблемы

+

Стандартизация, переиспользование, предотвращение ошибок

+
+
+

Домены применения

+

A/B, A/A тесты, matching, homogeneity анализ

+
+
+
+

Далее: Executor Pattern

+

+ В следующей части разберем ядро архитектуры - паттерн Executor, + систему типов данных и композицию экспериментов +

+
+
+
+ + + + + + \ No newline at end of file diff --git a/schemes/architecture.dot b/schemes/architecture.dot deleted file mode 100644 index 2a6fe7c2..00000000 --- a/schemes/architecture.dot +++ /dev/null @@ -1,280 +0,0 @@ -digraph Architecture{ - compound=true - node[style=filled] - - subgraph modules{ - node[shape=box3d, fillcolor=violet] - - executor_legend[label="Executor"] - // ------- - experiment_executor_list[label="List[Executor]", shape=folder] - experiment_legend[label="Experiment"] - experiment_executor_0_in_list[label="Executor 0"] - experiment_executor_1_in_list[label="Executor 1"] - experiment_executor_dotted_in_list[label="..."] - experiment_executor_n_in_list[label="Executor n"] - // ------- - multiexperiment_experiment[label="Experiment"] - multiexperiment_analyzer[label="Analyzer"] - multiexperiment[label="MultiExperiment"] - multiexperiment_execute_experiment[label="Experiment"] - multiexperiment_execute_analyzer[label="Analyzer"] - // ------- - spliter_executor[label="Spliter"] - // ------- - stat_executor[label="Stat"] - // ------- - analyzer_executor[label="Analyzer"] - // ------- - transformer_executor[label="Transformer"] - // ------- - operator_executor[label="Operator"] - // ------- - report_experiment[label="Experiment"] - } - - subgraph methods{ - node[shape=box, fillcolor=cornflowerblue] - executor_execute_legend[label="execute"] - // ------- - experiment_execute_legend[label="execute"] - experiment_execute_0[label="execute 0"] - experiment_execute_1[label="execute 1"] - experiment_execute_dotted[label="..."] - experiment_execute_n[label="execute n"] - // ------- - multiexperiment_execute[label="execute"] - multiexperiment_executor_execute[label="execute"] - multiexperiment_analyzer_execute[label="execute"] - // ------- - spliter_execute[label="execute"] - // ------- - stat_execute[label="execute"] - // ------- - analyzer_execute[label="execute"] - // ------- - transformer_execute[label="execute"] - // ------- - operator_execute[label="execute"] - // ------- - report_execute[label="execute"] - } - - subgraph data{ - node[shape=note, fillcolor=lightgreen] - - executor_legend_data[label="Dataset"] - // ------- - experiment_legend_data[label="Dataset"] - experiment_data_0[label="ExperimentData 0"] - experiment_data_1[label="ExperimentData 1"] - experiment_data_n[label="ExperimentData n-1"] - experiment_data_final[label="ExperimentData n"] - // ------- - multiexperiment_data[label="Dataset"] - multiexperiment_executor_experiment_data[label="ExperimentData i"] - multiexperiment_executor_result_data[label="ExperimentData"] - // ------- - spliter_data[label="Dataset"] - spliter_out_data[shape=record label="Dataset | group column"] - // ------- - stat_data[shape=record label="Dataset | stat target column"] - stat_experiment_data[label="ExperimentData"] - // ------- - analyzer_in_data[label="ExperimentData"] - analyzer_out_data[label="ExperimentData"] - // ------- - transformer_in_data[label="Dataset"] - transformer_out_data[label="Dataset"] - // ------- - operator_in_data[shape=record label="Dataset | x1 | x2"] - operator_out_data[label="ExperimentData"] - // ------- - report_in_data[label="ExperimentData"] - report_out_data[label="Report artifact"] - } - - subgraph attributes{ - node[shape=record, fillcolor=lightpink] - experiment_executor_list_attribute[label="List[Executor]"] - report_backend[label="Backend"] - } - - subgraph cluster_executors{ - graph[label="Executors"] - - subgraph cluster_executor{ - graph[style=dashed, label="Executor"] - - executor_legend_data -> executor_execute_legend - executor_legend -> executor_execute_legend [arrowhead=none] - } - - subgraph cluster_spliter{ - graph[style=dashed, label="Spliter"] - - spliter_executor -> spliter_execute [arrowhead=none] - spliter_execute -> spliter_out_data - spliter_data -> spliter_execute - } - - executor_legend -> spliter_executor [ - style="dotted" - arrowhead=curve - ltail=cluster_executor - lhead=cluster_spliter - ] - - subgraph cluster_stats{ - graph[style=dashed, label="Stats"] - - stat_executor -> stat_execute [arrowhead=none] - stat_data -> stat_execute - stat_execute -> stat_experiment_data - } - - executor_legend -> stat_executor [ - style="dotted" - arrowhead=curve - ltail=cluster_executor - lhead=cluster_stats - ] - - subgraph cluster_analyser{ - graph[style=dashed, label="Analyser"] - - analyzer_executor -> analyzer_execute [arrowhead=none] - analyzer_in_data -> analyzer_execute - analyzer_execute -> analyzer_out_data - } - - executor_legend -> analyzer_executor [ - style="dotted" - arrowhead=curve - ltail=cluster_executor - lhead=cluster_analyser - ] - - subgraph cluster_transformer{ - graph[style=dashed, label="Transformer"] - - transformer_executor -> transformer_execute [arrowhead=none] - transformer_in_data -> transformer_execute - transformer_execute -> transformer_out_data - } - - executor_legend -> transformer_executor [ - style="dotted" - arrowhead=curve - ltail=cluster_executor - lhead=cluster_transformer - ] - - subgraph cluster_operator{ - graph[style=dashed, label="Operator"] - - operator_executor -> operator_execute [arrowhead=none] - operator_in_data -> operator_execute - operator_execute -> operator_out_data - } - - executor_legend -> operator_executor [ - style="dotted" - arrowhead=curve - ltail=cluster_executor - lhead=cluster_operator - ] - - } - - subgraph cluster_experiment{ - graph[style=dashed, label="Experiment"] - experiment_executor_list -> experiment_executor_list_attribute - experiment_executor_list_attribute -> experiment_legend [arrowhead=none] - experiment_legend -> experiment_execute_legend [arrowhead=none] - experiment_legend_data -> experiment_execute_legend - experiment_legend_data -> experiment_execute_0 [style=dotted] - - subgraph cluster_experiment_executor{ - graph[style=dotted, label="execute"] - experiment_executor_0_in_list -> experiment_execute_0 [arrowhead=none] - experiment_execute_0 -> experiment_data_0 -> experiment_execute_1 - experiment_executor_1_in_list -> experiment_execute_1 [arrowhead=none] - experiment_execute_1 -> experiment_data_1 -> experiment_execute_dotted - experiment_executor_dotted_in_list -> experiment_execute_dotted [arrowhead=none] - experiment_execute_dotted -> experiment_data_n -> experiment_execute_n - experiment_executor_n_in_list -> experiment_execute_n [arrowhead=none] - experiment_execute_n -> experiment_data_final - } - - experiment_execute_legend -> experiment_execute_0 [ - arrowhead=none, - lhead=cluster_experiment_executor - ] - } - - executor_legend -> experiment_legend [ - style="dotted" - arrowhead=curve - ltail=cluster_executor - lhead=cluster_experiment - ] - - subgraph cluster_multiexperiment{ - graph[style=dashed, label="MultiExperiment"] - {multiexperiment_experiment multiexperiment_analyzer} -> multiexperiment [arrowhead=none] - multiexperiment -> multiexperiment_execute [arrowhead=none] - multiexperiment_data -> multiexperiment_execute - - subgraph cluster_multiexperiment_execute{ - graph[style=dotted, label="execute"] - - multiexperiment_execute_experiment -> multiexperiment_executor_execute [arrowhead=none] - multiexperiment_execute_analyzer -> multiexperiment_analyzer_execute [arrowhead=none] - multiexperiment_executor_execute -> multiexperiment_executor_experiment_data - multiexperiment_executor_experiment_data -> multiexperiment_analyzer_execute - - multiexperiment_analyzer_execute -> multiexperiment_executor_result_data - - multiexperiment_executor_result_data -> multiexperiment_executor_execute [ - style=dashed - label="n times" - ] - } - - multiexperiment_execute -> multiexperiment_executor_execute [ - arrowhead=none - lhead=cluster_multiexperiment_execute - ] - } - - analyzer_executor -> multiexperiment_analyzer [ - style="dotted" - arrowhead=curve - ltail=cluster_analyser - lhead=cluster_multiexperiment - ] - - experiment_legend -> multiexperiment [ - style="dotted" - arrowhead=curve - ltail=cluster_experiment - lhead=cluster_multiexperiment - ] - - subgraph cluster_report{ - graph[style=dashed, label="Report"] - - report_backend -> report_experiment [arrowhead=none] - report_experiment -> report_execute [arrowhead=none] - report_in_data -> report_execute - report_execute -> report_out_data - } - - experiment_legend -> report_experiment [ - style="dotted" - arrowhead=curve - ltail=cluster_experiment - lhead=cluster_report - ] -} \ No newline at end of file diff --git a/schemes/architecture.svg b/schemes/architecture.svg deleted file mode 100644 index c4bc5ed8..00000000 --- a/schemes/architecture.svg +++ /dev/null @@ -1,841 +0,0 @@ - - - - - - -Architecture - - -cluster_executors - -Executors - - -cluster_spliter - -Spliter - - -cluster_executor - -Executor - - -cluster_stats - -Stats - - -cluster_analyser - -Analyser - - -cluster_operator - -Operator - - -cluster_transformer - -Transformer - - -cluster_report - -Report - - -cluster_experiment - -Experiment - - -cluster_experiment_executor - -execute - - -cluster_multiexperiment - -MultiExperiment - - -cluster_multiexperiment_execute - -execute - - - -executor_legend - - - - -Executor - - - -experiment_legend - - - - -Experiment - - - -executor_legend->experiment_legend - - - - - - -spliter_executor - - - - -Spliter - - - -executor_legend->spliter_executor - - - - - - -stat_executor - - - - -Stat - - - -executor_legend->stat_executor - - - - - - -analyzer_executor - - - - -Analyzer - - - -executor_legend->analyzer_executor - - - - - - -transformer_executor - - - - -Transformer - - - -executor_legend->transformer_executor - - - - - - -operator_executor - - - - -Operator - - - -executor_legend->operator_executor - - - - - - -executor_execute_legend - -execute - - - -executor_legend->executor_execute_legend - - - - -experiment_executor_list - -List[Executor] - - - -experiment_executor_list_attribute - -List[Executor] - - - -experiment_executor_list->experiment_executor_list_attribute - - - - - -multiexperiment - - - - -MultiExperiment - - - -experiment_legend->multiexperiment - - - - - - -report_experiment - - - - -Experiment - - - -experiment_legend->report_experiment - - - - - - -experiment_execute_legend - -execute - - - -experiment_legend->experiment_execute_legend - - - - -experiment_executor_0_in_list - - - - -Executor 0 - - - -experiment_execute_0 - -execute 0 - - - -experiment_executor_0_in_list->experiment_execute_0 - - - - -experiment_executor_1_in_list - - - - -Executor 1 - - - -experiment_execute_1 - -execute 1 - - - -experiment_executor_1_in_list->experiment_execute_1 - - - - -experiment_executor_dotted_in_list - - - - -... - - - -experiment_execute_dotted - -... - - - -experiment_executor_dotted_in_list->experiment_execute_dotted - - - - -experiment_executor_n_in_list - - - - -Executor n - - - -experiment_execute_n - -execute n - - - -experiment_executor_n_in_list->experiment_execute_n - - - - -multiexperiment_experiment - - - - -Experiment - - - -multiexperiment_experiment->multiexperiment - - - - -multiexperiment_analyzer - - - - -Analyzer - - - -multiexperiment_analyzer->multiexperiment - - - - -multiexperiment_execute - -execute - - - -multiexperiment->multiexperiment_execute - - - - -multiexperiment_execute_experiment - - - - -Experiment - - - -multiexperiment_executor_execute - -execute - - - -multiexperiment_execute_experiment->multiexperiment_executor_execute - - - - -multiexperiment_execute_analyzer - - - - -Analyzer - - - -multiexperiment_analyzer_execute - -execute - - - -multiexperiment_execute_analyzer->multiexperiment_analyzer_execute - - - - -spliter_execute - -execute - - - -spliter_executor->spliter_execute - - - - -stat_execute - -execute - - - -stat_executor->stat_execute - - - - -analyzer_executor->multiexperiment_analyzer - - - - - - -analyzer_execute - -execute - - - -analyzer_executor->analyzer_execute - - - - -transformer_execute - -execute - - - -transformer_executor->transformer_execute - - - - -operator_execute - -execute - - - -operator_executor->operator_execute - - - - -report_execute - -execute - - - -report_experiment->report_execute - - - - -experiment_execute_legend->experiment_execute_0 - - - - -experiment_data_0 - - - -ExperimentData 0 - - - -experiment_execute_0->experiment_data_0 - - - - - -experiment_data_1 - - - -ExperimentData 1 - - - -experiment_execute_1->experiment_data_1 - - - - - -experiment_data_n - - - -ExperimentData n-1 - - - -experiment_execute_dotted->experiment_data_n - - - - - -experiment_data_final - - - -ExperimentData n - - - -experiment_execute_n->experiment_data_final - - - - - -multiexperiment_execute->multiexperiment_executor_execute - - - - -multiexperiment_executor_experiment_data - - - -ExperimentData i - - - -multiexperiment_executor_execute->multiexperiment_executor_experiment_data - - - - - -multiexperiment_executor_result_data - - - -ExperimentData - - - -multiexperiment_analyzer_execute->multiexperiment_executor_result_data - - - - - -spliter_out_data - -Dataset - -group column - - - -spliter_execute->spliter_out_data - - - - - -stat_experiment_data - - - -ExperimentData - - - -stat_execute->stat_experiment_data - - - - - -analyzer_out_data - - - -ExperimentData - - - -analyzer_execute->analyzer_out_data - - - - - -transformer_out_data - - - -Dataset - - - -transformer_execute->transformer_out_data - - - - - -operator_out_data - - - -ExperimentData - - - -operator_execute->operator_out_data - - - - - -report_out_data - - - -Report artifact - - - -report_execute->report_out_data - - - - - -executor_legend_data - - - -Dataset - - - -executor_legend_data->executor_execute_legend - - - - - -experiment_legend_data - - - -Dataset - - - -experiment_legend_data->experiment_execute_legend - - - - - -experiment_legend_data->experiment_execute_0 - - - - - -experiment_data_0->experiment_execute_1 - - - - - -experiment_data_1->experiment_execute_dotted - - - - - -experiment_data_n->experiment_execute_n - - - - - -multiexperiment_data - - - -Dataset - - - -multiexperiment_data->multiexperiment_execute - - - - - -multiexperiment_executor_experiment_data->multiexperiment_analyzer_execute - - - - - -multiexperiment_executor_result_data->multiexperiment_executor_execute - - -n times - - - -spliter_data - - - -Dataset - - - -spliter_data->spliter_execute - - - - - -stat_data - -Dataset - -stat target column - - - -stat_data->stat_execute - - - - - -analyzer_in_data - - - -ExperimentData - - - -analyzer_in_data->analyzer_execute - - - - - -transformer_in_data - - - -Dataset - - - -transformer_in_data->transformer_execute - - - - - -operator_in_data - -Dataset - -x1 - -x2 - - - -operator_in_data->operator_execute - - - - - -report_in_data - - - -ExperimentData - - - -report_in_data->report_execute - - - - - -experiment_executor_list_attribute->experiment_legend - - - - -report_backend - -Backend - - - -report_backend->report_experiment - - - - diff --git a/schemes/class inheritance scheme.svg b/schemes/class inheritance scheme.svg deleted file mode 100644 index 64f1d45d..00000000 --- a/schemes/class inheritance scheme.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Excecutor
AASplitter
Calculator
ABC
DatasetBase
ExperimentData
Dataset
DatasetBackendNavigation
Experiment
IfExecutor
MatchingAnalyzer
ABAnalyzer
OneAAStatAnalyzer
AAScoreAnalyzer
ExperimentShell
AATest
Matchint
HomegeneityTest
ABTest
ExperimentWIthReporter
OnRoleExperiment
IfAAExecutor
Transformer
Shuffle
GroupOperator
MLExcecutor
Encoder
Comparator
MahalanobisDistance
StatHypothesisTesting
GroupDifference
GroupSizes
PSI
Chi2Test
UTest
KSTest
TTest
TestPower
MDEBySize
DummyEncoder
ParamsExperiment
CycledExperiment
GroupExperiment
IfParamsExperiment
Extension
CompareExtension
MLExtension
DummyEncoderExtension
FiasExtension
CholeskyExtension
InverseExtension
StatTest
Chi2TestExtension
UTestExtension
KSTestExtension
TTestExtension
NormCDF
MultiTest
MultiTestQuantile
FaissNearestNeighbors
Bias
MatchingMetrics
SMD
AASplitterWithStratificattion
Reporter
DictReporter
OnDictReporter
DatasetReporter
OneAADictReporter
AADatasetReporter
AAParsedReporter
AABestSplitReporter
ABDictReporter
ABDatasetReporter
HomoDictReporter
HomoDatasetReporter
MatchingDictReporter
MatchingDatasetReporter
CategoryAggregator
OutliersFilter
CorrFilter
NanFIlter
ConstFilter
CVFilter
NaFiller
Ouput
AAOutput
ABOutput
HomoOutput
MatchingOutput
\ No newline at end of file diff --git a/schemes/class_path.dot b/schemes/class_path.dot deleted file mode 100644 index ff27c479..00000000 --- a/schemes/class_path.dot +++ /dev/null @@ -1,170 +0,0 @@ -digraph ClassPath{ - compound=true - - subgraph abstracts{ - node [shape=egg style=filled fillcolor=silver] - abstract_class[label="abstract"] - abstract_object[label="abstract object"] - - Executor - AbstractHypothesis - } - - subgraph modules{ - node [shape=box style=filled fillcolor=cornflowerblue] - module - factory_legend[label="Factory"] - created_object[label="created object"] - inner_object[label="inner object"] - implementation - - - Dataset - Factory - Experiment - Pipeline - Hypothesis - - // -------------------------------------- - Matcher - ReportMatcher [label="Report"] - TransformerMatchingValidation [label="Transformer"] - MatherMatchingValidation [label="Matcher"] - ReportMatchingValidation [label="Report"] - // -------------------------------------- - SpliterAAOne[label="Spliter"] - ReportAAOneSplit [label="Report"] - ReportAABest [label="Report"] - - } - - subgraph pipelines{ - node [shape=box3d, style=filled, fillcolor=violet] - pipeline - complex_object[label="complex object"] - - // -------------------------------------- - SelectorsMatching[label="Selectors"] - StatsMatcher[label="Stats"] - MetricsMatcher[label="Metrics"] - ValidationMatching[label="Validation"] - AnalyzerMatching[label="Analyzer"] - MetricMatchingValidation [label="Metric"] - StatsMatchingValidation [label="Stats"] - // -------------------------------------- - StatsAA[label="Stats"] - SpliterAAPipeline[label="Spliter"] - AnalyzerAASplit[label="Analyzer"] - MetricsAASplit[label="Metrics"] - StatsAASplit[label="Stats"] - AnalyzerAA[label="Analyzer"] - // -------------------------------------- - SelectorAB[label="Selector"] - MetricsAB[label="Metrics"] - StatsAB[label="Stats"] - AnalyzerAB[label="Analyzer"] - ReportAB[label="Report"] - } - - subgraph cluster_legend{ - graph[label="legend", style=filled, fillcolor=whitesmoke] - - subgraph cluster_nodes{ - graph[label="nodes", style=dashed] - abstract_class - module - pipeline - } - - subgraph cluster_edges{ - graph[label="edges", style=dashed] - - factory_legend -> created_object [arrowhead=crow, label="create"] - inner_object -> complex_object [arrowhead=box, label="in"] - abstract_object -> implementation [arrowhead=curve, label="implement"] - } - - - - } - -// ----------------------------------------------------------------------------------- -// ----------------------------------------------------------------------------------- - - - Executor -> Pipeline [arrowhead=curved] - AbstractHypothesis -> Hypothesis [arrowhead=curved] - - Hypothesis -> Factory [arrowhead=box] - Factory -> Experiment [arrowhead=crow label="object"] - Factory -> Pipeline [arrowhead=crow label="object"] - - subgraph cluster_experiment{ - graph[label="experiment structure" style=dotted] - Pipeline -> Experiment [arrowhead=box] - Dataset -> Experiment [arrowhead=box] - } - -// ----------------------------------------------------------------------------------- - - subgraph cluster_matcher{ - graph[label="Matcher process", style=dashed, style=filled, fillcolor=mistyrose] - - SelectorsMatching -> Matcher - Matcher -> MetricsMatcher - Matcher -> StatsMatcher - Matcher -> ValidationMatching - { - MetricsMatcher - StatsMatcher - ValidationMatching - } -> ReportMatcher - - subgraph cluster_matching_validation{ - graph[label="Validation process" style=dotted] - TransformerMatchingValidation -> MatherMatchingValidation - MatherMatchingValidation -> - {MetricMatchingValidation StatsMatchingValidation} -> ReportMatchingValidation - ReportMatchingValidation -> TransformerMatchingValidation [label="n times"] - } - TransformerMatchingValidation -> ValidationMatching [ltail=cluster_matching_validation, arrowhead=box] - ReportMatcher -> AnalyzerMatching - } - - Experiment -> SelectorsMatching [lhead=cluster_matcher] - -// ----------------------------------------------------------------------------------- - - subgraph cluster_AA{ - graph[label="AA process", style=dashed, style=filled, fillcolor=lavender] - - subgraph cluster_split{ - graph[label="Split process" style=dotted] - SpliterAAOne -> StatsAA -> ReportAAOneSplit - ReportAAOneSplit -> SpliterAAOne [label="n times"] - } - - SpliterAAOne -> SpliterAAPipeline [arrowhead=box ltail=cluster_split] - SpliterAAPipeline -> AnalyzerAASplit - AnalyzerAASplit -> {MetricsAASplit StatsAASplit} - {MetricsAASplit StatsAASplit} -> ReportAABest - ReportAABest -> AnalyzerAA - } - - Experiment -> SpliterAAPipeline [lhead=cluster_AA] - -// ----------------------------------------------------------------------------------- - - subgraph cluster_AB{ - graph[label="AB process", style=dashed, style=filled, fillcolor=honeydew] - - SelectorAB -> MetricsAB - SelectorAB -> StatsAB - {MetricsAB StatsAB} -> AnalyzerAB - AnalyzerAB -> ReportAB - - } - - Experiment -> SelectorAB [lhead=cluster_AB] - -} \ No newline at end of file diff --git a/schemes/class_path.svg b/schemes/class_path.svg deleted file mode 100644 index 31d8e0bc..00000000 --- a/schemes/class_path.svg +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - -ClassPath - - -cluster_AA - -AA process - - -cluster_split - -Split process - - -cluster_legend - -legend - - -cluster_nodes - -nodes - - -cluster_edges - -edges - - -cluster_matcher - -Matcher process - - -cluster_matching_validation - -Validation process - - -cluster_AB - -AB process - - -cluster_experiment - -experiment structure - - - -abstract_class - -abstract - - - -abstract_object - -abstract object - - - -implementation - -implementation - - - -abstract_object->implementation - - - -implement - - - -Executor - -Executor - - - -Pipeline - -Pipeline - - - -Executor->Pipeline - - - - - - -AbstractHypothesis - -AbstractHypothesis - - - -Hypothesis - -Hypothesis - - - -AbstractHypothesis->Hypothesis - - - - - - -module - -module - - - -factory_legend - -Factory - - - -created_object - -created object - - - -factory_legend->created_object - - -create - - - -inner_object - -inner object - - - -complex_object - - - - -complex object - - - -inner_object->complex_object - - - -in - - - -Dataset - -Dataset - - - -Experiment - -Experiment - - - -Dataset->Experiment - - - - - - -Factory - -Factory - - - -Factory->Experiment - - -object - - - -Factory->Pipeline - - -object - - - -SelectorsMatching - - - - -Selectors - - - -Experiment->SelectorsMatching - - - - - -SpliterAAPipeline - - - - -Spliter - - - -Experiment->SpliterAAPipeline - - - - - -SelectorAB - - - - -Selector - - - -Experiment->SelectorAB - - - - - -Pipeline->Experiment - - - - - - -Hypothesis->Factory - - - - - - -Matcher - -Matcher - - - -StatsMatcher - - - - -Stats - - - -Matcher->StatsMatcher - - - - - -MetricsMatcher - - - - -Metrics - - - -Matcher->MetricsMatcher - - - - - -ValidationMatching - - - - -Validation - - - -Matcher->ValidationMatching - - - - - -ReportMatcher - -Report - - - -AnalyzerMatching - - - - -Analyzer - - - -ReportMatcher->AnalyzerMatching - - - - - -TransformerMatchingValidation - -Transformer - - - -MatherMatchingValidation - -Matcher - - - -TransformerMatchingValidation->MatherMatchingValidation - - - - - -TransformerMatchingValidation->ValidationMatching - - - - - - -MetricMatchingValidation - - - - -Metric - - - -MatherMatchingValidation->MetricMatchingValidation - - - - - -StatsMatchingValidation - - - - -Stats - - - -MatherMatchingValidation->StatsMatchingValidation - - - - - -ReportMatchingValidation - -Report - - - -ReportMatchingValidation->TransformerMatchingValidation - - -n times - - - -SpliterAAOne - -Spliter - - - -StatsAA - - - - -Stats - - - -SpliterAAOne->StatsAA - - - - - -SpliterAAOne->SpliterAAPipeline - - - - - - -ReportAAOneSplit - -Report - - - -ReportAAOneSplit->SpliterAAOne - - -n times - - - -ReportAABest - -Report - - - -AnalyzerAA - - - - -Analyzer - - - -ReportAABest->AnalyzerAA - - - - - -pipeline - - - - -pipeline - - - -SelectorsMatching->Matcher - - - - - -StatsMatcher->ReportMatcher - - - - - -MetricsMatcher->ReportMatcher - - - - - -ValidationMatching->ReportMatcher - - - - - -MetricMatchingValidation->ReportMatchingValidation - - - - - -StatsMatchingValidation->ReportMatchingValidation - - - - - -StatsAA->ReportAAOneSplit - - - - - -AnalyzerAASplit - - - - -Analyzer - - - -SpliterAAPipeline->AnalyzerAASplit - - - - - -MetricsAASplit - - - - -Metrics - - - -AnalyzerAASplit->MetricsAASplit - - - - - -StatsAASplit - - - - -Stats - - - -AnalyzerAASplit->StatsAASplit - - - - - -MetricsAASplit->ReportAABest - - - - - -StatsAASplit->ReportAABest - - - - - -MetricsAB - - - - -Metrics - - - -SelectorAB->MetricsAB - - - - - -StatsAB - - - - -Stats - - - -SelectorAB->StatsAB - - - - - -AnalyzerAB - - - - -Analyzer - - - -MetricsAB->AnalyzerAB - - - - - -StatsAB->AnalyzerAB - - - - - -ReportAB - - - - -Report - - - -AnalyzerAB->ReportAB - - - - - diff --git a/schemes/hierarchy.dot b/schemes/hierarchy.dot new file mode 100644 index 00000000..4c92dff4 --- /dev/null +++ b/schemes/hierarchy.dot @@ -0,0 +1,876 @@ +digraph HypExInheritance { + rankdir=TB + node[shape=record, style="filled,rounded", fontsize=10] + edge[fontsize=9] + compound=true + + // Color scheme + // Base classes - light blue + // Abstract intermediate - light yellow + // Concrete implementations - light green + // Special/Complex - light pink + + // ========== EXECUTOR HIERARCHY ========== + subgraph cluster_executor { + label="Executor Hierarchy" + style="dashed,rounded" + bgcolor="#f0f0f0" + + // ========== BASE EXECUTOR CLASSES ========== + subgraph cluster_base_executors { + label="Core Executor Classes" + style="filled,rounded" + bgcolor="#e8f4f8" + + // Base Executor + Executor [ + fillcolor=lightblue + label="{ Executor | + + id: str\l + + key: Any\l + + params_hash: str\l | + + execute(data): ExperimentData\l + + set_params(params): void\l + + _generate_id(): void\l + }" + ] + + // Calculator branch + Calculator [ + fillcolor=lightyellow + label="{ «abstract» Calculator | | + + calc(data, **kwargs): Any\l + + _inner_function(data, **kwargs): Any\l + }" + ] + + // IfExecutor branch + IfExecutor [ + fillcolor=lightyellow + label="{ IfExecutor | + + if_executor: Executor\l + + else_executor: Executor\l | + + check_rule(data): bool\l + Conditional execution\l + }" + ] + + IfAAExecutor [ + fillcolor=lightgreen + label="{ IfAAExecutor | + + sample_size: float\l | + Checks AA test pass\l + }" + ] + + // MLExecutor branch + MLExecutor [ + fillcolor=lightyellow + label="{ «abstract» MLExecutor | + + grouping_role: ABCRole\l + + target_role: ABCRole\l | + + fit(X, Y): MLExecutor\l + + predict(X): Dataset\l + + score(X, Y): float\l + }" + ] + } + + // ========== COMPARATORS ========== + subgraph cluster_comparators { + label="Comparators" + style="filled,rounded" + bgcolor="#f8e8e8" + + Comparator [ + fillcolor=lightyellow + label="{ «abstract» Comparator | + + compare_by: str\l + + grouping_role: ABCRole\l + + target_roles: ABCRole\l + + baseline_role: ABCRole\l | + + _inner_function(data, test_data): dict\l + }" + ] + + // Basic comparators + subgraph cluster_basic_comparators { + label="Basic Metrics" + style="dashed" + + GroupDifference [ + fillcolor=lightgreen + label="{ GroupDifference | | + Calculates:\l + • control/test mean\l + • difference\l + • difference %\l + }" + ] + + GroupSizes [ + fillcolor=lightgreen + label="{ GroupSizes | | + Calculates:\l + • control/test size\l + • size percentages\l + }" + ] + + PSI [ + fillcolor=lightgreen + label="{ PSI | | + Population\l + Stability Index\l + }" + ] + + MahalanobisDistance [ + fillcolor=lightgreen + label="{ MahalanobisDistance | | + Calculates Mahalanobis\l + distance between groups\l + }" + ] + } + + // Statistical tests + subgraph cluster_stat_tests { + label="Statistical Tests" + style="dashed" + + StatHypothesisTesting [ + fillcolor=lightyellow + label="{ «abstract» StatHypothesisTesting | + + reliability: float\l | + Statistical tests base\l + }" + ] + + TTest [fillcolor=lightgreen label="{ TTest | | Student's t-test }"] + KSTest [fillcolor=lightgreen label="{ KSTest | | Kolmogorov-Smirnov test }"] + UTest [fillcolor=lightgreen label="{ UTest | | Mann-Whitney U test }"] + Chi2Test [fillcolor=lightgreen label="{ Chi2Test | + matrix_preparation(): void\l | Chi-squared test }"] + } + + // Power testing + subgraph cluster_power_testing { + label="Power Analysis" + style="dashed" + + PowerTesting [ + fillcolor=lightyellow + label="{ «abstract» PowerTesting | + + significance: float = 0.95\l + + power: float = 0.8\l | + Power analysis base\l + }" + ] + + MDEBySize [ + fillcolor=lightgreen + label="{ MDEBySize | | + Minimum Detectable Effect\l + by sample size\l + }" + ] + } + } + + // ========== SPLITTERS ========== + subgraph cluster_splitters { + label="Splitters" + style="filled,rounded" + bgcolor="#e8f8e8" + + // Note: No abstract Splitter class in actual code + AASplitter [ + fillcolor=lightgreen + label="{ AASplitter | + + control_size: float\l + + random_state: int\l + + sample_size: float\l + + constant_key: bool\l + + save_groups: bool\l | + + init_from_hash(hash): void\l + A/A test splitter\l + Uses AdditionalTreatmentRole\l + }" + ] + + AASplitterWithStratification [ + fillcolor=lightgreen + label="{ AASplitterWithStratification | | + + Uses StratificationRole\l + for grouped splitting\l + }" + ] + } + + // ========== TRANSFORMERS & ENCODERS ========== + subgraph cluster_transformers { + label="Transformers & Encoders" + style="filled,rounded" + bgcolor="#f8f8e8" + + Transformer [ + fillcolor=lightyellow + label="{ «abstract» Transformer | + + _is_transformer = True\l | + Transforms Dataset\l + }" + ] + + Shuffle [ + fillcolor=lightgreen + label="{ Shuffle | + + random_state: int\l | + Randomly shuffles data\l + }" + ] + + NaFiller [ + fillcolor=lightgreen + label="{ NaFiller | + + method: str\l + + values: Any\l | + Fills missing values\l + }" + ] + + CategoryAggregator [ + fillcolor=lightgreen + label="{ CategoryAggregator | + + threshold: int\l + + new_group_name: str\l | + Aggregates rare categories\l + }" + ] + + // Filters + ConstFilter [ + fillcolor=lightgreen + label="{ ConstFilter | + + threshold: float\l | + Filters constant columns\l + }" + ] + + CorrFilter [ + fillcolor=lightgreen + label="{ CorrFilter | + + threshold: float\l + + method: str\l | + Filters correlated columns\l + }" + ] + + CVFilter [ + fillcolor=lightgreen + label="{ CVFilter | + + lower_bound: float\l + + upper_bound: float\l | + Coefficient of variation filter\l + }" + ] + + NanFilter [ + fillcolor=lightgreen + label="{ NanFilter | + + threshold: float\l | + Filters columns with NaNs\l + }" + ] + + OutliersFilter [ + fillcolor=lightgreen + label="{ OutliersFilter | + + lower_percentile: float\l + + upper_percentile: float\l | + Filters outliers\l + }" + ] + + Encoder [ + fillcolor=lightyellow + label="{ «abstract» Encoder | + + target_roles: Sequence[str]\l | + Encodes categorical data\l + }" + ] + + DummyEncoder [ + fillcolor=lightgreen + label="{ DummyEncoder | | + One-hot encoding\l + }" + ] + } + + // ========== GROUP OPERATORS ========== + subgraph cluster_operators { + label="Group Operators" + style="filled,rounded" + bgcolor="#e8e8f8" + + GroupOperator [ + fillcolor=lightyellow + label="{ «abstract» GroupOperator | + + grouping_role: ABCRole\l + + target_roles: list[ABCRole]\l | + Operations on groups\l + }" + ] + + SMD [ + fillcolor=lightgreen + label="{ SMD | | + Standardized\l + Mean Difference\l + }" + ] + + Bias [ + fillcolor=lightgreen + label="{ Bias | | + + calc_coefficients(X, Y): list\l + + calc_bias(X, X_matched, coef): list\l + Bias estimation\l + }" + ] + + MatchingMetrics [ + fillcolor=lightgreen + label="{ MatchingMetrics | + + metric: str (atc/att/ate)\l | + Calculates treatment effects:\l + ATT, ATC, ATE metrics\l + }" + ] + } + + // ========== ANALYZERS ========== + subgraph cluster_analyzers { + label="Analyzers" + style="filled,rounded" + bgcolor="#f0e8f8" + + // Note: Analyzers inherit directly from Executor + OneAAStatAnalyzer [ + fillcolor=lightgreen + label="{ OneAAStatAnalyzer | | + Analyzes single\l + A/A test statistics\l + Inherits from Executor\l + }" + ] + + AAScoreAnalyzer [ + fillcolor=lightgreen + label="{ AAScoreAnalyzer | + + threshold: float\l + + alpha: float\l | + Scores A/A tests\l + Finds best split\l + Inherits from Executor\l + }" + ] + + ABAnalyzer [ + fillcolor=lightgreen + label="{ ABAnalyzer | + + multitest_method: ABNTestMethodsEnum\l + + alpha: float\l + + equal_variance: bool\l + + quantiles: float\l + + iteration_size: int\l | + A/B test analyzer\l + Multiple testing correction\l + Inherits from Executor\l + }" + ] + + MatchingAnalyzer [ + fillcolor=lightgreen + label="{ MatchingAnalyzer | | + Analyzes matching\l + quality and metrics\l + Inherits from Executor\l + }" + ] + } + + // ========== ML EXECUTORS ========== + subgraph cluster_ml_executors { + label="ML Executors" + style="filled,rounded" + bgcolor="#f8e8f8" + + FaissNearestNeighbors [ + fillcolor=lightgreen + label="{ FaissNearestNeighbors | + + n_neighbors: int\l + + two_sides: bool\l + + test_pairs: bool\l + + faiss_mode: str\l | + Finds nearest neighbors\l + Uses FAISS library\l + }" + ] + } + } + + // ========== EXPERIMENT HIERARCHY ========== + subgraph cluster_experiment { + label="Experiment Hierarchy" + style="dashed,rounded" + bgcolor="#f5f5f0" + + Experiment [ + fillcolor=lightblue + label="{ Experiment | + + executors: list[Executor]\l + + transformer: bool\l + + key: str\l | + + execute(data): ExperimentData\l + + set_params(params): void\l + Chains executors\l + }" + ] + + OnRoleExperiment [ + fillcolor=lightgreen + label="{ OnRoleExperiment | + + role: list[ABCRole]\l | + Executes on specific role\l + }" + ] + + ExperimentWithReporter [ + fillcolor=lightyellow + label="{ ExperimentWithReporter | + + reporter: Reporter\l | + + one_iteration(data): Dataset\l + Adds reporting capability\l + }" + ] + + CycledExperiment [ + fillcolor=lightgreen + label="{ CycledExperiment | + + n_iterations: int\l | + Runs n iterations\l + }" + ] + + GroupExperiment [ + fillcolor=lightgreen + label="{ GroupExperiment | + + searching_role: ABCRole\l | + Runs on groups\l + }" + ] + + ParamsExperiment [ + fillcolor=lightgreen + label="{ ParamsExperiment | + + params: dict\l + + flat_params: list\l | + Parameterized runs\l + }" + ] + + IfParamsExperiment [ + fillcolor=lightgreen + label="{ IfParamsExperiment | + + stopping_criterion: IfExecutor\l | + Conditional params\l + with early stopping\l + }" + ] + } + + // ========== REPORTER HIERARCHY ========== + subgraph cluster_reporter { + label="Reporter Hierarchy" + style="dashed,rounded" + bgcolor="#f0f5f0" + + Reporter [ + fillcolor=lightblue + label="{ «abstract» Reporter | | + + report(data): Any\l + }" + ] + + DictReporter [ + fillcolor=lightyellow + label="{ «abstract» DictReporter | + + front: bool\l | + Returns dict results\l + }" + ] + + OnDictReporter [ + fillcolor=lightyellow + label="{ «abstract» OnDictReporter | + + dict_reporter: DictReporter\l | + Wraps dict reporter\l + }" + ] + + DatasetReporter [ + fillcolor=lightgreen + label="{ DatasetReporter | | + Converts dict to Dataset\l + }" + ] + + TestDictReporter [ + fillcolor=lightyellow + label="{ «abstract» TestDictReporter | + + tests: list\l | + Extracts test results\l + }" + ] + + OneAADictReporter [ + fillcolor=lightgreen + label="{ OneAADictReporter | | + Reports A/A test results\l + }" + ] + + AADatasetReporter [ + fillcolor=lightgreen + label="{ AADatasetReporter | | + A/A results as Dataset\l + }" + ] + + ABDictReporter [ + fillcolor=lightgreen + label="{ ABDictReporter | | + Reports A/B test results\l + }" + ] + + ABDatasetReporter [ + fillcolor=lightgreen + label="{ ABDatasetReporter | | + A/B results as Dataset\l + }" + ] + + HomoDictReporter [ + fillcolor=lightgreen + label="{ HomoDictReporter | | + Homogeneity test results\l + }" + ] + + HomoDatasetReporter [ + fillcolor=lightgreen + label="{ HomoDatasetReporter | | + Homogeneity as Dataset\l + }" + ] + + AAPassedReporter [ + fillcolor=lightgreen + label="{ AAPassedReporter | | + Reports passed A/A tests\l + Detects which tests passed\l + }" + ] + + AABestSplitReporter [ + fillcolor=lightgreen + label="{ AABestSplitReporter | | + Reports best split\l + Adds split column to data\l + }" + ] + + MatchingDictReporter [ + fillcolor=lightgreen + label="{ MatchingDictReporter | + + searching_class: type\l | + Matching results dict\l + }" + ] + + MatchingQualityDictReporter [ + fillcolor=lightgreen + label="{ MatchingQualityDictReporter | | + Matching quality dict\l + }" + ] + + MatchingDatasetReporter [ + fillcolor=lightgreen + label="{ MatchingDatasetReporter | | + Matching as Dataset\l + }" + ] + + MatchingQualityDatasetReporter [ + fillcolor=lightgreen + label="{ MatchingQualityDatasetReporter | | + Quality as Dataset\l + }" + ] + } + + // ========== UI/OUTPUT HIERARCHY ========== + subgraph cluster_ui { + label="UI/Output Classes" + style="dashed,rounded" + bgcolor="#f5f0f5" + + Output [ + fillcolor=lightpink + label="{ Output | + + resume: Dataset\l + + resume_reporter: Reporter\l + + additional_reporters: dict\l | + + extract(data): void\l + Formats experiment output\l + }" + ] + + AAOutput [ + fillcolor=lightpink + label="{ AAOutput | + + best_split: Dataset\l + + experiments: Dataset\l + + aa_score: Dataset\l + + best_split_statistic: Dataset\l | + A/A test output\l + }" + ] + + ABOutput [ + fillcolor=lightpink + label="{ ABOutput | + + multitest: Dataset | str\l + + sizes: Dataset\l | + A/B test output with\l + multiple testing correction\l + }" + ] + + HomoOutput [ + fillcolor=lightpink + label="{ HomoOutput | + + resume: Dataset\l | + Homogeneity test output\l + }" + ] + + MatchingOutput [ + fillcolor=lightpink + label="{ MatchingOutput | + + full_data: Dataset\l + + quality_results: Dataset\l + + indexes: Dataset\l | + Matching analysis output\l + }" + ] + + ExperimentShell [ + fillcolor=lightpink + label="{ ExperimentShell | + + experiment: Experiment\l + + output: Output\l | + + execute(data): Output\l + UI wrapper for experiments\l + }" + ] + + AATest [ + fillcolor=lightpink + label="{ AATest | + + precision_mode: bool\l + + control_size: float\l + + stratification: bool\l + + n_iterations: int\l + + sample_size: float\l | + A/A test shell\l + with configuration\l + }" + ] + + ABTest [ + fillcolor=lightpink + label="{ ABTest | + + additional_tests: list[str]\l + + multitest_method: str\l | + A/B test shell with\l + configurable tests\l + }" + ] + + HomogeneityTest [ + fillcolor=lightpink + label="{ HomogeneityTest | | + Homogeneity test shell\l + Uses predefined experiment\l + }" + ] + + Matching [ + fillcolor=lightpink + label="{ Matching | + + group_match: bool\l + + distance: str\l + + metric: str\l + + bias_estimation: bool\l + + quality_tests: list\l | + Matching analysis shell\l + }" + ] + } + + // ========== INHERITANCE RELATIONSHIPS ========== + // Solid arrows for inheritance + Executor -> Calculator [arrowhead=empty] + Executor -> IfExecutor [arrowhead=empty] + Executor -> OneAAStatAnalyzer [arrowhead=empty] + Executor -> AAScoreAnalyzer [arrowhead=empty] + Executor -> ABAnalyzer [arrowhead=empty] + Executor -> MatchingAnalyzer [arrowhead=empty] + + Calculator -> Comparator [arrowhead=empty] + Calculator -> AASplitter [arrowhead=empty] + Calculator -> Transformer [arrowhead=empty] + Calculator -> Encoder [arrowhead=empty] + Calculator -> GroupOperator [arrowhead=empty] + Calculator -> MLExecutor [arrowhead=empty] + + Comparator -> GroupDifference [arrowhead=empty] + Comparator -> GroupSizes [arrowhead=empty] + Comparator -> PSI [arrowhead=empty] + Comparator -> MahalanobisDistance [arrowhead=empty] + Comparator -> StatHypothesisTesting [arrowhead=empty] + Comparator -> PowerTesting [arrowhead=empty] + + PowerTesting -> MDEBySize [arrowhead=empty] + + StatHypothesisTesting -> TTest [arrowhead=empty] + StatHypothesisTesting -> KSTest [arrowhead=empty] + StatHypothesisTesting -> UTest [arrowhead=empty] + StatHypothesisTesting -> Chi2Test [arrowhead=empty] + + AASplitter -> AASplitterWithStratification [arrowhead=empty] + + Transformer -> Shuffle [arrowhead=empty] + Transformer -> NaFiller [arrowhead=empty] + Transformer -> CategoryAggregator [arrowhead=empty] + Transformer -> ConstFilter [arrowhead=empty] + Transformer -> CorrFilter [arrowhead=empty] + Transformer -> CVFilter [arrowhead=empty] + Transformer -> NanFilter [arrowhead=empty] + Transformer -> OutliersFilter [arrowhead=empty] + + Encoder -> DummyEncoder [arrowhead=empty] + + GroupOperator -> SMD [arrowhead=empty] + GroupOperator -> Bias [arrowhead=empty] + GroupOperator -> MatchingMetrics [arrowhead=empty] + + IfExecutor -> IfAAExecutor [arrowhead=empty] + + MLExecutor -> FaissNearestNeighbors [arrowhead=empty] + + Experiment -> OnRoleExperiment [arrowhead=empty] + Experiment -> ExperimentWithReporter [arrowhead=empty] + + ExperimentWithReporter -> CycledExperiment [arrowhead=empty] + ExperimentWithReporter -> ParamsExperiment [arrowhead=empty] + ExperimentWithReporter -> GroupExperiment [arrowhead=empty] + ParamsExperiment -> IfParamsExperiment [arrowhead=empty] + + Reporter -> DictReporter [arrowhead=empty] + Reporter -> OnDictReporter [arrowhead=empty] + Reporter -> AAPassedReporter [arrowhead=empty] + Reporter -> AABestSplitReporter [arrowhead=empty] + + OnDictReporter -> DatasetReporter [arrowhead=empty] + + DictReporter -> TestDictReporter [arrowhead=empty] + DictReporter -> MatchingDictReporter [arrowhead=empty] + + TestDictReporter -> OneAADictReporter [arrowhead=empty] + TestDictReporter -> MatchingQualityDictReporter [arrowhead=empty] + + OneAADictReporter -> ABDictReporter [arrowhead=empty] + OneAADictReporter -> AADatasetReporter [arrowhead=empty] + OneAADictReporter -> HomoDictReporter [arrowhead=empty] + + ABDictReporter -> ABDatasetReporter [arrowhead=empty] + HomoDictReporter -> HomoDatasetReporter [arrowhead=empty] + MatchingQualityDictReporter -> MatchingQualityDatasetReporter [arrowhead=empty] + + DatasetReporter -> MatchingDatasetReporter [arrowhead=empty] + + Output -> AAOutput [arrowhead=empty] + Output -> ABOutput [arrowhead=empty] + Output -> HomoOutput [arrowhead=empty] + Output -> MatchingOutput [arrowhead=empty] + + ExperimentShell -> AATest [arrowhead=empty] + ExperimentShell -> ABTest [arrowhead=empty] + ExperimentShell -> HomogeneityTest [arrowhead=empty] + ExperimentShell -> Matching [arrowhead=empty] + + // ========== COMPOSITION/USAGE RELATIONSHIPS ========== + // Dashed arrows for composition/usage + Experiment -> Executor [style=dashed, label="uses list of", arrowhead=open] + ExperimentWithReporter -> Reporter [style=dashed, label="has", arrowhead=open] + DatasetReporter -> DictReporter [style=dashed, label="wraps", arrowhead=open] + OnDictReporter -> DictReporter [style=dashed, label="wraps", arrowhead=open] + Output -> Reporter [style=dashed, label="uses", arrowhead=open] + ExperimentShell -> Experiment [style=dashed, label="wraps", arrowhead=open] + ExperimentShell -> Output [style=dashed, label="has", arrowhead=open] + IfExecutor -> Executor [style=dashed, label="contains 2", arrowhead=open] + IfParamsExperiment -> IfExecutor [style=dashed, label="has stopping\ncriterion", arrowhead=open] + MatchingDatasetReporter -> MatchingDictReporter [style=dashed, label="uses", arrowhead=open] + FaissNearestNeighbors -> MahalanobisDistance [style=dashed, label="may use", arrowhead=open] + AAScoreAnalyzer -> AASplitter [style=dashed, label="rebuilds from id", arrowhead=open] + + // Main user classes usage + ABTest -> ABAnalyzer [style=dashed, label="uses", arrowhead=open] + ABTest -> OnRoleExperiment [style=dashed, label="creates", arrowhead=open] + HomogeneityTest -> OneAAStatAnalyzer [style=dashed, label="uses", arrowhead=open] + AATest -> ParamsExperiment [style=dashed, label="creates", arrowhead=open] + AATest -> IfParamsExperiment [style=dashed, label="may create", arrowhead=open] + AATest -> AAScoreAnalyzer [style=dashed, label="uses", arrowhead=open] + Matching -> FaissNearestNeighbors [style=dashed, label="uses", arrowhead=open] + Matching -> MatchingMetrics [style=dashed, label="uses", arrowhead=open] + Matching -> MatchingAnalyzer [style=dashed, label="uses", arrowhead=open] + + // Legend + subgraph cluster_legend { + label="Legend" + style="filled,rounded" + bgcolor="#fafafa" + + // Arrow types examples + node[shape=box, style=filled, fillcolor=white] + inherit_from [label="Parent"] + inherit_to [label="Child"] + inherit_from -> inherit_to [arrowhead=empty, label="Inheritance"] + + compose_from [label="Container"] + compose_to [label="Component"] + compose_from -> compose_to [style=dashed, arrowhead=open, label="Composition/Uses"] + + // Color coding examples + node[shape=record, style="filled,rounded"] + base_example [fillcolor=lightblue, label="{ Base Class | Foundation }"] + abstract_example [fillcolor=lightyellow, label="{ «abstract» Class | Template }"] + concrete_example [fillcolor=lightgreen, label="{ Concrete Class | Implementation }"] + ui_example [fillcolor=lightpink, label="{ UI/Shell Class | User Interface }"] + } +} \ No newline at end of file