Скачать ipynb
lec1_notebook

Phystech@DataScience

Занятие 1. Простейший анализ данных.

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

In [1]:
import numpy as np
import scipy.stats as sps
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(palette="Set2", font_scale=1.3)

%matplotlib inline

Выборка

Пусть нам попались какие-то данные.

В данном случае — 100 различных значений какой-то величины.

In [2]:
size = 100  # размер выборки
sample = sps.norm.rvs(size=size)  # генерируем реализацию выборки из N(0, 1)
In [3]:
sample
Out[3]:
array([ 1.4694579 , -1.16372156, -0.09200531,  2.04196851, -2.19080146,
       -0.80042021, -0.09050994,  0.48966552, -0.6104091 , -0.23999291,
       -1.0702828 ,  2.45909955, -0.76026938,  1.23184359,  0.35449563,
        1.11132924, -0.01478976,  0.90131138,  1.94285115, -0.00437244,
       -1.42938627, -1.583689  , -0.75399546,  0.23257718,  0.46463143,
        2.28289654, -0.63173655, -0.48240007,  0.36558808, -0.33946135,
       -0.69307015, -0.5291172 , -0.26940048, -0.06775259,  1.61811464,
       -0.64247747, -1.25793313, -0.57045423, -0.51259108, -1.84549721,
       -0.0415442 , -0.75694694, -1.16910648, -0.64347649,  1.84542019,
        0.64610333, -1.18691192, -0.53480103, -1.3065839 ,  1.29200288,
        1.39365151, -1.14226594,  2.39410418,  0.54798032, -2.14564358,
       -0.9717628 , -1.12055401,  0.56010414,  0.33707968, -1.18463322,
       -1.00506209, -0.8777773 ,  0.58858336,  0.89087633, -0.83548504,
        1.07012296,  0.56872595, -0.12620446, -0.57501254,  0.16505806,
        1.23542741,  0.70491768,  1.23909053, -0.54949278, -0.00982067,
        0.56001076,  1.37450317,  0.66161703,  0.51650303,  0.66760551,
        0.20649633, -1.46794604,  1.3694449 , -1.15321223,  0.85429712,
        1.13053514,  1.00234837,  1.11106   ,  0.74626235, -1.0790917 ,
        0.64532372, -0.47783549,  2.17570268,  1.47756022,  0.4606338 ,
        0.80496987,  2.40936153, -1.45828842,  0.51503181, -1.61893947])

Нарисуем их график. По оси x — значения реализаций случайной величины.

In [4]:
plt.figure(figsize=(15, 2))  # объявляем график и его размер
plt.scatter(sample, np.zeros(size), alpha=0.5, color='purple', label="Реализация выборки")
plt.legend()  # добавляем легенду
plt.show()  # печатаем график

Для начала давайте попробуем прикинуть, как распределены данные.

Гистограмма

Идея: разделим всю числовую прямую на несколько "корзин" и посмотрим, сколько объектов попало в каждую.

Формально:

Пусть $X_1, \ldots X_n$ — выборка.

$-\infty = a_0 < \ldots < a_i < \ldots < a_{m} = +\infty$ — разбиение на $m$ корзин.

$n_k = \sum \limits_{i=0}^{n-1} I(X_i \in [a_k, a_{k+1}])$ — количество элементов в $i$-й корзине.

Можно построить график в виде столбиков, где высота столбика показывает, сколько объектов попало в соответствующую корзину.

Этот график по форме похож на график плотности распределения нашей случайной величины.

Гистограмму можно построить с помощью библиотеки matplotlib.

In [5]:
# Тяжелые графики стоит рисовать на белом фоне
sns.set_style('whitegrid')

plt.figure(figsize=(15, 5))

plt.title("Нормированная гистограмма и график истинной плотности")

# Гистограмма:
# x - данные,
# bins - количество корзин, 
# density - нормируем, так что сумма высот столбиков равна 1
plt.hist(x=sample, bins = 10, density=True, label = "Гистограмма") 

grid = np.linspace(-3, 3, 500)  # сетка из 500 чисел от -3 до 3
plt.plot(grid, sps.norm.pdf(grid), lw=3, color='red', label='Истинная плотность')

plt.legend()

plt.show()

Другой способ

Для построения гистограммы удобно использовать библиотеку seaborn (подробнее — в туториалах).

In [6]:
plt.figure(figsize=(15, 5))

plt.title("Гистограмма")

# data - наши данные, 
# bins - количество корзин
sns.histplot(data=sample, bins=10)

plt.show()

Посмотрим, на что влияет количество корзин.

In [7]:
# Делаем несколько графиков на одной фигуре
fig, axs = plt.subplots(figsize=(20, 5), ncols=3)

axs[0].set_title("4 корзины" )
axs[1].set_title("20 корзин" )
axs[2].set_title("50 корзин" )

sns.histplot(data=sample, bins=4, ax=axs[0])
sns.histplot(data=sample, bins=10, ax=axs[1])
sns.histplot(data=sample, bins=30, ax=axs[2])

plt.show()

Ядерная оценка плотности

Функция kdeplot позволяет легко строить ядерные оценки плотности.

In [8]:
# Легкие графики стоит рисовать на сером фоне
sns.set_style('darkgrid')

plt.figure(figsize=(15, 5))

plt.title("Ядерная оценка плотности")

sns.kdeplot(data=sample, lw=3, label="Ядерная оценка")

