SkillForge — результати A/B-тесту нового онбордингу¶
Автор: Дмитро Купріянов
Період експерименту: 01–20 серпня 2025 року
Короткий підсумок. Новий онбординг «Миттєве занурення» показав вищу конверсію в преміум-підписку: 43,13% у A проти 64,73% у B. Абсолютна різниця становить +21,60 п.п., відносний приріст — +50,08%. Різниця статистично значуща (p-значення < 0,001); 95% довірчий інтервал різниці B − A: [+19,65; +23,52] п.п.
Основний розрахунок охоплює користувачів із визначеною групою та відомим підсумковим статусом конверсії. Аналіз чутливості показує збереження переваги B за крайніх сценаріїв для невідомих статусів. Рекомендація — розглядати B для поетапного впровадження після перевірки трекінгу та контролю додаткових бізнес-метрик.
1. Підключення до бази даних
↑ До змістуДо панелі навігації
Підключимося до PostgreSQL та завантажимо підготовлені дані для подальшого A/B аналізу.
import pandas as pd
from sqlalchemy import create_engine, text
from sqlalchemy.engine import URL
from getpass import getpass
password = getpass("Пароль PostgreSQL: ")
db_url = URL.create(
drivername="postgresql+psycopg2",
username="postgres",
password=password,
host="localhost",
port=5432,
database="skillforge")
engine = create_engine(db_url)
with engine.connect() as conn:
result = conn.execute(text("SELECT current_database(), current_user"))
print(result.fetchone())
with engine.connect() as conn:
df_users = pd.read_sql(
text("SELECT * FROM v_user_conversion"),conn)
df_users.head()
df_users.shape
df_users.info()
df_users.duplicated().sum()
df_users.isna().sum()
df_users['user_id'].duplicated().sum()
Із PostgreSQL завантажено 9 946 користувачів із визначеними групами A/B. Пропущені значення збережено для подальшого аналізу. Дублікатів і повторень user_id не виявлено. Дані підготовлені для подальшого аналізу.
with engine.connect() as conn:
df_daily = pd.read_sql(
text("SELECT * FROM v_daily_agg"),conn)
df_daily.head()
df_daily.shape
df_daily.isna().sum()
df_daily.duplicated().sum()
df_daily.info()
df_daily['date'] = pd.to_datetime(df_daily['date'])
df_daily = df_daily.sort_values(by = ['date','group_label']).reset_index(drop = True)
Дані з v_daily_agg успішно завантажено. Отримано 40 записів за 20 днів для двох груп A/B. Типи даних перевірено, дати впорядковано для подальшого аналізу.
2. Опис експерименту та гіпотези
Мета: перевірити, чи підвищує функція «Миттєве занурення» конверсію користувачів у преміум-підписку.
Група A (контрольна): стандартний процес після реєстрації.
Група B (тестова): автоматичний перехід до першого інтерактивного уроку.
Основна метрика: Конверсія — частка користувачів, які оформили преміум-підписку протягом тесту, серед користувачів із відомим підсумковим статусом.
Правило підсумкового статусу:
1— є хоча б один записconverted = 1;NULL— немає підтвердженої конверсії, але є невідомий щоденний статус, або немає жодного записуdaily;0— усі наявні щоденні статуси відомі та дорівнюють нулю.
Нульова гіпотеза (H₀): нова функція «Миттєве занурення» не підвищує конверсію користувачів у преміум-підписку.
Альтернативна гіпотеза (H₁): нова функція «Миттєве занурення» підвищує конверсію користувачів у преміум-підписку.
Рівень значущості: α = 0.05.
3. Огляд вибірки
↑ До змістуДо панелі навігації
Перевіримо кількість користувачів у групах A/B та розподіл статусів конверсії перед статистичним аналізом.
df_users
df_users['group_label'].value_counts()
group_stats = df_users.groupby('group_label')['conversion_status'].value_counts(dropna = False).reset_index()
group_stats['percent'] = (group_stats['count'] / group_stats.groupby('group_label')['count'].transform('sum') * 100).round(2)
group_stats.rename(columns={'group_label':'Група', 'conversion_status':'Статус конверсії', 'count':'Користувачі', 'percent':'Частка, %'})
У групі A виявлено 144 користувачі з невідомим статусом конверсії (2.88%), у групі B — 86 (1.74%). Пропуски розподілені нерівномірно, тому їхній вплив на результат A/B тесту перевіримо окремо.
До 230 невідомих статусів входять 224 користувачі з невизначеним результатом за наявними записами та 6 без записів daily (2 у A і 4 у B).
4. Конверсія та приріст
↑ До змістуДо панелі навігації
Розрахуємо конверсію для груп A та B, абсолютну різницю та відносний приріст конверсії.
4.1. Конверсія за групами
df_users_conver = df_users.groupby('group_label').agg(total_users=('user_id', 'size'),known_users = ('conversion_status','count'),
conversion_count = ('conversion_status','sum'))
df_users_conver['conversion_rate'] = (df_users_conver['conversion_count'] / df_users_conver['known_users'] * 100).round(2)
df_users_conver.rename(columns={'total_users':'Усього користувачів', 'known_users':'Користувачі з відомим статусом','conversion_count':'Конверсії',
'conversion_rate':'Конверсія, %'}).rename_axis('Група')
Конверсія в групі A становить 43.13%, у групі B — 64.73%. Нова функція показує вищу конверсію, проте статистичну значущість різниці ще потрібно перевірити
4.2. Абсолютний та відносний приріст
crn_a = df_users_conver.loc['A','conversion_count'] / df_users_conver.loc['A','known_users']
crn_b = df_users_conver.loc['B','conversion_count'] / df_users_conver.loc['B','known_users']
absolute_lift = (crn_b - crn_a) * 100
relative_lift = ((crn_b - crn_a) / crn_a) * 100
print(f'Абсолютний приріст: {absolute_lift:.2f} п.п.')
print(f'Відносний приріст: {relative_lift:.2f}%')
Конверсія групи B вища за групу A на 21.60 процентного пункта, що відповідає відносному приросту 50.08%. Результат свідчить про потенційну ефективність нової функції, проте статистичну значущість ще потрібно перевірити.
4.3. Графік конверсії
↑ До змістуДо панелі навігації
Конверсія A/B серед користувачів із відомим підсумковим статусом. Використано вже розраховані crn_a, crn_b та absolute_lift.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 5))
rates = [crn_a * 100, crn_b * 100]
bars = ax.bar(['A', 'B'], rates, color=['#3568A8', '#D7792A'], width=0.5)
for bar, rate in zip(bars, rates):
ax.text(bar.get_x() + bar.get_width() / 2, rate + 1.5,
f'{rate:.2f}%'.replace('.', ','), ha='center', fontweight='bold')
ax.set_ylim(0, 100)
ax.set_title('Конверсія в преміум-підписку — SkillForge\n01–20 серпня 2025 року')
ax.set_xlabel('Група експерименту')
ax.set_ylabel('Конверсія, %')
ax.text(0.98, 0.94, f'Приріст B відносно A: +{absolute_lift:.2f} п.п.'.replace('.', ',', 1),
transform=ax.transAxes, ha='right', va='top')
ax.grid(axis='y', alpha=0.2)
ax.set_axisbelow(True)
fig.tight_layout()
plt.show()
5. Статистична значущість
↑ До змістуДо панелі навігації
Перевіримо статистичну значущість різниці конверсій між групами A та B за допомогою двовибіркового z-тесту для пропорцій при рівні значущості α = 0.05.
from statsmodels.stats.proportion import proportions_ztest
count = [df_users_conver.loc['B', 'conversion_count'],
df_users_conver.loc['A', 'conversion_count']]
nobs = [df_users_conver.loc['B', 'known_users'],
df_users_conver.loc['A', 'known_users']]
z_stat, p_value = proportions_ztest(
count=count,
nobs=nobs,
alternative='larger')
print(f'Z-статистика: {z_stat:.4f}')
print(f'p-значення: {p_value:.3e}')
alpha = 0.05
if p_value < alpha:
print('Відхиляємо H0. Різниця статистично значуща.')
else:
print('Не відхиляємо H0. Статистичних доказів недостатньо.')
За результатами одностороннього Z-тесту для двох пропорцій отримано Z-статистика = 21.3566 та p-значення < 0.001. Оскільки p-значення < 0.05, нульову гіпотезу відхиляємо.
Різниця конверсій між групами A (43.13%) та B (64.73%) є статистично значущою. Результати свідчать на користь вищої конверсії в тестовій групі B. Остаточний висновок щодо ефективності нової функції зробимо після перевірки впливу пропущених значень.
6. Довірчий інтервал 95%
↑ До змістуДо панелі навігації
Розрахуємо 95% довірчий інтервал для абсолютної різниці конверсій між групами B та A, щоб оцінити точність отриманого ефекту.
from statsmodels.stats.proportion import confint_proportions_2indep
ci_low, ci_high = confint_proportions_2indep(
count1=count[0],
nobs1=nobs[0],
count2=count[1],
nobs2=nobs[1],
compare='diff',
method='newcomb',
alpha=0.05)
print(f'95% довірчий інтервал абсолютного приросту: [{ci_low * 100:.2f}; {ci_high * 100:.2f}] п.п.')
Висновок: Абсолютний приріст конверсії групи B відносно A становить +21.60 п.п. 95% довірчий інтервал для різниці конверсій знаходиться в межах від +19.65 до +23.52 п.п.
Оскільки довірчий інтервал повністю вищий за нуль, результати підтверджують статистично значущу перевагу групи B. Водночас перед остаточними бізнес-рекомендаціями необхідно перевірити вплив пропущених значень конверсії.
7. Аналіз чутливості
↑ До змістуДо панелі навігації
Перевіримо, наскільки пропущені значення conversion_status можуть вплинути на результати A/B-тесту.
У попередніх розрахунках користувачів із невідомим статусом було виключено. Оскільки частка пропусків відрізняється між групами A (2.88%) та B (1.74%), проведемо аналіз чутливості за трьома сценаріями:
- Основний сценарій: виключаємо користувачів із невідомим статусом, як в основному аналізі.
- Найгірший сценарій для B: усі невідомі статуси в групі A вважаємо конверсіями, а в групі B — неконверсіями.
- Найкращий сценарій для B: усі невідомі статуси в групі B вважаємо конверсіями, а в групі A — неконверсіями.
Для кожного сценарію розрахуємо конверсію обох груп та абсолютний приріст, щоб перевірити стійкість отриманих результатів.
7.1. Найгірший сценарій для групи B
# Загальна кількість користувачів у кожній групі
total_users = group_stats.groupby('group_label')['count'].sum()
# Кількість користувачів із невідомим статусом
missing = (group_stats[group_stats['conversion_status'].isna()].set_index('group_label')['count'])
# Підтверджені конверсії
conversions = df_users_conver['conversion_count']
# Найгірший сценарій для B
crn_a_worst = (conversions['A'] + missing['A']) / total_users['A'] * 100
crn_b_worst = conversions['B'] / total_users['B'] * 100
absolute_lift_worst = crn_b_worst - crn_a_worst
print(f'CR A: {crn_a_worst:.2f}%')
print(f'CR B: {crn_b_worst:.2f}%')
print(f'Абсолютний приріст: {absolute_lift_worst:.2f} п.п.')
7.2. Найкращий сценарій для групи B
# Найкращий сценарій для B
crn_a_case = conversions['A'] / total_users['A'] * 100
crn_b_case = (conversions['B'] + missing['B']) / total_users['B'] * 100
absolute_lift_case = crn_b_case - crn_a_case
print(f'CR A: {crn_a_case:.2f}%')
print(f'CR B: {crn_b_case:.2f}%')
print(f'Абсолютний приріст: {absolute_lift_case:.2f} п.п.')
7.3. Порівняння сценаріїв
table_scenario = pd.DataFrame({
'Сценарій': ['Основний сценарій', 'Найгірший сценарій для B', 'Найкращий сценарій для B'],
'Конверсія A, %': [crn_a * 100, crn_a_worst, crn_a_case],
'Конверсія B, %': [crn_b * 100, crn_b_worst, crn_b_case],
'Абсолютний приріст (п.п.)': [
absolute_lift,
absolute_lift_worst,
absolute_lift_case]}).round(2)
table_scenario
Висновок: Аналіз чутливості показав, що за всіх розглянутих сценаріїв конверсія групи B залишається вищою за групу A.
Абсолютний приріст становить від +18.84 п.п. у найгіршому сценарії до +23.45 п.п. у найкращому.
Отже, навіть за максимально несприятливого розподілу пропущених значень перевага групи B зберігається. Це свідчить про стійкість спостережуваного результату до невідомих статусів конверсії.
8. Аналіз сегментів
↑ До змістуДо панелі навігації
Проаналізуємо конверсію груп A та B за пристроями й країнами, щоб оцінити ефективність нового онбордингу в різних сегментах.
Для кожного сегмента розрахуємо кількість користувачів із відомим статусом конверсії, конверсію та абсолютний приріст. Пропущені статуси конверсії виключаємо, як і в основному аналізі.
8.1. Конверсія за пристроями
df_users_con_dev = df_users.groupby(['device_name','group_label'], dropna = False).agg(conversion_count = ('conversion_status','sum'),
known_users = ('conversion_status','count'))
df_users_con_dev = df_users_con_dev.unstack('group_label')
df_users_con_dev['crn_a'] = df_users_con_dev[('conversion_count', 'A')] / df_users_con_dev[('known_users', 'A')] * 100
df_users_con_dev['crn_b'] = df_users_con_dev[('conversion_count', 'B')] / df_users_con_dev[('known_users', 'B')] * 100
df_users_con_dev['absolute_lift'] = df_users_con_dev['crn_b'] - df_users_con_dev['crn_a']
(
df_users_con_dev.round(2)
.rename(columns={
'conversion_count': 'Конверсії',
'known_users': 'Користувачі з відомим статусом',
'crn_a': 'Конверсія A, %',
'crn_b': 'Конверсія B, %',
'absolute_lift': 'Абсолютний приріст, п.п.'
}, level=0)
.rename_axis(index='Пристрій', columns=[None, 'Група'])
)
8.2. Конверсія за країнами
df_users_con_cont = df_users.groupby(['country_code','group_label'], dropna = False).agg(conversion_count = ('conversion_status','sum'),
known_users = ('conversion_status','count'))
df_users_con_cont = df_users_con_cont.unstack('group_label')
df_users_con_cont['crn_a'] = df_users_con_cont[('conversion_count', 'A')] / df_users_con_cont[('known_users', 'A')] * 100
df_users_con_cont['crn_b'] = df_users_con_cont[('conversion_count', 'B')] / df_users_con_cont[('known_users', 'B')] * 100
df_users_con_cont['absolute_lift'] = df_users_con_cont['crn_b'] - df_users_con_cont['crn_a']
(
df_users_con_cont.round(2)
.rename(columns={
'conversion_count': 'Конверсії',
'known_users': 'Користувачі з відомим статусом',
'crn_a': 'Конверсія A, %',
'crn_b': 'Конверсія B, %',
'absolute_lift': 'Абсолютний приріст, п.п.'
}, level=0)
.rename_axis(index='Країна', columns=[None, 'Група'])
)
8.3. Висновки сегментного аналізу
↑ До змістуДо панелі навігації
Сегментний аналіз проведено для перевірки ефективності нового онбордингу серед користувачів різних пристроїв та країн.
За пристроями: група B демонструє вищу конверсію в усіх категоріях. Найбільший Абсолютний приріст спостерігається для Tablet (+25.33 п.п.), а Mobile (+21.66 п.п.) і Desktop (+21.32 п.п.) мають схожі результати.
За країнами: у всіх п'яти основних країнах конверсія групи B також вища за A. Найбільший приріст зафіксовано у BR (+24.81 п.п.), найменший — у FR (+15.64 п.п.).
Для Smartwatch, TV та країни XX результати слід інтерпретувати обережно через малі вибірки. Категорії з невідомим пристроєм або країною не можна однозначно віднести до конкретних сегментів.
Загальний висновок: перевага нового онбордингу спостерігається не лише на рівні загальної конверсії, а й у всіх основних досліджених сегментах. Це додатково підтримує позитивну оцінку тестового варіанта B. Водночас відмінності у величині приросту між сегментами є описовими та потребують окремої статистичної перевірки.
9. Тижневі тренди
↑ До змістуДо панелі навігації
Проаналізуємо щотижневу динаміку використання SkillForge, щоб визначити, як змінюється активність користувачів протягом експерименту та чи відрізняються тренди між групами A і B.
Для аналізу використаємо щоденні агреговані дані з v_daily_agg. Розділимо 20 днів експерименту на три періоди (7, 7 і 6 днів) та порівняємо середню кількість користувачів із записами за день для кожної групи.
9.1. Порівняння періодів експерименту
↑ До змістуДо панелі навігації
Використаємо кількість користувачів із записами daily. Порівняємо середню кількість таких користувачів за день у періодах 01–07.08, 08–14.08 та 15–20.08 (7, 7 і 6 днів), а також поденні графіки. Це показник наявності щоденних записів, а не основний CR у преміум-підписку.
start_date = df_daily['date'].min()
df_daily['experiment_week'] = ((df_daily['date'] - start_date).dt.days // 7 + 1)
df_daily.head(20)
weekly_stats = (df_daily.groupby(['experiment_week', 'group_label']).agg(days = ('date', 'nunique'),
total_user_days = ('cnt_users', 'sum')) .reset_index())
weekly_stats['avg_users_per_day'] = (weekly_stats['total_user_days'] / weekly_stats['days'])
weekly_stats.round(2).rename(columns={'experiment_week':'Тиждень', 'group_label':'Група', 'days':'Днів', 'total_user_days':'Користувачо-дні', 'avg_users_per_day':'Середня кількість за день'})
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
for group, data in weekly_stats.groupby('group_label'):
plt.plot(
data['experiment_week'],
data['avg_users_per_day'],
marker='o',
linewidth=2,
label=f'Група {group}')
plt.title('Тижнева динаміка користувачів — A/B-тест')
plt.xlabel('Тиждень експерименту')
plt.ylabel('Середня кількість користувачів за день')
plt.xticks([1, 2, 3])
plt.legend()
plt.grid(alpha=0.3)
plt.show()
9.2. Щоденні коливання
days_stats = df_daily[['date', 'group_label', 'cnt_users']].copy()
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
plt.figure(figsize=(12, 5))
for group, data in days_stats.groupby('group_label'):
plt.plot(
data['date'],
data['cnt_users'],
marker='o',
linewidth=2,
label=f'Група {group}')
plt.title('Щоденна динаміка користувачів — A/B-тест')
plt.xlabel('Дата')
plt.ylabel('Кількість користувачів за день')
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=2))
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%d.%m'))
plt.legend()
plt.grid(alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
9.3. Висновки щодо трендів
↑ До змістуДо панелі навігації
Аналіз показав, що протягом перших двох тижнів середня щоденна кількість користувачів із записами залишалася відносно стабільною. На третьому тижні спостерігається зниження показника в обох групах.
Поденний аналіз виявив повторювану тижневу закономірність: найменша кількість записів припадає на неділі, а найбільша — на четверги. Динаміка груп A та B майже однакова, що свідчить про схожий характер щоденних коливань.
У третьому періоді (15–20 серпня) немає четверга, який має найвищі значення. Інший склад днів тижня може частково пояснювати нижче середнє; сама кількість днів не пояснює зниження середнього показника.
Загалом: на графіках групи A та B демонструють схожий характер щоденних коливань. Статистичну рівність трендів не перевіряли. При цьому показник відображає кількість користувачів із щоденними записами, а не основну конверсію в преміум-підписку.
10. Висновки та бізнес-рекомендації
10.1. Основні результати A/B-тесту
↑ До змістуДо панелі навігації
Метою експерименту SkillForge було визначити, чи підвищує новий онбординг (група B) конверсію користувачів у преміум-підписку порівняно з традиційним варіантом (група A).
За результатами аналізу отримано:
| Метрика | Результат |
|---|---|
| Конверсія A | 43.13% |
| Конверсія B | 64.73% |
| Абсолютний приріст | +21.60 п.п. |
| Відносний приріст | +50.08% |
| Z-статистика | 21.3566 |
| p-значення | < 0.001 |
| 95% довірчий інтервал абсолютного приросту | [+19.65; +23.52] п.п. |
Односторонній Z-тест для двох пропорцій підтвердив статистично значущу різницю між групами (p-значення < 0.05). Нульову гіпотезу відхилено на користь вищої конверсії групи B.
10.2. Практична значущість
↑ До змістуДо панелі навігації
Отриманий приріст конверсії є суттєвим для основної бізнес-метрики — оформлення преміум-підписки.
За умови збереження результатів експерименту новий онбординг потенційно може забезпечувати приблизно 216 додаткових преміум-підписок на кожну 1 000 користувачів із відомим статусом конверсії порівняно з традиційним варіантом.
Водночас для оцінки фінансового ефекту необхідні додаткові дані про дохід від підписок, витрати на впровадження та утримання користувачів.
10.3. Додаткові результати та обмеження
↑ До змістуДо панелі навігації
Аналіз чутливості. Перевірка впливу невідомих статусів конверсії показала, що перевага групи B зберігається за всіх розглянутих сценаріїв. Абсолютний приріст становить від +18.84 до +23.45 п.п. Це свідчить про стійкість спостережуваної переваги до можливих значень пропущених статусів.
Сегментний аналіз. Вища конверсія групи B спостерігається на основних типах пристроїв та в усіх п'яти основних країнах. Найбільший Абсолютний приріст серед пристроїв зафіксовано для Tablet (+25.33 п.п.), серед основних країн — для BR (+24.81 п.п.). Результати малочисельних сегментів потребують обережної інтерпретації.
Тижнева динаміка. Групи A та B демонструють схожі коливання кількості користувачів зі щоденними записами. Помітна повторювана тижнева закономірність, однак наявні дані не дозволяють однозначно оцінити вплив онбордингу на активність користувачів.
Обмеження аналізу: основні статистичні розрахунки виконано для користувачів із відомим статусом конверсії. Також не проводилася окрема статистична перевірка відмінностей ефекту між сегментами. Тому результати сегментного аналізу слід розглядати як описові.
10.4. Бізнес-рекомендації
↑ До змістуДо панелі навігації
На основі отриманих результатів рекомендується:
- Розглядати новий онбординг B як пріоритетний варіант для впровадження, оскільки він демонструє статистично значуще та практично суттєве підвищення конверсії.
- Перед повним запуском перевірити якість даних і відстеження конверсій, зокрема причини пропущених значень та коректність реєстрації преміум-підписок.
- Впроваджувати зміни поетапно, контролюючи конверсію, утримання користувачів, скасування підписок та інші важливі бізнес-показники.
- Продовжити моніторинг за пристроями та країнами, приділяючи увагу сегментам із невеликими вибірками або нетиповими результатами.
10.5. Загальний висновок
↑ До змістуДо панелі навігації
A/B-тест SkillForge показав, що новий онбординг групи B має суттєву перевагу над традиційним варіантом за конверсією у преміум-підписку.
Зростання конверсії на 21.60 п.п. (+50.08% відносно групи A) є статистично значущим і потенційно цінним для бізнесу. Результати додаткових перевірок підтримують цей висновок.
Новий онбординг рекомендовано розглядати для поетапного впровадження після перевірки якості даних і контролю додаткових бізнес-метрик.
Код і збережені узагальнені результати з авторського ноутбука. Приклади індивідуальних записів не публікуються.