← Назад к вопросам

Как провести A/B-тест изменения дизайна кнопки "Купить"?

1.7 Middle🔥 151 комментариев
#Аналитика и метрики

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как провести A/B-тест изменения дизайна кнопки "Купить"

1. Задача Data Engineer в A/B тестировании

Data Engineer отвечает за инструментарий A/B теста:

  • Архитектура сбора данных (tracking)
  • Корректное распределение пользователей (randomization)
  • Расчёт статистики
  • Мониторинг качества данных
  • Pipeline анализа результатов

Это НЕ Data Scientist-ская задача. Data Engineer создаёт инструменты.

2. Дизайн эксперимента

Гипотеза: Красная кнопка купит выглядит агрессивнее и увеличит конверсию.

Метрика для оптимизации:

  • Первичная: Conversion rate = Orders / Sessions
  • Вторичные: Revenue per session, AOV, Button click rate

Допущения:

  • Duration: 2 недели (достаточно данных)
  • Sample size: 10,000 сессий в каждой группе (Control & Treatment)
  • Significance level: α = 0.05 (95% confidence)
  • Minimum detectable effect (MDE): 5% увеличение конверсии

3. Архитектура сбора данных

Первое — настроить tracking в приложении:

// Frontend: отправь событие с информацией о варианте
function trackButtonClick(buttonVariant) {
  fetch('/api/v1/events', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      event_type: 'button_click',
      experiment_name: 'buy_button_redesign',
      variant: buttonVariant,  // 'control' or 'treatment'
      user_id: getCurrentUserId(),
      session_id: getSessionId(),
      timestamp: new Date().toISOString(),
      page_url: window.location.href,
      button_color: buttonVariant === 'control' ? 'blue' : 'red'
    })
  });
}

// Вызовы в приложении
if (buttonVariant === 'control') {
  return <button className="bg-blue-500" onClick={() => trackButtonClick('control')}>Купить</button>;
} else {
  return <button className="bg-red-500" onClick={() => trackButtonClick('treatment')}>Купить</button>;
}

4. Распределение пользователей (Randomization)

Критично: рандомное, стабильное распределение.

# Backend: Определи вариант для пользователя
import hashlib

def get_experiment_variant(user_id: int, experiment_name: str) -> str:
    """
    Хеширование user_id для стабильного распределения.
    Если user_id = 123, всегда получит одинаковый вариант.
    """
    hash_val = int(
        hashlib.md5(f"{user_id}_{experiment_name}".encode()).hexdigest(),
        16
    )
    # 50% control, 50% treatment
    return 'treatment' if hash_val % 2 == 0 else 'control'

# Использование
@app.get('/api/v1/button-variant')
async def get_button_variant(user_id: int):
    variant = get_experiment_variant(user_id, 'buy_button_redesign')
    return {'variant': variant}

Важно: Хеширование обеспечивает:

  • Один пользователь → один вариант (стабильность)
  • Рандомное распределение (user_id % 2 ≈ 50/50)
  • Нет смещения (bias)

5. Сбор и хранение данных

Schema для events:

CREATE TABLE ab_test_events (
    event_id BIGINT PRIMARY KEY,
    experiment_name VARCHAR(255),
    variant VARCHAR(50),              -- 'control' or 'treatment'
    user_id BIGINT,
    session_id VARCHAR(255),
    event_type VARCHAR(100),          -- 'button_click', 'order'
    event_timestamp TIMESTAMPTZ,
    page_url TEXT,
    metadata JSONB,                   -- Доп данные (цвет, размер, position)
    
    UNIQUE (user_id, session_id),
    INDEX (experiment_name, variant, event_timestamp)
);

-- Таблица заказов
CREATE TABLE ab_test_orders (
    order_id BIGINT PRIMARY KEY,
    session_id VARCHAR(255),
    user_id BIGINT,
    variant VARCHAR(50),
    amount DECIMAL(10, 2),
    created_at TIMESTAMPTZ,
    
    FOREIGN KEY (session_id) REFERENCES ab_test_events(session_id)
);

6. Pipeline для аналитики результатов

Python скрипт (или Airflow DAG) для расчёта метрик:

import pandas as pd
from scipy import stats
from sqlalchemy import create_engine

engine = create_engine('postgresql://...')

