1. Датасет

Берём открытый датасет iris. Для бутстрапа будем оценивать медиану переменной Sepal.Length (так работа будет отличаться от предыдущих вариантов, где было среднее и Petal.Length).

data(iris)
head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

2. Небольшой разведочный анализ

2.1 Краткая статистика

summary(iris$Sepal.Length)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   4.300   5.100   5.800   5.843   6.400   7.900

2.2 Гистограмма Sepal.Length

ggplot(iris, aes(x = Sepal.Length)) +
  geom_histogram(bins = 14, fill = "lightcoral") +
  theme_minimal() +
  labs(title = "Распределение Sepal.Length",
       x = "Sepal.Length",
       y = "Частота")

Простое объяснение графика:
Гистограмма показывает, какие значения Sepal.Length встречаются чаще всего.

2.3 Sepal.Length по видам (boxplot)

ggplot(iris, aes(x = Species, y = Sepal.Length)) +
  geom_boxplot(fill = "khaki") +
  theme_minimal() +
  labs(title = "Sepal.Length по видам ириса",
       x = "Species",
       y = "Sepal.Length")

Простое объяснение графика:
Boxplot показывает медиану (линия внутри), разброс (коробка) и возможные выбросы (точки).


3. Бутстрап на Python (оценка медианы)

Бутстрап: много раз выбираем случайную выборку с возвращением и считаем статистику (здесь — медиану).

import numpy as np
import matplotlib.pyplot as plt

# Sepal.Length из iris (значения берем прямо числом для автономности примера)
data = np.array([
  5.1,4.9,4.7,4.6,5.0,5.4,4.6,5.0,4.4,4.9,
  5.4,4.8,4.8,4.3,5.8,5.7,5.4,5.1,5.7,5.1,
  5.4,5.1,4.6,5.1,4.8,5.0,5.0,5.2,5.2,4.7,
  4.8,5.4,5.2,5.5,4.9,5.0,5.5,4.9,4.4,5.1,
  5.0,4.5,4.4,5.0,5.1,4.8,5.1,4.6,5.3,5.0,
  7.0,6.4,6.9,5.5,6.5,5.7,6.3,4.9,6.6,5.2,
  5.0,5.9,6.0,6.1,5.6,6.7,5.6,5.8,6.2,5.6,
  5.9,6.1,6.3,6.1,6.4,6.6,6.8,6.7,6.0,5.7,
  5.5,5.5,5.8,6.0,5.4,6.0,6.7,6.3,5.6,5.5,
  5.5,6.1,5.8,5.0,5.6,5.7,5.7,6.2,5.1,5.7,
  6.3,5.8,7.1,6.3,6.5,7.6,4.9,7.3,6.7,7.2,
  6.5,6.4,6.8,5.7,5.8,6.4,6.5,7.7,7.7,6.0,
  6.9,5.6,7.7,6.3,6.7,7.2,6.2,6.1,6.4,7.2,
  7.4,7.9,6.4,6.3,6.1,7.7,6.3,6.4,6.0,6.9,
  6.7,6.9,5.8,6.8,6.7,6.7,6.3,6.5,6.2,5.9
])

B = 2500
boot_medians = np.empty(B)

for i in range(B):
    sample = np.random.choice(data, size=len(data), replace=True)
    boot_medians[i] = np.median(sample)

median_est = np.median(boot_medians)
ci_low, ci_high = np.percentile(boot_medians, [2.5, 97.5])

plt.hist(boot_medians, bins=30)
plt.axvline(median_est, linestyle='--')
plt.axvline(ci_low, linestyle=':')
plt.axvline(ci_high, linestyle=':')
plt.title("Bootstrap distribution of median (Python)")
plt.xlabel("Median Sepal.Length")
plt.ylabel("Frequency")
plt.show()

median_est, (ci_low, ci_high)
## (np.float64(5.8), (np.float64(5.6), np.float64(6.0)))

Простое объяснение графика:
- Гистограмма показывает распределение бутстрап-медиан.
- Пунктир — “типичное” значение медианы.
- Две линии по краям — 95% доверительный интервал.


4. Бутстрап на R (оценка медианы)

set.seed(42)

x <- iris$Sepal.Length
B <- 2500

boot_medians <- replicate(B, median(sample(x, replace = TRUE)))
median_est <- median(boot_medians)
ci <- quantile(boot_medians, probs = c(0.025, 0.975))

median_est
## [1] 5.8
ci
##  2.5% 97.5% 
##   5.6   6.0

Визуализация в R

hist(boot_medians, breaks = 30,
     col = "lightgray",
     main = "Bootstrap distribution of median (R)",
     xlab = "Median Sepal.Length")

abline(v = median_est, lty = 2)
abline(v = ci[1], lty = 3)
abline(v = ci[2], lty = 3)

Простое объяснение графика:
Линия в центре — оценка медианы по бутстрапу, крайние линии — границы 95% интервала.