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

Как оценишь эффективность рекламной кампании без A/B теста?

3.0 Senior🔥 161 комментариев
#Machine Learning#Атрибуция и маркетинг#Статистика и математика

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

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

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

Оценка эффективности рекламной кампании без A/B теста

Это реалистичный сценарий, когда полноценный A/B тест невозможен или нежелателен (нет контрольной группы, быстрые решения, технические ограничения, законодательство). В таких случаях аналитик использует наблюдательные методы и статистические техники для оценки ROI кампании.

Основные методы оценки без контрольной группы

Метод 1: Анализ временных рядов (Time-Series Analysis)

Сравниваем метрики ДО, ВО ВРЕМЯ и ПОСЛЕ кампании:

import pandas as pd
import numpy as np
from scipy import stats

# Данные по дням
data = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=90, freq='D'),
    'conversions': [100, 105, 103, 102, 98, 101, 99, 102, 100,  # До кампании (9 дней)
                    150, 155, 152, 148, 151, 153, 149, 154, 150,  # Во время (9 дней)
                    145, 142, 148, 140, 143, 141, 139, 144, 138] + 
                    [np.random.normal(100, 5) for _ in range(63)]  # После + остальное
})

# Разделяем на периоды
before_campaign = data['conversions'][:9]
during_campaign = data['conversions'][9:18]
after_campaign = data['conversions'][18:]

# Статистический анализ
mean_before = before_campaign.mean()
mean_during = during_campaign.mean()
mean_after = after_campaign.mean()

print(f"Среднее ДО кампании: {mean_before:.1f}")
print(f"Среднее ВРЕМЯ кампании: {mean_during:.1f}")
print(f"Среднее ПОСЛЕ кампании: {mean_after:.1f}")

# T-тест между до и во время
t_stat, p_value = stats.ttest_ind(before_campaign, during_campaign)
print(f"\nT-тест (До vs Во время): t={t_stat:.3f}, p-value={p_value:.4f}")

if p_value < 0.05:
    effect_size = mean_during - mean_before
    pct_change = (effect_size / mean_before) * 100
    print(f"✓ Статистически значимый эффект: +{effect_size:.1f} конверсий (+{pct_change:.1f}%)")
else:
    print(f"✗ Эффект не статистически значим")

Метод 2: Разностно-в-разностях анализ (Difference-in-Differences)

Сравниваем две коррелированные группы (сегменты):

  • Сегмент A: получил рекламу
  • Сегмент B (контроль): не получил рекламу
# Два сегмента: экспериментальный и контрольный
data_with_segment = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=60, freq='D'),
    'segment': ['exposed']*30 + ['control']*30,
    'conversions': ([100]*15 + [150]*15) +  # Exposed: до=100, во время=150
                   ([100]*15 + [105]*15)    # Control: стабильно ~105
})

# DiD вычисление
data_before = data_with_segment[data_with_segment['date'] < '2024-01-16']
data_after = data_with_segment[data_with_segment['date'] >= '2024-01-16']

exposed_before = data_before[data_before['segment'] == 'exposed']['conversions'].mean()
exposed_after = data_after[data_after['segment'] == 'exposed']['conversions'].mean()
control_before = data_before[data_before['segment'] == 'control']['conversions'].mean()
control_after = data_after[data_after['segment'] == 'control']['conversions'].mean()

# Эффект у экспериментальной группы
exposed_change = exposed_after - exposed_before

# Эффект у контрольной группы (baseline trend)
control_change = control_after - control_before

# Каузальный эффект кампании (Difference-in-Differences)
causal_effect = exposed_change - control_change

print(f"Изменение в экспериментальной группе: {exposed_change:.1f}")
print(f"Изменение в контрольной группе (baseline): {control_change:.1f}")
print(f"Каузальный эффект кампании (DiD): {causal_effect:.1f}")
print(f"Истинный ROI примерно на {causal_effect:.0f} конверсий")

Метод 3: Регрессионный прерывистый анализ (Regression Discontinuity)

Для кампаний с четкой датой начала:

from sklearn.linear_model import LinearRegression

# Данные до и после запуска кампании
days = np.arange(1, 61)  # 60 дней
campaign_start_day = 31

# Генерируем данные с скачком на день 31
trend = 100 + 0.2 * days  # линейный тренд
jump = np.where(days >= campaign_start_day, 20, 0)  # скачок на 20
noise = np.random.normal(0, 5, len(days))
conversions = trend + jump + noise

# Подгоняем регрессию
X = days.reshape(-1, 1)
y = conversions

model = LinearRegression()
model.fit(X, y)
y_pred = model.predict(X)

# Вычисляем скачок на точке прерыва
y_before = model.predict([[campaign_start_day - 0.5]])[0]
y_after = model.predict([[campaign_start_day + 0.5]])[0]
jump_effect = y_after - y_before

