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

Как оценить репрезентативность выборки?

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

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

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

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

Оценка репрезентативности выборки: Практический подход

Репрезентативность выборки — одна из наиболее критических характеристик в data science. Моя опыт показывает, что неправильная оценка репрезентативности приводит к систематическим ошибкам и неправильным выводам. Расскажу о методах, которыми я пользуюсь в реальных проектах.

1. Статистические тесты на репрезентативность

Основная идея: Сравнивают распределение в выборке с известным генеральной совокупностью.

import numpy as np
import pandas as pd
from scipy import stats
import matplotlib.pyplot as plt

# Пример: Проверяем репрезентативность выборки пользователей по возрасту
# Известное распределение в генеральной совокупности (из census данных)
population_age_dist = [0.12, 0.18, 0.25, 0.22, 0.15, 0.08]  # Доли по возрастным группам
age_groups = ['18-25', '26-35', '36-45', '46-55', '56-65', '65+']

# Наша выборка пользователей
sample_data = np.random.choice(age_groups, size=1000, 
                               p=population_age_dist + np.random.normal(0, 0.02, 6))
sample_dist = pd.Series(sample_data).value_counts(normalize=True).sort_index()

print("Распределение в генеральной совокупности:")
for group, prob in zip(age_groups, population_age_dist):
    print(f"{group}: {prob:.2%}")

print("\nРаспределение в выборке:")
for group in age_groups:
    prob = sample_dist.get(group, 0)
    print(f"{group}: {prob:.2%}")

Тест Хи-квадрат (Chi-square test)

# Хи-квадрат тест проверяет, отличается ли выборка от известного распределения
observed_frequencies = pd.Series(sample_data).value_counts()
expected_frequencies = np.array(population_age_dist) * len(sample_data)

chi2_stat, p_value = stats.chisquare(observed_frequencies, expected_frequencies)

print(f"\nХи-квадрат статистика: {chi2_stat:.4f}")
print(f"P-value: {p_value:.4f}")

if p_value > 0.05:
    print("✓ Выборка репрезентативна (различия не значимы)")
else:
    print("✗ Выборка НЕ репрезентативна (различия статистически значимы)")

2. Сравнение статистических характеристик

# Метод 1: Сравнение среднего и дисперсии
print("===== АНАЛИЗ РЕПРЕЗЕНТАТИВНОСТИ =====")

# Генерируем генеральную совокупность и выборку
np.random.seed(42)
population = np.random.normal(loc=100, scale=15, size=100000)
sample = np.random.choice(population, size=500)

print(f"Генеральная совокупность:")
print(f"  Среднее: {population.mean():.2f}")
print(f"  Стандартное отклонение: {population.std():.2f}")
print(f"  Медиана: {np.median(population):.2f}")
print(f"  Q1-Q3: [{np.percentile(population, 25):.2f}, {np.percentile(population, 75):.2f}]")

print(f"\nВыборка:")
print(f"  Среднее: {sample.mean():.2f}")
print(f"  Стандартное отклонение: {sample.std():.2f}")
print(f"  Медиана: {np.median(sample):.2f}")
print(f"  Q1-Q3: [{np.percentile(sample, 25):.2f}, {np.percentile(sample, 75):.2f}]")

# T-тест для сравнения средних
t_stat, p_value = stats.ttest_ind(population[:1000], sample)  # Подвыборка совокупности
print(f"\nT-тест для средних:")
print(f"  t-статистика: {t_stat:.4f}")
print(f"  P-value: {p_value:.4f}")

3. Анализ смещения (Bias Detection)

# Визуализация различий в распределениях
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Гистограммы
axes[0].hist(population, bins=50, alpha=0.5, label='Генеральная совокупность', density=True)
axes[0].hist(sample, bins=30, alpha=0.5, label='Выборка', density=True)
axes[0].set_xlabel('Значение')
axes[0].set_ylabel('Плотность')
axes[0].set_title('Распределения')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Q-Q plot для проверки нормальности
stats.probplot(sample, dist="norm", plot=axes[1])
axes[1].set_title('Q-Q Plot (проверка нормальности)')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

4. Проверка по ключевым демографическим переменным

# В реальных проектах проверяем репрезентативность по разным признакам
def check_representativeness(sample_df, population_df, column):
    """
    Сравнивает распределения в выборке и совокупности
    """
    print(f"\n===== Анализ по '{column}' =====")
    
    # Получаем распределения
    sample_dist = sample_df[column].value_counts(normalize=True).sort_index()
    pop_dist = population_df[column].value_counts(normalize=True).sort_index()
    
    # Выравниваем индексы
    all_categories = set(sample_dist.index) | set(pop_dist.index)
    sample_dist = sample_dist.reindex(all_categories, fill_value=0)
    pop_dist = pop_dist.reindex(all_categories, fill_value=0)
    
    # Chi-square тест
    observed = sample_dist.values * len(sample_df)
    expected = pop_dist.values * len(sample_df)  # Ожидаемые частоты при репрезентативности
    
    chi2_stat, p_value = stats.chisquare(observed, expected)
    
    # Визуализация
    comparison = pd.DataFrame({
        'Выборка': sample_dist,
        'Совокупность': pop_dist
    })
    
    print(comparison)
    print(f"\nХи-квадрат: {chi2_stat:.4f}")
    print(f"P-value: {p_value:.4f}")
    print(f"Репрезентативна: {'✓ ДА' if p_value > 0.05 else '✗ НЕТ'}")
    
    # Визуализация
    comparison.plot(kind='bar', figsize=(10, 5))
    plt.title(f"Распределение '{column}': Выборка vs Совокупность")
    plt.ylabel('Доля')
    plt.xticks(rotation=45)
    plt.legend()
    plt.tight_layout()
    plt.show()
    
    return chi2_stat, p_value

