Бутстрап — это метод, позволяющий оценить распределение статистики с помощью многократного переотбора выборки с возвращением.
Проще говоря, мы как будто много раз «пересобираем» наши данные из самих себя и смотрим, как меняется результат.
Логика работы: 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()
Тепловая карта показывает корреляции между признаками.
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
n — размер выборкиB — сколько раз делаем бутстрап (чем больше, тем
точнее)alpha — уровень значимости (0.05 → 95% доверительный
интервал)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 означает «разница равна нулю».
Если интервал не содержит 0 → различие статистически значимо.
# Загрузка необходимых библиотек
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)
Бутстрап позволил оценить распределение разницы средних без предположений о нормальности данных.
Результаты показывают, что:
Это означает, что различие между выборками статистически значимо и не является случайным.