Опубликовано: 08 сен 2021
В части первой была представлена концепция генерирования данных для построения модели A/B теста. Идея заключается в следующем - если мы можем описать наши реальные данные с помощью модели, мы можем изучать эту модель и проверять правильность наших методик оценки результатов эксперимента.
Disclaimer: эта статья скорее представляет из себя рассуждения и нуждается в дальнейшей проверке.
Давайте посмотрим на то, что можно взять за основу такой модели данных. На мой взгляд — это Probability density function (функция плотности вероятности), которая задаёт отношение вероятностей для непрерывной случайной величины. Другими словами она показывает участки (интервалы), где вероятность попадания случайной величины больше или меньше, и мы можем визуализировать это с помощью кривой.
Чтобы пример с непрерывной случайной величиной был нагляднее, давайте также вспомним, что могла бы представлять из себя функция "плотности" вероятности для дискретной величины (здесь мы не можем говорить о плотности в том же смысле, что и для непрерывной величины, поэтому слово взято в кавычки).
Рассмотрим классический пример игрального кубика (d6).
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.ticker as mtick
Чтобы посчитать вероятность выпадания, к примеру, 3 или 4 (одного из двух) нужно просто просуммировать вероятности всех удовлетворяющих нас результатов: 1/6+1/6=1/3 или ~ 33,33%. Вероятность выпадания любого значения соответственно равна 1/6+1/6+1/6+1/6+1/6+1/6 = 1.
Для непрерывной величины, мы не можем применить простое сложение, но по аналогии мы можем проинтегрировать функцию плотности:
$$ \int_{-\infty}^{\infty} f(x) \; dx = 1. $$
Случайная величина должна принимать некоторое число, соответственно значение интеграла на всем множестве равно 1.
И для любого интервала вероятность попадания случайной величины между a
и b
:
$$ P(X\in(a, b]) = \int _ {a}^{b} f(x) \; dx. $$
Мы можем аппроксимировать это в коде, используя методы дискретной аппроксимации. В этом случае мы можем приблизительно оценить вероятность попадания в закрашенную область:
# Approximate probability using numerical integration
epsilon = 0.01
x = np.arange(-5, 5, 0.01)
#the probability density function for some random variable
p = 0.2*np.exp(-(x - 3)**2 / 2) / np.sqrt(2 * np.pi) + \
0.8*np.exp(-(x + 1)**2 / 2) / np.sqrt(2 * np.pi)
plt.plot(x, p)
plt.fill_between(x.tolist()[300:800], p.tolist()[300:800])
plt.ylabel("Плотность")
plt.xlabel("х")
plt.show()
f'Аппроксимированная вероятность: {np.sum(epsilon*p[300:800])}' # Approximate the integral of the PDF
'Аппроксимированная вероятность: 0.7736171925899021'
#аппроксимированная вероятность выпадания любого значения (в реале = 1)
np.sum(epsilon*p)
0.9953709966191726
Примеры распределений:
Построим PDF (функцию плотности вероятности) нормального распределения. Функция следующая:
$$\mathcal{N}(x \mid \mu, \sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2} } e^{ -\frac{(x-\mu)^2}{2\sigma^2} }$$
Где
PDF с помощью python и matplotlib:
dist_norm = np.random.normal(0,1,1000) # Mean, Standard deviation, Size
import seaborn as sns
g = sns.displot(dist_norm, kind="kde", aspect=5/3)
g.set(ylim=(-0.02, 0.6));
Другой способ создания выборки с помощью модуля stats.norm:
from scipy import stats
Создаем объект dist_norm2
, представляющий нормальное распределение со средним значением 0
и стандартным отклонением 1
.
dist_norm2 = stats.norm(0, 1) # Mean, Standard deviation
И используем его, чтобы нарисовать выборку из 1000 значений, как и в примере выше.
sample = dist_norm2.rvs(1000) # Выборка с размером 1000
sample.mean(), sample.std()
(0.009605850578821618, 1.0040143072264596)
g = sns.displot(sample, kind="kde", aspect=5/3)
g.set(ylim=(-0.02, 0.6));
График PDF самого распределения из которого была сделана выборка:
x = np.linspace(-4,4,80)
plt.plot(x,dist_norm2.pdf(x)); # plot x and y using default line style and color
Общая вероятность на всем поле значений равна 1
(вероятность выпадания любого значения). Этот тезис отражает функция CDF
(comulative dencity). По сути это функция, отражающая вероятность того, что случайная величина X примет значение, меньшее х.
from empiricaldist import Cdf
cdf = Cdf.from_seq(sample)
cdf.plot(label='CDF выборки', lw=5)
xs = np.linspace(-4, 4)
ps = dist_norm2.cdf(xs)
plt.plot(xs, ps, label='CDF Нормального распределения', lw=2)
plt.xlabel("х")
plt.legend();
В этом примере мы знаем, что выборка была взята из нормального распределения, поэтому неудивительно, что кривые почти совпадают. Но если бы выборка была получена из реальных измерений, мы могли бы использовать этот рисунок, чтобы показать, что нормальное распределение хорошая модель для этих данных.
PDF для экспоненциального распределения:
$$ f(x ∣ \lambda) = \begin{cases} \frac{1}{\beta} e^{-\frac{1}{\beta} x} & x \ge 0, \\ 0 & x < 0. \end{cases}$$
где $\beta$ - scale parameter. $\frac{1}{\beta}$ часто называется rate parameter $\lambda$
Построение модели выборки с помощью модуля stats.expon
dist_expon = stats.expon(0, 1) #loc, scale
sample = dist_expon.rvs(1000) # Выборка с размером 1000
x = np.linspace(0,6,61)
g = sns.displot(sample, kind='kde', label='PDF выборки', lw=5, bw_adjust=.35)
g.set(xlim=(0.1, 6))
g.set(ylim=(-0.02,1.02))
plt.plot(x, dist_expon.pdf(x), label='PDF Экспоненциального распределения', lw=2, color='#FF5252')
plt.xlabel("х")
plt.legend();
cdf = Cdf.from_seq(sample)
cdf.plot(label='CDF выборки', lw=5)
plt.plot(x, dist_expon.cdf(x), label='CDF Экспоненциального распределения', lw=2)
plt.xlabel("х")
plt.legend();
Если мы полагаем, что случайная величина $x$ равномерно распределена на промежутке $[a,b]$, то PDF для такого распределния будет следующей функцией:
$$ f(x ∣ a,b) = \begin{cases} \frac{1}{b-a} \quad x \in [a,b] \\ 0 \quad \quad \mathrm{вне \ интервала} \end{cases} $$
Построение модели выборки с помощью модуля stats.uniform
dist_uniform = stats.uniform(-1, 2) #loc, scale
sample = dist_uniform.rvs(1000) # Выборка с размером 1000
x = np.linspace(-3,3,61)
g = sns.displot(sample, kind='kde', label='PDF выборки', lw=5, bw_adjust=.5)
g.set(ylim=(-0.05,1.02))
plt.plot(x, dist_uniform.pdf(x), label='PDF Равномерного распределения', lw=2, color='#FF5252')
plt.xlabel("х")
plt.legend();
cdf = Cdf.from_seq(sample)
cdf.plot(label='CDF выборки', lw=5)
plt.plot(x, dist_uniform.cdf(x), label='CDF Равномерного распределения', lw=2)
plt.xlabel("х")
plt.legend();
Что делать, если данные сложные и не являются явным примером какого-либо стандартного типа распределения? Например, как на графике ниже, это может быть сочитание двух нормальных распределений с разными коэффициентами.
s1 = np.random.normal(-5, 2.0, 1000)
s2 = np.random.normal(5, 3.0, 1000)
all_s = np.concatenate([s1, s2])
sns.displot(all_s, kde=True);
В реальности сложное распределение может быть сочетанием не обязательно двух, но и большего количества распределений разного типа. Описание данного типа моделей выходит за рамки этой статьи.
В целом же мы видим, что процесс создания и использования модели будет состоять из нескольких шагов:
*Стоит сделать оговорку, что для реальных данных PDF может не всегда существовать. CDF - в этом плане более фундаментальна.
Понимание структуры реальных данных участвующих в эксперименте (a/b тесте), их распределения вероятности и возможность моделирования данного распределения - это дополнительные факторы, способствующие правильной оценке полученных результатов.