Статистика для аналитика

Всё что нужно знать о распределениях, корреляции и доверительных интервалах — с кодом и примерами из практики.

Среднее, медиана, мода — когда что использовать

~7 мин

Три меры центра распределения. Почему медиана лучше среднего при выбросах, и как объяснить это интервьюеру.

Среднее (mean): сумма / количество
  • Хорошо: нормальное распределение, нет выбросов
  • Плохо: один выброс сильно тянет вверх
  • Пример: средняя зарплата — плохая метрика (миллиардеры)

Медиана (median): значение посередине (50-й перцентиль)
  • Хорошо: данные с хвостами, скошенное распределение
  • Устойчива к выбросам
  • Пример: медианная зарплата, медианный чек

Мода (mode): наиболее часто встречающееся значение
  • Хорошо: категориальные данные, дискретные переменные
  • Пример: самый популярный размер одежды, самый частый город

Правило: если mean >> median → правый хвост → используй median
          если mean ≈ median → симметричное → можно mean

SQL:
  AVG(amount) AS mean_check
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS median_check

Python:
  df['amount'].mean()
  df['amount'].median()
  df['amount'].mode()[0]

Дисперсия, стандартное отклонение, IQR

~8 мин

Меры разброса данных. Что значит «σ = 20» и почему это важно при сравнении групп в A/B тесте.

Дисперсия (Variance):
  Среднеквадратичное отклонение от среднего.
  Var = Σ(xi - mean)² / n

Стандартное отклонение (Std, σ):
  σ = √Var
  Имеет ту же размерность что и данные.

  Пример: mean=100 ₸, σ=20 ₸ → большинство значений 80–120 ₸

Правило 68-95-99.7 (для нормального распределения):
  ±1σ → 68% данных
  ±2σ → 95% данных
  ±3σ → 99.7% данных

IQR (Interquartile Range):
  IQR = Q3 - Q1 (разница 75-го и 25-го перцентиля)
  Устойчива к выбросам!
  Выброс: x < Q1 - 1.5*IQR  или  x > Q3 + 1.5*IQR

Python:
  df['amount'].std()      # σ
  df['amount'].var()      # дисперсия
  df['amount'].quantile([0.25, 0.75])

  from scipy.stats import iqr
  iqr_val = iqr(df['amount'])

Корреляция — Pearson, Spearman, ошибки интерпретации

~9 мин

Что такое r = 0.7, когда использовать Spearman вместо Pearson, и почему корреляция ≠ причинность.

Pearson (r):
  • Линейная связь между двумя числовыми переменными
  • r = 1: идеальная прямая, r = 0: нет связи, r = -1: обратная
  • Требует нормальности и отсутствия выбросов

Spearman (ρ):
  • Монотонная связь (не обязательно линейная)
  • Устойчив к выбросам (работает с рангами)
  • Используй когда данные ненормальные или есть выбросы

Python:
  df[['sessions', 'revenue']].corr()                    # Pearson
  df[['sessions', 'revenue']].corr(method='spearman')   # Spearman

  from scipy.stats import pearsonr, spearmanr
  r, p = pearsonr(df['sessions'], df['revenue'])
  print(f'r={r:.3f}, p={p:.4f}')

Ошибки интерпретации:
  ✗ «r = 0.8 → sessions вызывают revenue»
  ✓ Есть СВЯЗЬ, но причинность не доказана

Примеры ложной корреляции:
  Мороженое и утопания (оба зависят от жаркой погоды)
  Количество пожарных и ущерб (больше пожарных = больший пожар)

Правило на собесе: «Correlation does not imply causation»

Распределения — нормальное, биномиальное, Пуассон

~10 мин

Три ключевых распределения в аналитике. Как понять что данные нормально распределены и что с этим делать.

Нормальное (Gaussian):
  Bell curve. Симметрично вокруг среднего.
  Применяется: рост, IQ, ошибки измерений.

  Проверка нормальности:
  from scipy.stats import shapiro, normaltest
  stat, p = shapiro(df['amount'])   # p > 0.05 → нормальное
  # Визуально: QQ-plot

  import scipy.stats as stats
  stats.probplot(df['amount'], dist='norm', plot=plt)

Биномиальное:
  Количество «успехов» в n независимых экспериментах.
  Применяется: конверсия (купил/не купил), CTR.
  P(k успехов из n) = C(n,k) * p^k * (1-p)^(n-k)

  from scipy.stats import binom
  binom.pmf(k=5, n=100, p=0.05)  # P(ровно 5 покупок из 100 при conv=5%)

Пуассон:
  Количество событий за фиксированное время при известной средней частоте.
  Применяется: число заказов в час, число обращений в поддержку.
  P(k) = (λ^k * e^-λ) / k!   где λ = среднее число событий

  from scipy.stats import poisson
  poisson.pmf(k=3, mu=5)   # P(ровно 3 заказа при среднем 5/час)

Доверительные интервалы

~8 мин

95% CI — что это означает, как считать и как объяснять результаты теста без p-value жаргона.

95% доверительный интервал:
  «Если повторить эксперимент 100 раз, в 95 случаях
  истинное значение попадёт в этот диапазон»

  НЕ означает: «с 95% вероятностью истинное значение в интервале»

Формула для среднего:
  CI = mean ± z * (σ / √n)
  z = 1.96 для 95% CI
  z = 2.58 для 99% CI

Python:
  import numpy as np
  from scipy import stats

  data = df['amount'].values
  n = len(data)
  mean = np.mean(data)
  se = stats.sem(data)      # standard error = σ/√n

  ci = stats.t.interval(0.95, df=n-1, loc=mean, scale=se)
  print(f'95% CI: ({ci[0]:.1f}, {ci[1]:.1f})')

Применение в A/B:
  Если CI для разницы (variant - control) НЕ включает 0
  → изменение статистически значимо

  Если CI = [+2₸, +50₸] → эффект положительный
  Если CI = [-10₸, +15₸] → неопределённо (включает 0)

Bootstrap CI (без предположений о распределении):
  from sklearn.utils import resample
  boot_means = [np.mean(resample(data)) for _ in range(10000)]
  ci = np.percentile(boot_means, [2.5, 97.5])

Асимметрия и эксцесс — диагностика распределения

~6 мин

Skewness и kurtosis — быстро понять форму данных до построения графика.

Skewness (асимметрия):
  = 0: симметричное распределение
  > 0: правый хвост (большие выбросы справа)
       Типично для: выручки, времени ожидания, LTV
  < 0: левый хвост (большие выбросы слева)

Kurtosis (эксцесс):
  = 3 (или 0 в «excess kurtosis»): нормальное
  > 3: остроконечное (много выбросов)
  < 3: плоское (мало выбросов)

Python:
  from scipy.stats import skew, kurtosis

import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Статистика для аналитика',
  description: 'Среднее, медиана, корреляция, доверительные интервалы — с кодом и примерами.',
  openGraph: { title: 'Статистика для аналитика', description: 'Среднее, медиана, корреляция, доверительные интервалы — с кодом и примерами.' },
};


  print(f'Skewness: {skew(df["amount"]):.2f}')
  print(f'Kurtosis: {kurtosis(df["amount"]):.2f}')

Интерпретация для аналитика:
  Skewness > 1 → данные сильно скошены
                → median > mean или mean > median
                → лучше использовать median, Mann-Whitney, log-transform

  Log-преобразование для нормализации правого хвоста:
  df['log_amount'] = np.log1p(df['amount'])  # log(x+1)