Бутстрап - это компьютерный метод, который позволяет оценить точность ваших статистических выводов, не зная теоретического распределения данных.
Говоря совсем просто: это метод “имитации” повторных экспериментов на основе всего одной имеющейся у вас выборки.
options(repos = c(CRAN = "https://cran.rstudio.com/"))
Для работы был выбран датасет iris доступный и в R и в Python.
Выполним разведочный анализ данных на Python. Выведем базовые статистики, парные диаграммы рассеяния и диаграмму корреляции признаков.
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'})
df = df.rename(columns={
'sepal length (cm)': 'Длина чашелистика (см)',
'sepal width (cm)': 'Ширина чашелистика (см)',
'petal length (cm)': 'Длина лепестка (см)',
'petal width (cm)': 'Ширина лепестка (см)'
})
df['species'] = df['species'].map({
'setosa': 'Щетинистый ирис',
'versicolor': 'Разноцветный ирис',
'virginica': 'Виргинский ирис'
})
print("Основные статистики:")
## Основные статистики:
print(df.describe())
## Длина чашелистика (см) ... Ширина лепестка (см)
## count 150.000000 ... 150.000000
## mean 5.843333 ... 1.199333
## std 0.828066 ... 0.762238
## min 4.300000 ... 0.100000
## 25% 5.100000 ... 0.300000
## 50% 5.800000 ... 1.300000
## 75% 6.400000 ... 1.800000
## max 7.900000 ... 2.500000
##
## [8 rows x 4 columns]
# Создаем фигуру с несколькими подграфиками
fig = plt.figure(figsize=(15, 12))
# ГРАФИК 1: Ящик с усами (Box plot)
plt.subplot(2, 2, 1)
sns.boxplot(x='species', y='Длина чашелистика (см)', data=df, palette='Set2')
plt.title('Распределение длины чашелистика по видам', fontsize=12, fontweight='bold')
plt.xlabel('Вид ириса', fontsize=10)
plt.ylabel('Длина чашелистика (см)', fontsize=10)
plt.xticks(rotation=45)
## ([0, 1, 2], [Text(0, 0, 'Щетинистый ирис'), Text(1, 0, 'Разноцветный ирис'), Text(2, 0, 'Виргинский ирис')])
# ГРАФИК 2: Скрипичный график (Violin plot)
plt.subplot(2, 2, 2)
sns.violinplot(x='species', y='Длина лепестка (см)', data=df, palette='Set3')
plt.title('Распределение длины лепестка по видам', fontsize=12, fontweight='bold')
plt.xlabel('Вид ириса', fontsize=10)
plt.ylabel('Длина лепестка (см)', fontsize=10)
plt.xticks(rotation=45)
## ([0, 1, 2], [Text(0, 0, 'Щетинистый ирис'), Text(1, 0, 'Разноцветный ирис'), Text(2, 0, 'Виргинский ирис')])
# ГРАФИК 3: Ящик с усами + точки
plt.subplot(2, 2, 3)
sns.boxplot(x='species', y='Ширина чашелистика (см)', data=df, palette='pastel')
sns.swarmplot(x='species', y='Ширина чашелистика (см)', data=df, color='black', alpha=0.5, size=3)
plt.title('Ширина чашелистика: боксплот с точками', fontsize=12, fontweight='bold')
plt.xlabel('Вид ириса', fontsize=10)
plt.ylabel('Ширина чашелистика (см)', fontsize=10)
plt.xticks(rotation=45)
## ([0, 1, 2], [Text(0, 0, 'Щетинистый ирис'), Text(1, 0, 'Разноцветный ирис'), Text(2, 0, 'Виргинский ирис')])
# ГРАФИК 4: Корреляционная матрица
plt.subplot(2, 2, 4)
# Переименовываем для корреляционной матрицы
corr_df = df[['Длина чашелистика (см)', 'Ширина чашелистика (см)',
'Длина лепестка (см)', 'Ширина лепестка (см)']].corr()
sns.heatmap(corr_df, annot=True, cmap='coolwarm', center=0,
square=True, linewidths=0.5, fmt='.2f')
plt.title('Корреляция между признаками', fontsize=12, fontweight='bold')
plt.suptitle('Анализ данных Iris (Ирисы Фишера)', fontsize=16, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
Выполним аналогичный разведочный анализ данных на R. Выведем базовые статистики, парные диаграммы рассеяния и диаграмму корреляции признаков.
install.packages("corrplot")
## Устанавливаю пакет в 'C:/Users/rezni/AppData/Local/R/win-library/4.4'
## (потому что 'lib' не определено)
## пакет 'corrplot' успешно распакован, MD5-суммы проверены
##
## Скачанные бинарные пакеты находятся в
## C:\Users\rezni\AppData\Local\Temp\Rtmpa4ahB9\downloaded_packages
# Загружаем данные
data(iris)
# Переименовываем всё на русский
names(iris) <- c("Длина_чашелистика", "Ширина_чашелистика",
"Длина_лепестка", "Ширина_лепестка", "Вид")
# Меняем названия видов
levels(iris$Вид) <- c("Щетинистый", "Разноцветный", "Виргинский")
# Статистики
summary(iris)
## Длина_чашелистика Ширина_чашелистика Длина_лепестка Ширина_лепестка
## Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100
## 1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300
## Median :5.800 Median :3.000 Median :4.350 Median :1.300
## Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199
## 3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
## Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
## Вид
## Щетинистый :50
## Разноцветный:50
## Виргинский :50
##
##
##
# Парные диаграммы
pairs(iris[,1:4], # ("Щетинистый", "Разноцветный", "Виргинский")
col = c("red", "green3", "blue")[iris$Вид],
pch = 19,
main = "Диаграммы рассеяния для Iris",
labels = c("Длина чашелистика", "Ширина чашелистика",
"Длина лепестка", "Ширина лепестка"))
# Корреляционная матрица
library(corrplot)
## corrplot 0.95 loaded
cor_matrix <- cor(iris[,1:4])
rownames(cor_matrix) <- colnames(cor_matrix) <-
c("Длина чашелистика", "Ширина чашелистика", "Длина лепестка", "Ширина лепестка")
corrplot(cor_matrix, method = "color", addCoef.col = "black",
tl.col = "black", tl.srt = 45)
# Импорт необходимых библиотек
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
# Загружаем данные iris
data = sns.load_dataset("iris")
n = len(data) # Размер выборки
B = 5000 # Количество бутстрап-итераций
alpha = 0.05 # Уровень значимости
# Выделяем длину чашелистика для двух видов ирисов
setosa = data[data.species == "setosa"]["sepal_length"]
versicolor = data[data.species == "versicolor"]["sepal_length"]
# Точечная оценка - разница средних между видами
pe = setosa.mean() - versicolor.mean()
# Бутстрап для setosa: генерируем B выборок с возвращением и считаем средние
bootstrap_setosa = np.random.choice(setosa, (B, n), replace=True).mean(axis=1)
# Бутстрап для versicolor
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)
# Проверяем статистическую значимость (если 0 не входит в ДИ)
has_effect = not (ci[0] < 0 < ci[1])
# Вывод результатов
print("=" * 60)
## ============================================================
print("РЕЗУЛЬТАТЫ БУТСТРАП-АНАЛИЗА")
## РЕЗУЛЬТАТЫ БУТСТРАП-АНАЛИЗА
print("=" * 60)
## ============================================================
print(f"Разница средних (Щетинистый - Разноцветный): {pe:.3f} см")
## Разница средних (Щетинистый - Разноцветный): -0.930 см
print(f"{((1 - alpha) * 100)}% доверительный интервал: ({ci[0]:.3f}, {ci[1]:.3f}) см")
## 95.0% доверительный интервал: (-1.031, -0.831) см
print(f"Различия статистически значимы (0 не входит в ДИ): {has_effect}")
## Различия статистически значимы (0 не входит в ДИ): True
print("=" * 60)
## ============================================================
# СОЗДАЕМ 2 ГРАФИКА
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# ГРАФИК 1: Гистограмма с плотностью
sns.histplot(bootstrap_stats, bins=50, kde=True, color='purple',
alpha=0.6, edgecolor='white', ax=ax1)
# Закрашиваем область доверительного интервала
ax1.axvspan(ci[0], ci[1], alpha=0.2, color='orange', label='95% доверительный интервал')
# Вертикальные линии
ax1.axvline(pe, color='darkred', linestyle='-', linewidth=2.5,
label=f'Точечная оценка: {pe:.3f} см')
ax1.axvline(ci[0], color='orange', linestyle='--', linewidth=2,
label=f'Нижняя граница: {ci[0]:.3f} см')
ax1.axvline(ci[1], color='orange', linestyle='--', linewidth=2,
label=f'Верхняя граница: {ci[1]:.3f} см')
ax1.axvline(0, color='black', linestyle=':', linewidth=2,
label='Нулевое значение')
ax1.set_title('Бутстрап-распределение разницы средних', fontsize=12, fontweight='bold')
ax1.set_xlabel('Разница средних (Щетинистый - Разноцветный), см', fontsize=10)
ax1.set_ylabel('Частота', fontsize=10)
ax1.legend(loc='upper right', fontsize=9)
ax1.grid(alpha=0.3, linestyle='--')
# Добавляем текст на первый график
if has_effect:
ax1.text(0.02, 0.95, 'Статистически значимое различие',
transform=ax1.transAxes, fontsize=10,
bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.7))
# ГРАФИК 2: Ящик с усами (Boxplot)
# Создаем данные для boxplot (сравнение исходных выборок)
box_data = [setosa, versicolor]
bp = ax2.boxplot(box_data, labels=['Щетинистый', 'Разноцветный'],
patch_artist=True, widths=0.6)
## <string>:1: MatplotlibDeprecationWarning: The 'labels' parameter of boxplot() has been renamed 'tick_labels' since Matplotlib 3.9; support for the old name will be dropped in 3.11.
# Настройка цветов для boxplot
colors = ['lightcoral', 'lightblue']
for patch, color in zip(bp['boxes'], colors):
patch.set_facecolor(color)
patch.set_alpha(0.7)
# Настройка линий
bp['medians'][0].set_color('darkred')
bp['medians'][0].set_linewidth(2)
bp['medians'][1].set_color('darkblue')
bp['medians'][1].set_linewidth(2)
# Добавляем точки (все наблюдения)
ax2.scatter(np.random.normal(1, 0.04, len(setosa)), setosa,
alpha=0.5, color='darkred', s=20, label='Щетинистый')
ax2.scatter(np.random.normal(2, 0.04, len(versicolor)), versicolor,
alpha=0.5, color='darkblue', s=20, label='Разноцветный')
ax2.set_title('Сравнение исходных выборок\n(длина чашелистика)', fontsize=12, fontweight='bold')
ax2.set_xlabel('Вид ириса', fontsize=10)
ax2.set_ylabel('Длина чашелистика (см)', fontsize=10)
ax2.legend(loc='upper right', fontsize=9)
ax2.grid(alpha=0.3, axis='y', linestyle='--')
# Общий заголовок
plt.suptitle('Бутстрап-анализ: сравнение длины чашелистика у двух видов ирисов',
fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# Дополнительная статистика на русском
print("\n" + "=" * 60)
##
## ============================================================
print("ДОПОЛНИТЕЛЬНАЯ СТАТИСТИКА")
## ДОПОЛНИТЕЛЬНАЯ СТАТИСТИКА
print("=" * 60)
## ============================================================
print(f"Стандартная ошибка (SE): {bootstrap_stats.std():.3f} см")
## Стандартная ошибка (SE): 0.051 см
print(f"Смещение (bias): {bootstrap_stats.mean() - pe:.3f} см")
## Смещение (bias): 0.001 см
print(f"Коэффициент вариации: {bootstrap_stats.std() / abs(pe):.3f}")
## Коэффициент вариации: 0.055
print(f"Медиана бутстрап-распределения: {np.median(bootstrap_stats):.3f} см")
## Медиана бутстрап-распределения: -0.929 см
# Статистика по исходным данным
print("\nСТАТИСТИКА ПО ИСХОДНЫМ ВЫБОРКАМ:")
##
## СТАТИСТИКА ПО ИСХОДНЫМ ВЫБОРКАМ:
print(f"Щетинистый ирис: среднее = {setosa.mean():.3f} см, std = {setosa.std():.3f} см")
## Щетинистый ирис: среднее = 5.006 см, std = 0.352 см
print(f"Разноцветный ирис: среднее = {versicolor.mean():.3f} см, std = {versicolor.std():.3f} см")
## Разноцветный ирис: среднее = 5.936 см, std = 0.516 см
# Устанавливаем seed для воспроизводимости результатов
set.seed(123)
# Загружаем необходимые библиотеки
library(boot)
library(ggplot2)
# Загружаем данные iris
data(iris)
# Переименовываем столбцы
iris$Species <- factor(iris$Species,
levels = c("setosa", "versicolor", "virginica"),
labels = c("Щетинистый", "Разноцветный", "Виргинский"))
names(iris)[names(iris) == "Sepal.Length"] <- "Длина.чашелистика"
names(iris)[names(iris) == "Sepal.Width"] <- "Ширина.чашелистика"
names(iris)[names(iris) == "Petal.Length"] <- "Длина.лепестка"
names(iris)[names(iris) == "Petal.Width"] <- "Ширина.лепестка"
# Оставляем только два вида ирисов для сравнения
iris_subset <- subset(iris, Species %in% c("Щетинистый", "Разноцветный"))
# Функция для бутстрапа: вычисляет разницу средних длин чашелистика между видами
bootstrap_function <- function(data, indices) {
d <- data[indices, ]
mean_setosa <- mean(d[d$Species == "Щетинистый", "Длина.чашелистика"])
mean_versicolor <- mean(d[d$Species == "Разноцветный", "Длина.чашелистика"])
return(mean_setosa - mean_versicolor)
}
# Количество бутстрап-итераций
B <- 5000
# Выполняем бутстрап с помощью функции boot()
bootstrap_results <- boot(data = iris_subset,
statistic = bootstrap_function,
R = B)
# Вычисляем доверительный интервал методом BCa
ci <- boot.ci(bootstrap_results, type = "bca")
# Выводим доверительный интервал
print(ci)
## BOOTSTRAP CONFIDENCE INTERVAL CALCULATIONS
## Based on 5000 bootstrap replicates
##
## CALL :
## boot.ci(boot.out = bootstrap_results, type = "bca")
##
## Intervals :
## Level BCa
## 95% (-1.1035, -0.7540 )
## Calculations and Intervals on Original Scale
# Создаем датафрейм с бутстрап-статистиками для визуализации
bootstrap_df <- data.frame(Diff = bootstrap_results$t)
# Точечная оценка (разница средних в исходной выборке)
point_estimate <- bootstrap_function(iris_subset, 1:nrow(iris_subset))
# Визуализация распределения бутстрап-статистик
ggplot(bootstrap_df, aes(x = Diff)) +
# Гистограмма
geom_histogram(aes(y = after_stat(density)),
bins = 50,
fill = "orange",
color = "white",
alpha = 0.7) +
# Ядерная оценка плотности (новый цвет: темно-красный)
geom_density(color = "darkred", size = 1.2) +
# Вертикальная линия для точечной оценки (новый цвет: синий)
geom_vline(xintercept = point_estimate,
color = "blue",
linetype = "solid",
size = 1.2) +
# Вертикальная линия для нижней границы ДИ (новый цвет: оранжевый)
geom_vline(xintercept = ci$bca[4],
color = "orange",
linetype = "dashed",
size = 1.2) +
# Вертикальная линия для верхней границы ДИ (новый цвет: оранжевый)
geom_vline(xintercept = ci$bca[5],
color = "orange",
linetype = "dashed",
size = 1.2) +
# Вертикальная линия для нулевого значения
geom_vline(xintercept = 0,
color = "black",
linetype = "dotted",
size = 0.8) +
# Подписи и заголовок (на русском)
labs(title = "Бутстрап-распределение разницы средних длин чашелистика",
subtitle = paste0("Щетинистый vs Разноцветный (B = ", B, " итераций)"),
x = "Разница средних (Щетинистый - Разноцветный), см",
y = "Плотность") +
# Тема оформления
theme_minimal() +
# Добавляем подписи к линиям (на русском)
annotate("text",
x = point_estimate + 0.05,
y = 2,
label = "Точечная оценка",
color = "blue",
hjust = 0,
size = 3.5) +
annotate("text",
x = ci$bca[4] - 0.05,
y = 1.8,
label = "Нижняя граница ДИ",
color = "orange",
hjust = 1,
size = 3.5) +
annotate("text",
x = ci$bca[5] + 0.05,
y = 1.8,
label = "Верхняя граница ДИ",
color = "orange",
hjust = 0,
size = 3.5)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Вывод дополнительной статистики
cat("РЕЗУЛЬТАТЫ БУТСТРАП-АНАЛИЗА\n")
## РЕЗУЛЬТАТЫ БУТСТРАП-АНАЛИЗА
cat("Точечная оценка (разница средних):", round(point_estimate, 3), "см\n")
## Точечная оценка (разница средних): -0.93 см
cat("Стандартная ошибка (SE):", round(sd(bootstrap_results$t), 3), "см\n")
## Стандартная ошибка (SE): 0.088 см
cat("Смещение (bias):", round(mean(bootstrap_results$t) - point_estimate, 3), "см\n")
## Смещение (bias): -0.001 см
cat("95% доверительный интервал (BCa): [", round(ci$bca[4], 3), ", ", round(ci$bca[5], 3), "] см\n")
## 95% доверительный интервал (BCa): [ -1.103 , -0.754 ] см
# Проверка статистической значимости
has_effect <- !(ci$bca[4] < 0 & ci$bca[5] > 0)
cat("Статистически значимое различие (0 не входит в ДИ):", has_effect, "\n")
## Статистически значимое различие (0 не входит в ДИ): TRUE
В результате выполнения данной лабораторной работы были закреплены навыки в работе с языками R и Python. Также была проведена работа с бутстрапом.