← Назад к вопросам
Как оценить репрезентативность выборки?
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. Всегда проводите эту проверку перед началом анализа.