def analyze_ab_test(experiment_name: str, start_date: str, end_date: str):
    """
    Рассчитай статистику A/B теста
    """
    
    # 1. Получи данные
    query = f"""
    WITH events_summary AS (
        SELECT 
            variant,
            COUNT(DISTINCT user_id) as unique_users,
            COUNT(DISTINCT session_id) as sessions,
            COUNT(*) FILTER (WHERE event_type = 'button_click') as button_clicks,
            COUNT(*) FILTER (WHERE event_type = 'page_view') as page_views
        FROM ab_test_events
        WHERE experiment_name = '{experiment_name}'
            AND event_timestamp >= '{start_date}'
            AND event_timestamp <= '{end_date}'
        GROUP BY variant
    ),
    orders_summary AS (
        SELECT 
            variant,
            COUNT(*) as orders,
            SUM(amount) as total_revenue,
            AVG(amount) as avg_order_value
        FROM ab_test_orders
        WHERE variant IN ('control', 'treatment')
            AND created_at >= '{start_date}'
            AND created_at <= '{end_date}'
        GROUP BY variant
    )
    SELECT 
        e.variant,
        e.unique_users,
        e.sessions,
        e.button_clicks,
        ROUND(e.button_clicks * 100.0 / e.sessions, 2) as ctr_pct,
        COALESCE(o.orders, 0) as orders,
        COALESCE(o.total_revenue, 0) as total_revenue,
        COALESCE(o.avg_order_value, 0) as aov
    FROM events_summary e
    LEFT JOIN orders_summary o ON e.variant = o.variant
    """
    
    df = pd.read_sql(query, engine)
    
    # 2. Рассчитай конверсии
    control = df[df['variant'] == 'control'].iloc[0]
    treatment = df[df['variant'] == 'treatment'].iloc[0]
    
    control_conversion = control['orders'] / control['sessions']
    treatment_conversion = treatment['orders'] / treatment['sessions']
    
    # 3. Статистический тест (Chi-squared для бинарных событий)
    contingency_table = [
        [control['orders'], control['sessions'] - control['orders']],
        [treatment['orders'], treatment['sessions'] - treatment['orders']]
    ]
    
    chi2, pvalue, dof, expected = stats.chi2_contingency(contingency_table)
    
    # 4. Относительный лифт
    relative_lift = (treatment_conversion - control_conversion) / control_conversion * 100
    
    # 5. Размер эффекта (Cohen's h)
    cohens_h = 2 * (math.asin(math.sqrt(treatment_conversion)) - 
                    math.asin(math.sqrt(control_conversion)))
    
    # 6. Результаты
    results = {
        'experiment': experiment_name,
        'control_conversion': f"{control_conversion * 100:.2f}%",
        'treatment_conversion': f"{treatment_conversion * 100:.2f}%",
        'relative_lift': f"{relative_lift:.2f}%",
        'pvalue': pvalue,
        'is_significant': pvalue < 0.05,
        'confidence': (1 - pvalue) * 100,
        'cohens_h': cohens_h,
        'sample_size_control': control['sessions'],
        'sample_size_treatment': treatment['sessions']
    }
    
    return results

# Запусти анализ
results = analyze_ab_test('buy_button_redesign', '2024-03-01', '2024-03-14')
print(results)

Вывод:

Control conversion: 2.50%
Treatment conversion: 2.65%
Relative lift: +6.0%
P-value: 0.023
Statistically significant: YES (95% confidence)

7. Мониторинг качества данных (Data Quality)

Во время теста проверяй:

-- Симметрия распределения
SELECT 
    variant,
    COUNT(DISTINCT user_id) as unique_users
FROM ab_test_events
GROUP BY variant;

-- Ожидается: примерно 50/50
-- Если 60/40 → может быть баг в распределении!

-- Проверь, нет ли дублей
SELECT 
    user_id,
    COUNT(DISTINCT variant) as variant_count
FROM ab_test_events
GROUP BY user_id
HAVING COUNT(DISTINCT variant) > 1;

-- Должно быть 0 (user принадлежит только одной группе)

-- Тренд по дням
SELECT 
    DATE(event_timestamp) as date,
    variant,
    COUNT(*) as events,
    COUNT(DISTINCT user_id) as users
FROM ab_test_events
GROUP BY DATE(event_timestamp), variant
ORDER BY date, variant;

-- Ищи аномалии (резкие скачки, упадки)

8. Шаг за шагом: процесс

День 1-7:

  • Развернул A/B тест код
  • Пользователи рандомно видят Control (синяя) или Treatment (красная) кнопку
  • Собираю события в БД

День 7:

  • Проверяю data quality
  • Уже видно тренд: есть ли отличие?

День 14:

  • Выполняю полный анализ
  • Рассчитываю p-value
  • Если p < 0.05 → результат значимый
  • Если лифт положительный → деплой Treatment
  • Если лифт отрицательный → откатываю на Control

9. Типичные ошибки

Не проверяй результаты до конца теста

  • Если смотришь день 5 из 14, можешь поймать lucky variance
  • Остановка теста раньше срока: Selection bias

Не смешивай пользователей между группами

  • user_id 123 видит Control
  • Пока тест идёт, он НЕ должен переходить на Treatment

Не переделай тест, если результат не нравится

  • Это p-hacking
  • Запланируй тест ДО запуска

Не жди статистической значимости, если sample size малый

  • Для 5% лифта с 2% baseline нужно ~50k пользователей
  • Если у тебя 1k → power теста ~ 30%, не увидишь настоящего эффекта

10. Код для Airflow DAG (автоматизация)

from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta

default_args = {'owner': 'data_eng', 'retries': 2}

dag = DAG(
    'ab_test_analysis',
    default_args=default_args,
    schedule_interval='@daily',
    start_date=datetime(2024, 3, 1)
)

def check_data_quality():
    # Валидация распределения, дублей, etc
    pass

def calculate_metrics():
    # Расчёт конверсий, лифтов, p-value
    pass

def alert_if_anomaly():
    # Отправь Slack алерт если что-то не так
    pass

check_task = PythonOperator(
    task_id='check_quality',
    python_callable=check_data_quality,
    dag=dag
)

calc_task = PythonOperator(
    task_id='calculate',
    python_callable=calculate_metrics,
    dag=dag
)

alert_task = PythonOperator(
    task_id='alert',
    python_callable=alert_if_anomaly,
    dag=dag
)

check_task >> calc_task >> alert_task

Заключение

Data Engineer в A/B тестировании создаёт инфраструктуру:

  1. Randomization — честное распределение (хеширование по user_id)
  2. Tracking — сбор событий с информацией о варианте
  3. Data Quality — проверка симметрии, дублей, аномалий
  4. Statistical Analysis — расчёт p-value, лифта, доверительных интервалов
  5. Automation — DAG для регулярного расчёта

Хороший Data Engineer обеспечивает надёжные результаты, плохой — сделает тест биасированным и компания примет неправильное решение.

Как провести A/B-тест изменения дизайна кнопки "Купить"? | PrepBro