plt.scatter(sample, np.zeros(size)+0.005, color='purple', label='Реализация выборки')

plt.legend()
plt.show()

Сравним график ядерной оценки с графиком истинной плотности.

In [9]:
plt.figure(figsize=(15, 5))

plt.title("Ядерная оценка плотности")

sns.kdeplot(data=sample, lw=3, label="Ядерная оценка")

plt.scatter(sample, np.zeros(size)+0.005, color='purple', label='Реализация выборки')

plt.plot(grid, sps.norm.pdf(grid), lw=3, color='crimson', label='Истинная плотность')

plt.legend()
plt.show()

Также метод histplot позволяет сразу строить и гистограмму, и ядерную оценку плотности.

In [10]:
# Тяжелые графики стоит рисовать на белом фоне
sns.set_style('whitegrid')

plt.figure(figsize=(15, 5))

plt.title("Гистограмма и ядерная оценка")

sns.histplot(data=sample, kde=True)
plt.show()

Точечные оценки

Рассмотрим выборочное среднее.

$\overline{X} = \frac1n \sum\limits_{i=1}^n X_i$

Сгенерируем выборку из $\mathcal{N}(3, 25)$ размера 1000:

In [11]:
size = 1000  # размер выборки
sample = sps.norm(loc=3, scale=5).rvs(size=size)

Посчитаем выборочное среднее

In [12]:
mean = sample.mean()

print(f"Выборочное среднее: {mean:.4}")
Выборочное среднее: 2.762

Что хорошего может нам дать эта величина?

Давайте посмотрим, как она ведет себя с ростом размера выборки!

$X_1, X_2, .....X_j, ... X_n$

In [12]:
 

Посчитаем выборочное среднее по всем подвыборкам $X_1, ..., X_j$ для $j \in \{1, \ldots, n\}$:

$\left(\overline{X}\right)_j = \frac1j \sum\limits_{i=1}^j X_i$

In [13]:
sample.cumsum() [:10]
Out[13]:
array([ 0.27056126, -1.72903326,  2.41310232, 12.18448146, 10.67177173,
       18.78481844, 16.30887698, 14.66257492, 35.05795531, 36.10807508])
In [14]:
means = sample.cumsum() / (np.arange(size) + 1)  # кумулятивное усреднение

Построим график зависимости среднего от размера префикса:

In [15]:
# Легкие графики стоит рисовать на сером фоне
sns.set_style('darkgrid')

plt.figure(figsize=(15, 5))
plt.plot(means, lw=3)
plt.hlines(3, 0, size, alpha=0.5, linestyles='--')
plt.xlabel('Количество случайных величин')
plt.ylabel('Значение среднего')
plt.xlim((0, size));

Но одного эксперимента мало, чтобы понять свойства вероятностных объектов. Запомните это!

Повторим эксперимент 10 раз независимо.

In [16]:
plt.figure(figsize=(20, 15))

for i in range(10):

    # Генерация выборки и вычисление средних
    sample = sps.norm(loc=3, scale=5).rvs(size=size)
    means = sample.cumsum() / (np.arange(size) + 1)
    
    # График
    plt.subplot(5, 2, i+1)
    plt.plot(means, lw=3)
    plt.hlines(3, 0, size, alpha=0.5, linestyles='--')
    plt.xlabel('Количество случайных величин')
    plt.ylabel('Значение среднего')
    plt.xlim((-5, size))
    
plt.tight_layout()

Как видим, со временем график среднего приближается к значению параметра $a$ нормального распределения.

Вспомним теорию вероятностей!

Закон больших чисел

Формулировка.

Пусть $\xi_1, ..., \xi_n$ — независимые одинаково распределенные случайные величины из некоторого распределения, причем $\mathsf{E}\xi_i = \mu$. Тогда выполнена сходимость $$\frac{\xi_1 + ... + \xi_n}{n} \stackrel{п.н.}{\longrightarrow} \mu.$$

Вспомнимаем, что для нормального распределения $\mathsf{E}\xi_i = a$, то есть согласно ЗБЧ $$\overline{X} = \frac{X_1 + ... + X_n}{n} \stackrel{п.н.}{\longrightarrow} a.$$

Здесь $\overline{X} $ обозначает последовательность средних.

Сгенерируем большое количество независимых наборов случайных величин

In [17]:
size = 1000  # размер выборки
samples_count = 500  # количество выборок

sample = sps.norm(loc=3, scale=5).rvs(size=(samples_count, size))  # генерация сразу всех выборок
means = sample.cumsum(axis=1) / (np.arange(size) + 1)  # кумулятивное усреднение внутри каждой выборки

Нарисуем траектории среднего для всех реализациий на одном графике.

In [18]:
plt.figure(figsize=(15, 7))

# для каждой выборки рисуем отдельную кривую
for i in range(samples_count):
    plt.plot(np.arange(size) + 1, means[i], color='crimson', alpha=0.01)

plt.xlabel('Количество случайных величин')
plt.ylabel('Значение среднего')

# ограничения графика по осям
plt.xlim((0, size))
plt.ylim((-4, 10));

Вывод:

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

Таким образом, в данном случае выборочное среднее — "хорошая оценка", т.к. с ростом выборки его значение приближается к истинному значению параметра.

Это эксперимент намекает нам, что некоторые оценки могут быть "хорошими" в каком-лмбо смысле. О том, какие у оценок бывают "хорошие" свойства — на следующих занятиях!