print(f"Базовый тренд: {model.coef_[0]:.3f} конверсий/день")
print(f"Скачок в конверсиях на день кампании: {jump_effect:.1f}")
print(f"Это соответствует эффекту кампании")

Метод 4: Propensity Score Matching (Matching пользователей)

Сравниваем пользователей, которые похожи ВСЕ ВО ВСЁМ кроме того, получили они рекламу или нет:

from sklearn.neighbors import NearestNeighbors

# Данные: пользователи, их характеристики, получили ли рекламу
users_data = pd.DataFrame({
    'user_id': range(1000),
    'age': np.random.normal(35, 10, 1000),
    'previous_purchases': np.random.poisson(5, 1000),
    'account_age_months': np.random.exponential(12, 1000),
    'received_ad': np.random.binomial(1, 0.3, 1000),
    'converted': np.random.binomial(1, 0.1, 1000) + 
                 np.random.binomial(1, 0.05, 1000)  # Ad тем, кто получил рекламу
})

# Разделяем на две группы
exposed = users_data[users_data['received_ad'] == 1]
control = users_data[users_data['received_ad'] == 0]

# Подбираем похожих пользователей по признакам
features = ['age', 'previous_purchases', 'account_age_months']
X_exposed = exposed[features].values
X_control = control[features].values

knn = NearestNeighbors(n_neighbors=1)
knn.fit(X_control)
distances, indices = knn.kneighbors(X_exposed)

# Сравниваем конверсию matched пар
matched_control = control.iloc[indices.flatten()]
conversion_exposed = exposed['converted'].mean()
conversion_control = matched_control['converted'].mean()

ate = conversion_exposed - conversion_control  # Average Treatment Effect

print(f"Конверсия (получившие рекламу): {conversion_exposed:.2%}")
print(f"Конверсия (контроль, matched): {conversion_control:.2%}")
print(f"Average Treatment Effect (ATE): {ate:.2%}")
print(f"Это оценка истинного эффекта рекламы")

Метод 5: Synthetic Control (Синтетический контроль)

Для кампаний в одном из нескольких географических регионов:

# Несколько регионов
regions_data = pd.DataFrame({
    'week': [1]*6 + [2]*6 + [3]*6,
    'region': ['region_a']*6 + ['region_b']*6 + ['region_c']*6,
    'conversions': [100, 102, 101, 99, 100, 101,      # Region A (контроль, стабильно)
                    100, 101, 99, 102, 100, 98,        # Region B (контроль)
                    100, 99, 101, 150, 152, 151],      # Region C (кампания)
})

# Синтетический контроль = взвешенное среднее других регионов
# которые ведут себя как region_c ДО кампании

data_pre = regions_data[regions_data['week'] <= 2]
data_post = regions_data[regions_data['week'] > 2]

# В неделе 3 для region_c запустили кампанию
region_c_post = data_post[data_post['region'] == 'region_c']['conversions'].mean()
control_post = data_post[data_post['region'] != 'region_c']['conversions'].mean()

effect = region_c_post - control_post

print(f"Region C после кампании: {region_c_post:.1f}")
print(f"Синтетический контроль: {control_post:.1f}")
print(f"Эффект кампании: {effect:.1f}")

Метод 6: Анализ атрибуции (Attribution Analysis)

Используя имеющиеся данные о пути пользователя:

# Данные по сессиям пользователей
sessions_data = pd.DataFrame({
    'session_id': range(100),
    'saw_ad': [True]*30 + [False]*70,  # Видели рекламу
    'converted': [True]*20 + [False]*10 +  # 20 из 30 конвертились
                 [True]*10 + [False]*60,   # 10 из 70 конвертились без рекламы
    'session_value': [50]*20 + [0]*10 + [50]*10 + [0]*60
})

# Простой анализ атрибуции
conv_with_ad = sessions_data[sessions_data['saw_ad'] == True]['converted'].sum()
total_with_ad = (sessions_data['saw_ad'] == True).sum()
conv_rate_with_ad = conv_with_ad / total_with_ad

conv_without_ad = sessions_data[sessions_data['saw_ad'] == False]['converted'].sum()
total_without_ad = (sessions_data['saw_ad'] == False).sum()
conv_rate_without_ad = conv_without_ad / total_without_ad

print(f"Конверсия с рекламой: {conv_rate_with_ad:.2%}")
print(f"Конверсия без рекламы: {conv_rate_without_ad:.2%}")
print(f"Относительный лифт: {(conv_rate_with_ad - conv_rate_without_ad) / conv_rate_without_ad:.1%}")

# ОГРАНИЧЕНИЕ: это может быть смещено (bias)
print(f"\n⚠️ ВАЖНО: Это не каузальный эффект!")
print(f"Те, кто видит рекламу, могут быть изначально более заинтересованы")

Метод 7: Исторические бенчмарки

Сравнение с прошлыми кампаниями:

# Исторические данные по кампаниям
campaigns_history = pd.DataFrame({
    'campaign': ['Email_2023_Q1', 'Email_2023_Q2', 'Email_2023_Q3', 'Email_2023_Q4',
                 'SocialAds_2023_Q1', 'SocialAds_2023_Q2', 'Current_Email_2024'],
    'spend': [10000, 12000, 11000, 13000, 8000, 9000, 11500],
    'conversions': [500, 520, 480, 550, 300, 320, 490],
})

# Вычисляем КПД
campaigns_history['roas'] = campaigns_history['conversions'] / (campaigns_history['spend'] / 1000)

# Отделяем типы кампаний
email_campaigns = campaigns_history[campaigns_history['campaign'].str.contains('Email')]
social_campaigns = campaigns_history[campaigns_history['campaign'].str.contains('Social')]

email_avg_roas = email_campaigns[:-1]['roas'].mean()  # Исключаем текущую
current_roas = campaigns_history.iloc[-1]['roas']

print(f"Средний ROAS по email кампаниям (2023): {email_avg_roas:.2f}")
print(f"ROAS текущей кампании: {current_roas:.2f}")
print(f"Относительно истории: {'+' if current_roas > email_avg_roas else ''}{((current_roas - email_avg_roas) / email_avg_roas * 100):.1f}%")

Таблица методов: Когда использовать

МетодКогда применятьПлюсыМинусы
Time SeriesКампания с четкой датойПросто, быстроМожет быть сезонность
Difference-in-DifferencesЕсть две сравнимые группыХороший результатТребует двух групп
Regression DiscontinuityЧеткая дата запускаНадежноНужен правильный дизайн
Propensity MatchingНет рандомизацииГотовые данныеМожет быть unmeasured bias
Synthetic ControlМного регионов/сегментовМощный методСложный в реализации
AttributionНужны пути юзеровРеальные данныеМожет быть смещен
Historical BenchmarkЧасто повторяющиеся кампанииБыстроГрубая оценка

Практический план анализа

def evaluate_campaign(campaign_data):
    """
    Комплексная оценка кампании
    """
    print("=== ОЦЕНКА ЭФФЕКТИВНОСТИ КАМПАНИИ ===")
    
    # 1. Описательная статистика
    print(f"\n1. БАЗОВЫЕ МЕТРИКИ")
    print(f"   Трафик: {campaign_data['traffic']:,}")
    print(f"   Конверсии: {campaign_data['conversions']:,}")
    print(f"   Конверсия: {campaign_data['conversions']/campaign_data['traffic']:.2%}")
    print(f"   Потрачено: ${campaign_data['spend']:,}")
    print(f"   ROI: {campaign_data['conversions'] * campaign_data['avg_order_value'] / campaign_data['spend']:.2f}x")
    
    # 2. Времена временных рядов
    print(f"\n2. АНАЛИЗ ВРЕМЕННЫХ РЯДОВ")
    print(f"   До кампании: {campaign_data['baseline']:.0f} конверсий/день")
    print(f"   Во время кампании: {campaign_data['during']:.0f} конверсий/день")
    print(f"   Лифт: {((campaign_data['during'] - campaign_data['baseline']) / campaign_data['baseline'] * 100):.1f}%")
    
    # 3. Сравнение с бенчмарком
    print(f"\n3. СРАВНЕНИЕ С ИСТОРИЕЙ")
    print(f"   Исторический ROAS: {campaign_data['historical_roas']:.2f}")
    print(f"   Текущий ROAS: {campaign_data['current_roas']:.2f}")
    if campaign_data['current_roas'] > campaign_data['historical_roas']:
        print(f"   ✓ Выше ожидаемого на {((campaign_data['current_roas'] - campaign_data['historical_roas']) / campaign_data['historical_roas'] * 100):.1f}%")
    
    print(f"\n4. ВЫВОДЫ")
    print(f"   Кампания была эффективна")
    print(f"   Рекомендация: {'Расширить' if campaign_data['current_roas'] > 2 else 'Оптимизировать'}")

# Пример
data = {
    'traffic': 50000,
    'conversions': 2500,
    'spend': 10000,
    'avg_order_value': 5,
    'baseline': 40,
    'during': 50,
    'historical_roas': 1.2,
    'current_roas': 1.25
}

evaluate_campaign(data)

Best Practices

1. Выбирайте метод заранее:

  • Определитесь, какой метод подходит ДО запуска кампании
  • Подготовьте необходимые данные

2. Используйте несколько методов:

  • Триангуляция результатов
  • Если все методы дают похожий результат — это надежно

3. Учитывайте ограничения:

  • Признавайте bias и unmeasured confounders
  • Обсуждайте с командой уровень уверенности

4. Документируйте допущения:

  • Какие факторы вы контролировали
  • Что могло повлиять на результаты

5. Plan for future:

  • Если возможно, планируйте правильный A/B тест на будущее
  • Собирайте данные для history
  • Логируйте external факторы (seasonality, events)