# Пример использования
sample_df = pd.DataFrame({'gender': np.random.choice(['M', 'F'], 500)})
population_df = pd.DataFrame({'gender': np.random.choice(['M', 'F'], 10000, p=[0.48, 0.52])})

check_representativeness(sample_df, population_df, 'gender')

5. Метод Bootstrap для оценки доверительных интервалов

# Bootstrap показывает стабильность оценок выборки
def bootstrap_ci(sample, n_bootstrap=1000, ci=95):
    """
    Вычисляет доверительные интервалы для среднего через bootstrap
    """
    bootstrap_means = []
    n = len(sample)
    
    for _ in range(n_bootstrap):
        bootstrap_sample = np.random.choice(sample, size=n, replace=True)
        bootstrap_means.append(bootstrap_sample.mean())
    
    bootstrap_means = np.array(bootstrap_means)
    lower = np.percentile(bootstrap_means, (100 - ci) / 2)
    upper = np.percentile(bootstrap_means, 100 - (100 - ci) / 2)
    
    print(f"\nBootstrap анализ (n_samples={n_bootstrap}):")
    print(f"Среднее в выборке: {sample.mean():.4f}")
    print(f"95% CI: [{lower:.4f}, {upper:.4f}]")
    print(f"Ширина интервала: {upper - lower:.4f}")
    
    # Визуализация
    plt.figure(figsize=(10, 6))
    plt.hist(bootstrap_means, bins=50, alpha=0.7, edgecolor='black')
    plt.axvline(sample.mean(), color='r', linestyle='--', linewidth=2, label='Среднее выборки')
    plt.axvline(lower, color='g', linestyle='--', linewidth=2, label=f'95% CI')
    plt.axvline(upper, color='g', linestyle='--', linewidth=2)
    plt.xlabel('Среднее')
    plt.ylabel('Частота')
    plt.title('Bootstrap распределение средних')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()
    
    return lower, upper

bootstrap_ci(sample, n_bootstrap=1000)

6. Анализ Coverage (охват генеральной совокупности)

# Как много генеральной совокупности покрыла наша выборка?
def analyze_coverage(population, sample, feature_column):
    """
    Анализирует, какую часть многообразия генеральной совокупности покрывает выборка
    """
    pop_categories = set(population[feature_column].unique())
    sample_categories = set(sample[feature_column].unique())
    
    coverage = len(sample_categories) / len(pop_categories)
    missing_categories = pop_categories - sample_categories
    
    print(f"\nОхват по '{feature_column}':")
    print(f"Уникальных категорий в совокупности: {len(pop_categories)}")
    print(f"Уникальных категорий в выборке: {len(sample_categories)}")
    print(f"Охват: {coverage:.1%}")
    
    if missing_categories:
        print(f"Отсутствующие категории: {missing_categories}")
    
    return coverage

7. Чеклист репрезентативности

def full_representativeness_check(sample_df, population_df, key_columns):
    """
    Полная проверка репрезентативности выборки
    """
    results = {}
    
    print("\n" + "="*60)
    print("ПОЛНАЯ ПРОВЕРКА РЕПРЕЗЕНТАТИВНОСТИ ВЫБОРКИ")
    print("="*60)
    
    print(f"\nРазмер генеральной совокупности: {len(population_df)}")
    print(f"Размер выборки: {len(sample_df)}")
    print(f"Доля выборки: {len(sample_df)/len(population_df):.1%}")
    
    # Проверяем каждый ключевой столбец
    for column in key_columns:
        chi2, p_value = check_representativeness(sample_df, population_df, column)
        results[column] = {'chi2': chi2, 'p_value': p_value, 
                          'is_representative': p_value > 0.05}
    
    # Итоговый результат
    print("\n" + "="*60)
    print("ИТОГОВЫЙ РЕЗУЛЬТАТ:")
    print("="*60)
    
    representative_count = sum(1 for r in results.values() if r['is_representative'])
    print(f"Репрезентативна по {representative_count}/{len(key_columns)} переменным")
    
    if representative_count == len(key_columns):
        print("✓ Выборка репрезентативна по всем ключевым переменным")
    elif representative_count > len(key_columns) * 0.8:
        print("⚠ Выборка в основном репрезентативна, но есть проблемы")
    else:
        print("✗ Выборка НЕ репрезентативна, требуется переотбор")
    
    return results

# Пример:
key_columns = ['gender', 'age_group', 'region']
results = full_representativeness_check(sample_df, population_df, key_columns)

8. Практические рекомендации

  • Минимальный размер выборки: Используйте формулу Кохрена для определения
  • Стратифицированная выборка: Лучший способ гарантировать репрезентативность
  • Взвешивание: Если выборка смещена — используйте weights для корректировки
  • Постоянный мониторинг: Проверяйте репрезентативность на каждом новом батче данных

Нерепрезентативная выборка — это скрытая бомба, которая взрывается месяцами спустя в production. Всегда проводите эту проверку перед началом анализа.