Skip to content

Instantly share code, notes, and snippets.

@dmitriyshashkin
Created September 24, 2018 07:03
Show Gist options
  • Save dmitriyshashkin/a91d61d4e11ed4d99c6a44b87e8aad59 to your computer and use it in GitHub Desktop.
Save dmitriyshashkin/a91d61d4e11ed4d99c6a44b87e8aad59 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from statsmodels.stats.proportion import proportions_ztest\n",
"import statsmodels.stats.power as smp\n",
"from statsmodels.stats.proportion import proportion_effectsize"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Параметры симуляции\n",
"\n",
"Базовая конверси 0.10%, конверсия в улучшенной версии 0.11%. Число пользователей подобрал так, чтобы получить стат мощность 80% (при заданном учлушении до 0.11% обычный z-test покажет стат значимый результат в 80% случае). N рассчитал калькулятором от evan miller"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"conversion_a = 0.0010\n",
"conversion_b = 0.0011\n",
"max_vistors = 1300000\n",
"N = 2922 # https://www.evanmiller.org/ab-testing/sequential.html"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.8010077236088784"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Проверяю стат мощность при таких параметрах\n",
"smp.NormalIndPower().solve_power(proportion_effectsize(conversion_b, conversion_a), nobs1=max_vistors, alpha=0.05, alternative='larger')"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"# эмуляция одного эксперимента\n",
"\n",
"# генерирую результаты для максимально возможного числа пользователей. затем прохожусь по результатам каждого пользователя (как будто он только поучаствовал в эксперименте).\n",
"# после каждого нового пользователя проверяю выполняется ли одно из условий остановки по правилам evan miller'а. \n",
"# в зависимости от того какое из условий выполне засчитываю или нет победу вариации B \n",
"\n",
"def run_seq(hyp): \n",
" group_a = np.random.binomial(1, conversion_a, max_vistors*2)\n",
" \n",
" # для нулевой гипотез конверсия такая как в группе A, для альтернативной - 0.11%\n",
" group_b = np.random.binomial(1, conversion_b if hyp == 'alternative' else conversion_a, max_vistors*2)\n",
"\n",
" C = T = 0\n",
" t_wins = None\n",
"\n",
" for i in range(len(group_a)):\n",
" C = C + group_a[i]\n",
" T = T + group_b[i]\n",
"\n",
" # условие остановки для победы вариации B\n",
" if T - C >= 2 * np.math.sqrt(N):\n",
" t_wins = True\n",
" break\n",
"\n",
" # условие остановки для не-победы вариации B\n",
" if T + C >= N:\n",
" t_wins = False\n",
" break\n",
"\n",
" return [t_wins, i]"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.76800000000000002"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# как часто я присуждаю победу вариации B если она действительно лучше?\n",
"# в идеале должно быть 80%, тк именно это значение необходимой стат мощности я внёс в калькулятор от evan miller\n",
"rA = [run_seq('alternative') for i in range(1000)]\n",
"np.mean([ri[0] for ri in rA])"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1004162.7439999999"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Как средняя продолжительность эксперимента?\n",
"np.mean([ri[1] for ri in rA])"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.77243287999999999"
]
},
"execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# На сколько меньше пользователей мне нужно при таком подходе?\n",
"np.mean([ri[1] for ri in rA]) / max_vistors"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Type 1 error (null is true)"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {},
"outputs": [],
"source": [
"# как часто я присуждаю победу вариации B если она ничем не отличается от вариации A?\n",
"# в идеале должно быть 5%, тк именно это значение alpha я внёс в калькулятор от evan miller\n",
"r0 = [run_seq('null') for i in range(1000)]\n",
"np.mean([ri[0] for ri in r0])"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1446983.767"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Как средняя продолжительность эксперимента?\n",
"np.mean([ri[1] for ri in r0])"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.1130644361538462"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# На сколько больше пользователей мне нужно при таком подходе?\n",
"np.mean([ri[1] for ri in r0]) / max_vistors"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Вывод\n",
"В варианте evan miller нужно меньше пользователей если альтернативная гипотеза верна и больше пользователей если нулевая гипотеза верна. Точность примерно соответствует заданным показателям"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment