Как оценишь эффективность рекламной кампании без A/B теста?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Оценка эффективности рекламной кампании без 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)