Введение

Бутстрап — это метод, позволяющий оценить распределение статистики с помощью многократного переотбора выборки с возвращением.

Проще говоря, мы как будто много раз «пересобираем» наши данные из самих себя и смотрим, как меняется результат.

Логика работы: 1. Есть исходные данные
2. Мы много раз случайно выбираем из них новые выборки (с повторениями)
3. Считаем статистику (например, среднее)
4. Анализируем полученное распределение

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

Разведочный анализ данных

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

iris = load_iris()
df = pd.DataFrame(data=iris.data, columns=iris.feature_names)

df['species'] = iris.target
df['species'] = df['species'].map({0: 'setosa', 1: 'versicolor', 2: 'virginica'})

print(df.describe())
##        sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
## count         150.000000        150.000000         150.000000        150.000000
## mean            5.843333          3.057333           3.758000          1.199333
## std             0.828066          0.435866           1.765298          0.762238
## min             4.300000          2.000000           1.000000          0.100000
## 25%             5.100000          2.800000           1.600000          0.300000
## 50%             5.800000          3.000000           4.350000          1.300000
## 75%             6.400000          3.300000           5.100000          1.800000
## max             7.900000          4.400000           6.900000          2.500000

Описание данных показывает:

Это помогает понять, какие значения «типичные», а какие — выбросы.

sns.pairplot(df, hue='species')

plt.show()

График pairplot показывает попарные зависимости между признаками.

По нему видно, что виды растений достаточно хорошо разделяются — значит, признаки действительно различаются между классами.

plt.figure(figsize=(8, 6))
sns.heatmap(df.drop('species', axis=1).corr(), annot=True, cmap='coolwarm')
plt.show()

Тепловая карта показывает корреляции между признаками.

Бутстрап на Python

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def get_percentile_ci(bootstrap_stats, pe, alpha=0.05):
    left, right = np.quantile(bootstrap_stats, [alpha / 2, 1 - alpha / 2])
    return left, right

Эта функция считает доверительный интервал — диапазон, в котором с высокой вероятностью находится истинное значение.

data = sns.load_dataset("iris")
n = len(data)
B = 10000
alpha = 0.05
setosa = data[data.species == "setosa"]["sepal_length"]
versicolor = data[data.species == "versicolor"]["sepal_length"]

Берём только нужные группы для сравнения.

pe = setosa.mean() - versicolor.mean()

Точечная оценка — это просто разница средних.

Это одно число, которое показывает:
насколько в среднем отличаются группы.

bootstrap_setosa = np.random.choice(setosa, (B, n), replace=True).mean(axis=1)
bootstrap_versicolor = np.random.choice(versicolor, (B, n), replace=True).mean(axis=1)

Здесь происходит сам бутстрап:

мы много раз:

В итоге получаем распределение возможных средних.

bootstrap_stats = bootstrap_setosa - bootstrap_versicolor

Теперь у нас есть распределение разницы средних.

Это уже не одно число, а целый набор возможных значений.

ci = get_percentile_ci(bootstrap_stats, pe, alpha)

Доверительный интервал — это диапазон значений, в котором, скорее всего, лежит настоящая разница.

has_effect = not (ci[0] < 0 < ci[1])

Проверка значимости:

Почему 0?
Потому что 0 означает «разница равна нулю».

Интерпретация результатов

  • Точечная оценка — показывает среднюю разницу
  • Стандартная ошибка (SE) — показывает, насколько «прыгает» результат
  • Смещение (bias) — показывает, не искажает ли бутстрап результат
  • Доверительный интервал — диапазон возможных значений

Если интервал не содержит 0 → различие статистически значимо.

Реализация алгоритма бутстрапа на R

# Загрузка необходимых библиотек
library(boot)
library(ggplot2)

# Установка seed для воспроизводимости
set.seed(123)

# Загрузка данных
data(iris)

# Подготовка данных (только два вида)
iris_subset <- subset(iris, Species %in% c("setosa", "versicolor"))

# Бутстрап-функция для разницы средних
bootstrap_function <- function(data, indices) {
  d <- data[indices, ]
  
  mean_setosa <- mean(d[d$Species == "setosa", "Sepal.Length"])
  mean_versicolor <- mean(d[d$Species == "versicolor", "Sepal.Length"])
  
  return(mean_setosa - mean_versicolor)
}

# Количество бутстрап-итераций
B <- 10000

# Выполнение бутстрапа
bootstrap_results <- boot(data = iris_subset, 
                          statistic = bootstrap_function, 
                          R = B)

# Точечная оценка (разница средних в исходных данных)
point_estimate <- mean(iris_subset[iris_subset$Species == "setosa", "Sepal.Length"]) - 
                  mean(iris_subset[iris_subset$Species == "versicolor", "Sepal.Length"])

# Доверительный интервал методом BCa
ci <- boot.ci(bootstrap_results, type = "bca")

# Вывод результатов
cat("Точечная оценка:", round(point_estimate, 3), "\n")
## Точечная оценка: -0.93
cat("Стандартная ошибка (SE):", round(sd(bootstrap_results$t), 3), "\n")
## Стандартная ошибка (SE): 0.087
cat("Смещение (bias):", round(mean(bootstrap_results$t) - point_estimate, 3), "\n")
## Смещение (bias): -0.001
cat("95% доверительный интервал:", round(ci$bca[4], 3), "-", round(ci$bca[5], 3), "\n")
## 95% доверительный интервал: -1.1 - -0.76
# Проверка значимости
has_effect <- !(ci$bca[4] < 0 & ci$bca[5] > 0)
cat("Статистически значимо:", has_effect, "\n")
## Статистически значимо: TRUE
# Визуализация распределения бутстрап-статистик
hist(bootstrap_results$t, 
     breaks = 50,
     main = "Распределение разницы средних (бутстрап)",
     xlab = "Разница средних (setosa - versicolor)",
     col = "lightblue",
     border = "white")

# Добавление доверительного интервала
abline(v = point_estimate, col = "red", lwd = 2, lty = 2)
abline(v = ci$bca[4], col = "blue", lwd = 2)
abline(v = ci$bca[5], col = "blue", lwd = 2)

# Легенда
legend("topright", 
       legend = c("Точечная оценка", "95% доверительный интервал"),
       col = c("red", "blue"),
       lty = c(2, 1),
       lwd = 2)

Вывод

Бутстрап позволил оценить распределение разницы средних без предположений о нормальности данных.

Результаты показывают, что:

Это означает, что различие между выборками статистически значимо и не является случайным.