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

Как проводить A/B тест, когда есть сетевые эффекты между пользователями?

3.0 Senior🔥 201 комментариев
#A/B тестирование#Статистика и математика

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

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

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

A/B тестирование с сетевыми эффектами

Сетевые эффекты — это когда ценность продукта для одного пользователя зависит от действий других пользователей. Это серьёзно усложняет A/B тестирование, так как нарушается независимость наблюдений. Классические методы статистики дают неправильные результаты.

Типы сетевых эффектов

1. Прямые сетевые эффекты

Величина чем больше пользователей, тем ценнее продукт.

Примеры:

  • Мессенджер (стоит тем больше, чем больше друзей)
  • Социальная сеть (стоит тем больше, чем больше контактов)
  • Маркетплейс (стоит тем больше, чем больше продавцов и покупателей)

2. Косвенные сетевые эффекты

Программисты покупают платформу, потому что больше разработчиков пишут для неё.

Примеры:

  • OS (iOS стоит больше, потому что больше приложений)
  • Платежные системы (большая база торговцев = больше платежей)

3. Двусторонние сетевые эффекты

Оба типа эффектов существуют одновременно.

Примеры:

  • Uber (водители нужны потому что много пассажиров, пассажиры потому что много водителей)
  • Airbnb (хозяева потому что много гостей, гости потому что много жилья)

Проблемы классического A/B теста

Проблема 1: Спилловер (Spillover Effect)

Пользователь в контроле получает пользу от действий пользователя в тесте и наоборот.

Пример: Тестируешь улучшение в приложении знакомств
- Тест: новый алгоритм подбора
- Контроль: старый алгоритм

Проблема: парень в тесте получает улучшенные подборки и делает больше
миндов на девушку в контроле. Это влияет на её метрики, хотя она в контроле.

Проблема 2: Парадокс "лучше всем"

Тест кажется худшим, потому что его результативность зависит от контроля и наоборот.

-- Пример: маркетплейс
-- Тестируем улучшение для продавцов в тесте
-- Но если меньше хороших товаров, покупатели (половина в контроле)
-- делают меньше покупок

Методы решения

1. Кластеризация (Cluster Randomization)

Делить не пользователей, а группы пользователей так, чтобы минимизировать взаимодействие между группами.

-- Раздели по регионам/городам
SELECT 
  user_id,
  city,
  CASE 
    WHEN city IN ('NY', 'LA', 'Chicago') THEN 'test'
    ELSE 'control'
  END as variant
FROM users;

-- Или по странам
SELECT 
  user_id,
  country,
  CASE 
    WHEN country IN ('US', 'CA') THEN 'test'
    ELSE 'control'
  END as variant
FROM users;

Плюсы: просто реализуется Минусы: теряешь статистическую мощность, может быть конфаунд (разные страны = разные характеристики)

2. Временное разделение (Time-based Rollout)

Сначала тестируешь в одном периоде (контроль), потом переключаешься (тест).

-- Неделя 1-2: контроль
SELECT 
  user_id,
  'control' as variant,
  created_at
FROM events
WHERE DATE_TRUNC('week', created_at) <= '2024-01-14';

-- Неделя 3-4: тест
SELECT 
  user_id,
  'test' as variant,
  created_at
FROM events
WHERE DATE_TRUNC('week', created_at) > '2024-01-14';

Плюсы: полностью избегаешь спилловера Минусы: долго, могут быть временные эффекты, сложно контролировать переменные

3. Двусторонний дизайн (Two-sided Experiments)

Для маркетплейсов разделяй обе стороны (покупатели И продавцы).

-- Для маркетплейса
WITH sellers_treatment AS (
  SELECT 
    seller_id,
    'test' as seller_variant
  FROM sellers
  WHERE RANDOM() < 0.5
),
buyers_treatment AS (
  SELECT 
    buyer_id,
    'test' as buyer_variant
  FROM buyers
  WHERE RANDOM() < 0.5
)
SELECT 
  o.order_id,
  COALESCE(s.seller_variant, 'control') as seller_variant,
  COALESCE(b.buyer_variant, 'control') as buyer_variant
FROM orders o
LEFT JOIN sellers_treatment s ON o.seller_id = s.seller_id
LEFT JOIN buyers_treatment b ON o.buyer_id = b.buyer_id;

4. Анализ параллельного тренда (Diff-in-Diff)

Сравни тренды между группами, а не абсолютные значения.

import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression

# Diff-in-Diff анализ
def diff_in_diff_analysis(data, pre_period, post_period):
    """
    data: DataFrame с колонками [date, variant, metric]
    """
    pre = data[data['date'] <= pre_period]
    post = data[data['date'] > post_period]
    
    # Тренд в контроле
    control_pre = pre[pre['variant'] == 'control']['metric'].mean()
    control_post = post[post['variant'] == 'control']['metric'].mean()
    control_diff = control_post - control_pre
    
    # Тренд в тесте
    test_pre = pre[pre['variant'] == 'test']['metric'].mean()
    test_post = post[post['variant'] == 'test']['metric'].mean()
    test_diff = test_post - test_pre
    
    # Каузальный эффект
    causal_effect = test_diff - control_diff
    
    return {
        'control_trend': control_diff,
        'test_trend': test_diff,
        'causal_effect': causal_effect
    }

5. Instrumenatl Variables (IV)

Для экономических данных используй внешние переменные.

from statsmodels.regression.linear_model import IV2SLS

# IV регрессия для маркетплейса
# Instrumental: погода (влияет на спрос, но не на качество рекомендаций)
model = IV2SLS(endog=y, exog=X_exog, instruments=Z)
results = model.fit()

Практические примеры

Пример 1: Социальная сеть (лайки)

Проблема: новая система лайков

-- Кластеризация по возрастным группам
SELECT 
  user_id,
  age_group,
  CASE 
    WHEN age_group IN ('13-18', '19-24') THEN 'test'
    ELSE 'control'
  END as variant,
  -- Метрики
  COUNT(DISTINCT post_id) as posts_liked,
  COUNT(DISTINCT liked_by_user) as received_likes
FROM user_interactions
GROUP BY user_id, age_group;

Пример 2: Маркетплейс (рекомендации продавцов)

-- Двусторонний дизайн
WITH seller_treatment AS (
  SELECT 
    seller_id,
    CASE 
      WHEN RANDOM() < 0.5 THEN 'test' 
      ELSE 'control' 
    END as seller_variant
  FROM sellers
  WHERE active = true
),
buyer_treatment AS (
  SELECT 
    buyer_id,
    CASE 
      WHEN RANDOM() < 0.5 THEN 'test' 
      ELSE 'control' 
    END as buyer_variant
  FROM buyers
  WHERE active = true
)
SELECT 
  s.seller_variant,
  b.buyer_variant,
  COUNT(DISTINCT o.order_id) as orders,
  AVG(o.amount) as avg_order_value
FROM orders o
JOIN seller_treatment s ON o.seller_id = s.seller_id
JOIN buyer_treatment b ON o.buyer_id = b.buyer_id
GROUP BY s.seller_variant, b.buyer_variant;

Пример 3: Алгоритм подбора (мессенджер)

-- Time-based rollout
WITH pre_period AS (
  SELECT 
    user_id,
    'control' as variant,
    DATE_TRUNC('day', created_at) as day,
    COUNT(*) as messages_sent,
    COUNT(DISTINCT recipient_id) as unique_conversations
  FROM messages
  WHERE created_at >= '2024-01-01' AND created_at < '2024-01-15'
  GROUP BY user_id, DATE_TRUNC('day', created_at)
),
post_period AS (
  SELECT 
    user_id,
    'test' as variant,
    DATE_TRUNC('day', created_at) as day,
    COUNT(*) as messages_sent,
    COUNT(DISTINCT recipient_id) as unique_conversations
  FROM messages
  WHERE created_at >= '2024-01-15' AND created_at < '2024-01-29'
  GROUP BY user_id, DATE_TRUNC('day', created_at)
)
SELECT 
  variant,
  day,
  AVG(messages_sent) as avg_messages,
  AVG(unique_conversations) as avg_conversations
FROM (
  SELECT * FROM pre_period
  UNION ALL
  SELECT * FROM post_period
)
GROUP BY variant, day
ORDER BY day, variant;

Рекомендации

Выбор метода:

Тип продуктаРекомендация
МессенджерКластеризация (города/страны)
МаркетплейсДвусторонний дизайн
Социальная сетьTime-based или кластеризация
B2B платформаDiff-in-Diff или IV

Чек-лист при проведении теста с сетевыми эффектами:

  • Идентифицированы все типы сетевых эффектов
  • Выбран подходящий метод рандомизации
  • Проверен спилловер (если возможно)
  • Проверено, что эффект консистентен во времени
  • Анализ включает обе стороны (если двусторонний рынок)
  • Рассчитана статистическая мощность с учетом correlation
  • Результаты интерпретируются с осторожностью

Сетевые эффекты делают A/B тестирование сложнее, но не невозможнее. Ключ — правильный дизайн эксперимента.