722 lines
72 KiB
Text
722 lines
72 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# A/B test monitoring\n",
|
|
"\n",
|
|
"## Initial setup\n",
|
|
"This first section just ensures that the connection to DWH works correctly."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 30,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import pathlib\n",
|
|
"import yaml\n",
|
|
"import pandas as pd\n",
|
|
"import numpy as np\n",
|
|
"from sqlalchemy import create_engine\n",
|
|
"import seaborn as sns\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"from statsmodels.stats.proportion import proportions_ztest\n",
|
|
"from scipy import stats\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 31,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"/home/joaquin/.superhog-dwh/credentials.yml\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"CREDS_FILEPATH = pathlib.Path.home() / \".superhog-dwh\" / \"credentials.yml\"\n",
|
|
"print(CREDS_FILEPATH)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 32,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Prepare connection to DWH\n",
|
|
"# Function to read credentials from the YAML file\n",
|
|
"def read_credentials(yaml_path: str, env: str = \"prd\"):\n",
|
|
" with open(yaml_path, \"r\") as file:\n",
|
|
" credentials = yaml.safe_load(file)\n",
|
|
" return credentials[\"envs\"][env]\n",
|
|
"# Function to create a PostgreSQL connection string\n",
|
|
"def create_postgres_engine(creds: dict):\n",
|
|
" user = creds[\"user\"]\n",
|
|
" password = creds[\"password\"]\n",
|
|
" host = creds[\"host\"]\n",
|
|
" port = creds[\"port\"]\n",
|
|
" database = creds[\"database\"]\n",
|
|
" # Create the connection string for SQLAlchemy\n",
|
|
" connection_string = f\"postgresql://{user}:{password}@{host}:{port}/{database}\"\n",
|
|
" engine = create_engine(connection_string)\n",
|
|
" return engine\n",
|
|
"# Function to execute a query and return the result as a pandas DataFrame\n",
|
|
"def query_to_dataframe(engine, query: str):\n",
|
|
" with engine.connect() as connection:\n",
|
|
" df = pd.read_sql(query, connection)\n",
|
|
" return df\n",
|
|
"dwh_creds = read_credentials(yaml_path=CREDS_FILEPATH, env=\"prd\")\n",
|
|
"dwh_pg_engine = create_postgres_engine(creds=dwh_creds)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 33,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" ?column?\n",
|
|
"0 1\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Silly query to test things out\n",
|
|
"test_df = query_to_dataframe(engine=dwh_pg_engine, query=\"SELECT 1;\")\n",
|
|
"print(test_df.head())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## A/B test configuration\n",
|
|
"In this section we configure the parameters for the A/B test. Likely you do NOT need to change anything else than this, unless of course you want to create new metrics and so on.\n",
|
|
"\n",
|
|
"The parameters to be specified are:\n",
|
|
"* **ab_test_name**: this should be the name of the feature flag corresponding to the A/B test. If you don't know the name, ask Guest Squad\n",
|
|
"* **var_A** and **var_B**: these correspond to the name of the variants. At this moment, we can only handle univariant testing (though updating the code to include multivariant testing should not be extremely difficult). In general, choose var_A to be the Control group."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 34,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# A/B test name to measure\n",
|
|
"#ab_test_name = \"AAVariantTest\"\n",
|
|
"ab_test_name = \"ShowNewIllustrations\"\n",
|
|
"\n",
|
|
"# Define the variations in which we want to run the tests\n",
|
|
"var_A = 'OldIllustrations' # Ideally, this should be the control group\n",
|
|
"var_B = 'NewIllustrations' # Ideally, this should be the study group\n",
|
|
"\n",
|
|
"variations = [var_A, var_B]\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Data Extraction\n",
|
|
"In this section we extract the data from the Guest Journey monitoring within DWH by configuring which A/B test we want to measure. Here we already handle the basic aggregations that will be needed in the future, directly in SQL."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 35,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" ab_test_name variation last_update guest_journeys_count \\\n",
|
|
"0 ShowNewIllustrations NewIllustrations 2025-02-26 474 \n",
|
|
"1 ShowNewIllustrations OldIllustrations 2025-02-26 466 \n",
|
|
"\n",
|
|
" guest_journey_started_count guest_journey_completed_count \\\n",
|
|
"0 474 274 \n",
|
|
"1 466 255 \n",
|
|
"\n",
|
|
" guest_journey_with_responses_count guest_journey_with_payment_count \\\n",
|
|
"0 98 160 \n",
|
|
"1 102 140 \n",
|
|
"\n",
|
|
" guest_revenue_count deposit_count ... \\\n",
|
|
"0 160 25 ... \n",
|
|
"1 140 26 ... \n",
|
|
"\n",
|
|
" guest_revenue_avg_per_guest_journey guest_revenue_sdv_per_guest_journey \\\n",
|
|
"0 8.706173 14.797536 \n",
|
|
"1 7.277096 12.552086 \n",
|
|
"\n",
|
|
" deposit_avg_per_guest_journey deposit_sdv_per_guest_journey \\\n",
|
|
"0 0.347666 1.642609 \n",
|
|
"1 0.382324 1.689702 \n",
|
|
"\n",
|
|
" waiver_avg_per_guest_journey waiver_sdv_per_guest_journey \\\n",
|
|
"0 8.242979 14.719363 \n",
|
|
"1 6.748036 12.430776 \n",
|
|
"\n",
|
|
" check_in_cover_avg_per_guest_journey check_in_cover_sdv_per_guest_journey \\\n",
|
|
"0 0.115528 1.025914 \n",
|
|
"1 0.146735 1.118852 \n",
|
|
"\n",
|
|
" csat_avg_per_guest_journey_with_response \\\n",
|
|
"0 3.959184 \n",
|
|
"1 3.754902 \n",
|
|
"\n",
|
|
" csat_sdv_per_guest_journey_with_response \n",
|
|
"0 0.962368 \n",
|
|
"1 0.958921 \n",
|
|
"\n",
|
|
"[2 rows x 26 columns]\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Query to extract data\n",
|
|
"data_extraction_query = \"\"\"\n",
|
|
"select \n",
|
|
"\tab_test_name,\n",
|
|
"\tvariation,\n",
|
|
"\tmax(first_appearance_date_utc) as last_update,\n",
|
|
" \n",
|
|
" -- SIMPLE COUNTS --\n",
|
|
"\tcount(id_verification_request) as guest_journeys_count,\n",
|
|
"\tcount(verification_started_date_utc) as guest_journey_started_count,\n",
|
|
"\tcount(verification_completed_date_utc) as guest_journey_completed_count,\n",
|
|
"\tcount(experience_rating) as guest_journey_with_responses_count,\n",
|
|
"\tcount(last_payment_paid_date_utc) as guest_journey_with_payment_count,\n",
|
|
"\tcount(guest_revenue_without_taxes_in_gbp) as guest_revenue_count,\n",
|
|
"\tcount(deposit_fees_without_taxes_in_gbp) as deposit_count,\n",
|
|
"\tcount(waiver_fees_without_taxes_in_gbp) as waiver_count,\n",
|
|
"\tcount(check_in_cover_fees_without_taxes_in_gbp) as check_in_cover_count,\n",
|
|
" \n",
|
|
" -- SIMPLE SUMS --\n",
|
|
"\tsum(guest_revenue_without_taxes_in_gbp) as guest_revenue_sum,\n",
|
|
"\tsum(deposit_fees_without_taxes_in_gbp) as deposit_sum,\n",
|
|
"\tsum(waiver_fees_without_taxes_in_gbp) as waiver_sum,\n",
|
|
"\tsum(check_in_cover_fees_without_taxes_in_gbp) as check_in_cover_sum,\n",
|
|
" \n",
|
|
" -- AVGs/SDVs PER GUEST JOURNEY (ANY GJ APPEARING IN THE A/B TEST) --\n",
|
|
" -- NOTE THE COALESCE HERE. THIS IS IMPORTANT FOR THE T-TEST COMPUTATION --\n",
|
|
" avg(coalesce(guest_revenue_without_taxes_in_gbp,0)) as guest_revenue_avg_per_guest_journey,\n",
|
|
" stddev(coalesce(guest_revenue_without_taxes_in_gbp,0)) as guest_revenue_sdv_per_guest_journey,\n",
|
|
" avg(coalesce(deposit_fees_without_taxes_in_gbp,0)) as deposit_avg_per_guest_journey,\n",
|
|
" stddev(coalesce(deposit_fees_without_taxes_in_gbp,0)) as deposit_sdv_per_guest_journey,\n",
|
|
" avg(coalesce(waiver_fees_without_taxes_in_gbp,0)) as waiver_avg_per_guest_journey,\n",
|
|
" stddev(coalesce(waiver_fees_without_taxes_in_gbp,0)) as waiver_sdv_per_guest_journey,\n",
|
|
" avg(coalesce(check_in_cover_fees_without_taxes_in_gbp,0)) as check_in_cover_avg_per_guest_journey,\n",
|
|
" stddev(coalesce(check_in_cover_fees_without_taxes_in_gbp,0)) as check_in_cover_sdv_per_guest_journey,\n",
|
|
" \n",
|
|
" -- AVGs/SDVs PER GUEST JOURNEY WITH CSAT RESPONSE --\n",
|
|
" -- NOTE THAT THERE'S NO COALESCE HERE. THIS IS IMPORTANT FOR THE T-TEST COMPUTATION --\n",
|
|
" avg(experience_rating) as csat_avg_per_guest_journey_with_response,\n",
|
|
" stddev(experience_rating) as csat_sdv_per_guest_journey_with_response\n",
|
|
" \n",
|
|
"from\n",
|
|
"\tintermediate.int_core__ab_test_monitoring_guest_journey\n",
|
|
"where\n",
|
|
"\tab_test_name = '{}'\n",
|
|
" and first_appearance_at_utc >= '2025-02-25 09:45:00'\n",
|
|
"group by\n",
|
|
"\t1,2\n",
|
|
"\"\"\".format(ab_test_name)\n",
|
|
"\n",
|
|
"# Retrieve Data from Query\n",
|
|
"df = query_to_dataframe(engine=dwh_pg_engine, query=data_extraction_query)\n",
|
|
"print(df.head())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Check A/B test Allocation to Variation"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 36,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAf0AAAIYCAYAAABnrTUkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/GU6VOAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB/i0lEQVR4nO3dd3hTZcMG8DtJ0733nnRAS9l7CbIEBHHiABTU1wHu/Sn6uhf6KqIITgQRZYoCMmSvUlYptKXQCR2Ulu4283x/xEZCOqHtybh/19ULenKSPGmSc59nnOeRCIIggIiIiCyeVOwCEBERUedg6BMREVkJhj4REZGVYOgTERFZCYY+ERGRlWDoExERWQmGPhERkZVg6BMREVkJhj4REZGVYOi30aFDh/Dqq69iwoQJ6NevH+Lj4zFgwADcfvvtePPNN7F//35Y2ySHa9asQWxsLEaNGiV2UagJ9957L2JjY/Hxxx+3av+3334bsbGxeOihhzq0XC+99BJiY2OxZs2aDn2eK8XGxiI2NrbTnq89TJ48GbGxsUhISMDly5eb3bepv2nD9/Sll17qyKKKYtSoUYiNjcX58+fFLorJY+i3UllZGWbPno0ZM2bgt99+Q3V1NXr37o3x48ejZ8+eKCsrw/Lly/HAAw/g1ltvFbu4bXLo0CHExsZi+vTpYheFOsjtt98OAFi3bh00Gk2z+yqVSmzYsMHgfuZi+vTpiI2NxaFDh8QuSrtJSUlBRkYGAEClUuH3338XuUSdS4wTQ0tmI3YBzEFlZSXuueceZGdnIzIyEq+//joGDhxotN+ZM2fwww8/YOPGjSKUkqhp48ePx9tvv42SkhLs3r0bI0eObHLf7du3o7y8HJ6enh3eevPMM8/goYcegq+vb4c+z5XM7fu5atUqAICfnx+Ki4uxatUqzJw5U+RSmZYffvgBKpUKfn5+YhfF5LGm3wpvvfUWsrOzERISgl9++aXRwAeAmJgYvPvuu1i6dGknl5CoeQ4ODpg4cSIAtFhjarh98uTJkMvlHVouX19fREVFwcXFpUOf50pRUVGIiorqtOe7HnV1dfjzzz8BAB9++CEcHR1x5swZpKSkiFwy0xIaGoqoqKgO/7xaAoZ+C/Ly8vDHH38AAF5++WW4ubm1eJ/ExESjbS31ObXUhHXgwAHMmTMHQ4cORUJCAgYNGoTHH38cx44da3T/nJwcvPzyyxg1ahQSEhLQq1cvjBw5Eg8//DBWr16t32/69OmYMWMGACApKUnf39meffRFRUV46623MHbsWHTv3h19+vTBtGnT8MsvvzTa1LxgwQLExsZiwYIFjT5eU90RV26vq6vDZ599hptuugk9evTQv5Yr+zVra2sxf/58jBkzBgkJCRgyZAhefPFFFBcXN/laiouL8d577+kft1evXrjtttuwbNkyqNVqg33vu+8+xMbG6j8/jVmyZAliY2Px5JNPNrlPe2loqt+xYwfKysoa3ae4uBj79u0z2L+srAxLly7FQw89hFGjRiExMRG9e/fGrbfeisWLF0OhUDT6WFf2na9evRp33XUX+vTpY/A9aOpzX11djV9//RVz5szB2LFj0bNnT/Ts2RM333wzPv30U1RWVhrs3/DeJyUlAQBmzJhh8Fm+8vGb69MvLy/HJ598gokTJ+rf31tvvRVLlixBfX290f5XfuZUKhUWL16MiRMnIjExEQMGDMCcOXNw7ty5Rp+rNTZv3ozq6mrExMRg4MCBmDBhAoB/a//tKSUlBU8++aTBMeaRRx7Rfx6acuDAATzxxBMYPnw4EhISMHDgQNx22234/PPPDcYfqFQqrF+/Hs8++yzGjx+P3r17IzExEePGjcPbb79t9L07f/48YmNjsXbtWgC64++V7+mVx4fmjq91dXVYvHgxpk6dil69eqFHjx6YOHEiPv30U1RUVBjt3/C8o0aNgiAIWLlyJW699Vb07NkTffr0waxZs5o87poDNu+3YMeOHdBqtXBzc2u2SbQjffDBB/juu+8glUqRkJCAPn36oLCwENu3b8eOHTvw1ltv4bbbbtPvf+bMGdx9992orq5GREQERo4cCalUiuLiYhw+fBjFxcX6/YcNGwZbW1vs3bsX3t7eGDZsmP5xPDw8rrvsKSkpeOihh1BeXo7AwECMHj0aVVVVSEpKwrFjx7B161Z89dVXsLW1ve7naqBQKDB9+nScO3cOffv2RVxcHMrLyw32qaqqwrRp01BYWIg+ffogOjoax48fx7p163D48GGsX7/eqPZ5+PBhPP7446ioqEBQUBAGDx4MpVKJkydP4q233sKOHTuwaNEifW1jxowZOHz4MJYtW4ZJkyYZlVOr1WLFihUAdCcIHS0xMRExMTE4c+YMfv/9d9x///1G+6xduxYajQY9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VoKhT0REZCUY+kRERFaCoU9ERGQlGPpERERWgqFPRERkJRj6REREVoKhT0REZCUY+kRERFaCoU9ERGQlGPpERERWgqFPRERkJRj6REREVoKhT0REZCUY+kRERFaCoU9ERGQlGPpERERWgqFPRERkJRj6REREVoKhT0REZCUY+kRERFaCoU9ERGQlGPpERERWgqFPRERkJRj6REREVoKhT0REZCUY+kRERFaCoU9ERGQlGPpERERWgqFPRERkJUw+9CUSCdatWyd2MYiIiMxeq0NfIpE0+/PGG280ed+cnBxIJBIcP368HYpsrKioCHPnzkVkZCTs7OwQEhKCm2++Gdu3b++Q52vO9ZykLFy4EF27doWDgwNiY2OxdOnSJvf95ZdfIJFIcMsttxhsFwQB8+bNQ0BAABwcHDB69GhkZmZeU3mIiMiy2LR2x8LCQv3/V65ciXnz5iEjI0O/zdnZuX1L1ko5OTkYMmQI3N3d8dFHH6F79+5QqVT466+/8PjjjyM9PV2UcrXVV199hZdffhlLlixBv379kJSUhIceeggeHh64+eabDfbNycnBc889h2HDhhk9zocffojPP/8cP/74IyIiIvDaa69h3LhxOH36NOzt7Tvr5RARkSkSrsH3338vuLm56X/XaDTCf//7XyEoKEiwtbUVevToIWzatEl/OwCDnxEjRgiCIAhJSUnC6NGjBS8vL8HV1VUYPny4cOTIEYPnAiCsXbu2ybLcdNNNQlBQkFBdXW102+XLl/X/z83NFSZPniw4OTkJLi4uwh133CEUFRXpb585c6YwZcoUg/s/+eST+rIKgiCMGDFCmDt3rvD8888LHh4egp+fn/D666/rbw8LCzN4nWFhYU2W+2qDBg0SnnvuOYNtzzzzjDBkyBCDbWq1Whg8eLDwzTffGJVZq9UK/v7+wkcffaTfVl5eLtjZ2QkrVqxodVmIrEl9fb3wzjvvCOvWrWv09uXLlwv5+fnCjh07hA8//FD46quvhK+++kpYvHixkJeXZ7T/4cOH9fu8//77wvz58/W/Z2VltalsO3bsEFQqVav2raurE1avXi0sXLhQ+PLLL4WFCxcKKSkpgiAIwrFjxzrsGJCdnS288cYbws6dO/XbiouLhU8//bTdnuPTTz8VCgsLBUHQ5U9aWpogCIKwdu1a4cCBA9f8uGlpaUJ+fv413//Y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",
|
|
"text/plain": [
|
|
"<Figure size 800x600 with 1 Axes>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Ensure Seaborn styling\n",
|
|
"sns.set_theme(style=\"whitegrid\")\n",
|
|
"\n",
|
|
"# Calculate the total guest_journeys_count per variation\n",
|
|
"grouped_data = df.groupby('variation')['guest_journeys_count'].sum()\n",
|
|
"\n",
|
|
"# Find the total count and other metadata\n",
|
|
"total_count = grouped_data.sum()\n",
|
|
"ab_test_name = df['ab_test_name'].iloc[0] # Assuming all rows are for the same A/B test\n",
|
|
"last_update = df['last_update'].max()\n",
|
|
"\n",
|
|
"# Create a pie chart using Seaborn styling\n",
|
|
"plt.figure(figsize=(8, 6))\n",
|
|
"colors = sns.color_palette(\"pastel\") # Seaborn pastel colors\n",
|
|
"\n",
|
|
"# Pie chart with labels inside each sector\n",
|
|
"plt.pie(\n",
|
|
" grouped_data, \n",
|
|
" labels=[f\"{var}\\n{count} ({count/total_count:.1%})\" for var, count in grouped_data.items()],\n",
|
|
" autopct=None, \n",
|
|
" colors=colors, \n",
|
|
" startangle=90,\n",
|
|
" wedgeprops={'edgecolor': 'none'}, # Remove edges around sectors\n",
|
|
" pctdistance=0.70, # Places the labels closer to the center (inside)\n",
|
|
" labeldistance=0.2 # Ensure labels are positioned inside the sectors\n",
|
|
")\n",
|
|
"\n",
|
|
"# Add title\n",
|
|
"plt.title(\"Guest Journey - Variation Allocation\", fontsize=16)\n",
|
|
"\n",
|
|
"# Add total count to the bottom-left\n",
|
|
"plt.text(-1.4, -1.3, f\"Total Count: {total_count}\", fontsize=10, ha='left', color='black')\n",
|
|
"\n",
|
|
"# Add A/B test name and last update to the bottom-right\n",
|
|
"plt.text(1.2, -1.3, f\"A/B Test: {ab_test_name}\", fontsize=8, ha='right', color='gray')\n",
|
|
"plt.text(1.2, -1.4, f\"Last Update: {last_update}\", fontsize=8, ha='right', color='gray')\n",
|
|
"\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Statistical Analysis\n",
|
|
"In this section we compute the metrics needed for monitoring as well as check if there's any statistical difference between the different variations."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Z-test for Proportion Metrics (Rates)\n",
|
|
"This section defines the functions used to compute Z-test Proportion analysis"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 37,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Generalized function to calculate Z-test for any metric\n",
|
|
"def calculate_z_test(df, metric_name, variation_a, variation_b, success_counts, total_counts):\n",
|
|
"\n",
|
|
" # Aggregate the success counts (numerator) and total counts (denominator) for each variation\n",
|
|
" success_a = df[df['variation'] == variation_a][success_counts].sum()\n",
|
|
" success_b = df[df['variation'] == variation_b][success_counts].sum()\n",
|
|
"\n",
|
|
" total_a = df[df['variation'] == variation_a][total_counts].sum()\n",
|
|
" total_b = df[df['variation'] == variation_b][total_counts].sum()\n",
|
|
"\n",
|
|
" # Calculate conversion rates for each variation\n",
|
|
" value_A = success_a / total_a if total_a != 0 else 0\n",
|
|
" value_B = success_b / total_b if total_b != 0 else 0\n",
|
|
"\n",
|
|
" # Absolute difference (B - A)\n",
|
|
" abs_diff = value_B - value_A\n",
|
|
"\n",
|
|
" # Relative difference (B - A) / A\n",
|
|
" rel_diff = (value_B - value_A) / value_A if value_A != 0 else 0\n",
|
|
"\n",
|
|
" # Perform the z-test for proportions\n",
|
|
" count = [success_a, success_b] # Success counts for A and B\n",
|
|
" nobs = [total_a, total_b] # Total counts for A and B\n",
|
|
" \n",
|
|
" # Calculate z-stat and p-value\n",
|
|
" z_stat, p_value = proportions_ztest(count, nobs)\n",
|
|
" \n",
|
|
" # Flag for significance at 95% level (p-value < 0.05)\n",
|
|
" is_significant = p_value < 0.05\n",
|
|
"\n",
|
|
" # Return the result as a dictionary\n",
|
|
" return {\n",
|
|
" 'metric': metric_name,\n",
|
|
" 'variation_A_name': variation_a,\n",
|
|
" 'variation_B_name': variation_b,\n",
|
|
" 'variation_A_value': value_A,\n",
|
|
" 'variation_B_value': value_B,\n",
|
|
" 'absolute_difference': abs_diff,\n",
|
|
" 'relative_difference': rel_diff,\n",
|
|
" 'statistic': z_stat,\n",
|
|
" 'p_value': p_value,\n",
|
|
" 'is_significant_95': is_significant\n",
|
|
" }\n",
|
|
"\n",
|
|
"# Function to run Z-tests for multiple metrics and aggregate results into a DataFrame\n",
|
|
"def run_z_tests(df, z_stat_metric_definition, variations):\n",
|
|
" results = []\n",
|
|
" \n",
|
|
" # Loop over all metrics in z_stat_metric_definition\n",
|
|
" for metric_name, metric_definition in z_stat_metric_definition.items():\n",
|
|
" success_counts = metric_definition['success_counts']\n",
|
|
" total_counts = metric_definition['total_counts']\n",
|
|
" \n",
|
|
" # Run the Z-test for each metric\n",
|
|
" result = calculate_z_test(df, metric_name, variation_a=variations[0], variation_b=variations[1], \n",
|
|
" success_counts=success_counts, total_counts=total_counts)\n",
|
|
" results.append(result)\n",
|
|
" \n",
|
|
" # Create a DataFrame from the results\n",
|
|
" results_df = pd.DataFrame(results)\n",
|
|
" \n",
|
|
" return results_df"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### T-test for non-proportion metrics\n",
|
|
"This section defines the functions used to compute T-tests for metrics outside of the proportion scope, mostly Revenue-related metrics."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 38,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"\n",
|
|
"# Generalized function to calculate T-test for revenue-related metrics\n",
|
|
"def calculate_t_test(df, metric_name, variation_a, variation_b, metric_avg_column, metric_sdv_column, total_counts):\n",
|
|
" # Aggregate the avgs and standard deviations for each variation\n",
|
|
" mean_a = df[df['variation'] == variation_a][metric_avg_column].mean() # Assuming the avg is calculated for each group\n",
|
|
" mean_b = df[df['variation'] == variation_b][metric_avg_column].mean() # Assuming the avg is calculated for each group\n",
|
|
" \n",
|
|
" sdv_a = df[df['variation'] == variation_a][metric_sdv_column].mean() # Assuming the stddev is calculated for each group\n",
|
|
" sdv_b = df[df['variation'] == variation_b][metric_sdv_column].mean() # Assuming the stddev is calculated for each group\n",
|
|
" \n",
|
|
" total_a = df[df['variation'] == variation_a][total_counts].sum()\n",
|
|
" total_b = df[df['variation'] == variation_b][total_counts].sum()\n",
|
|
"\n",
|
|
" # Absolute difference (B - A)\n",
|
|
" abs_diff = mean_b - mean_a\n",
|
|
"\n",
|
|
" # Relative difference (B - A) / A\n",
|
|
" rel_diff = (mean_b - mean_a) / mean_a if mean_a != 0 else 0\n",
|
|
"\n",
|
|
" # Calculate the T-statistic and p-value using the formula for two-sample T-test\n",
|
|
" se_a = sdv_a / (total_a ** 0.5) if total_a != 0 else 0\n",
|
|
" se_b = sdv_b / (total_b ** 0.5) if total_b != 0 else 0\n",
|
|
"\n",
|
|
" # Standard error of the difference between the means\n",
|
|
" se_diff = (se_a ** 2 + se_b ** 2) ** 0.5\n",
|
|
" \n",
|
|
" # T-statistic formula\n",
|
|
" if se_diff != 0:\n",
|
|
" t_stat = (mean_a - mean_b) / se_diff\n",
|
|
" else:\n",
|
|
" t_stat = 0\n",
|
|
" \n",
|
|
" # Degrees of freedom (for independent samples)\n",
|
|
" df_degrees = min(total_a - 1, total_b - 1) # Using the smaller of the two sample sizes minus 1\n",
|
|
" \n",
|
|
" # P-value from the T-distribution\n",
|
|
" p_value = stats.t.sf(abs(t_stat), df_degrees) * 2 # Two-tailed test\n",
|
|
" \n",
|
|
" # Flag for significance at 95% level (p-value < 0.05)\n",
|
|
" is_significant = p_value < 0.05\n",
|
|
"\n",
|
|
" # Return the result as a dictionary\n",
|
|
" return {\n",
|
|
" 'metric': metric_name,\n",
|
|
" 'variation_A_name': variation_a,\n",
|
|
" 'variation_B_name': variation_b,\n",
|
|
" 'variation_A_value': mean_a,\n",
|
|
" 'variation_B_value': mean_b,\n",
|
|
" 'absolute_difference': abs_diff,\n",
|
|
" 'relative_difference': rel_diff,\n",
|
|
" 'statistic': t_stat,\n",
|
|
" 'p_value': p_value,\n",
|
|
" 'is_significant_95': is_significant\n",
|
|
" }\n",
|
|
"\n",
|
|
"# Function to run T-tests for multiple revenue metrics and aggregate results into a DataFrame\n",
|
|
"def run_t_tests(df, t_stat_metric_definition, variations):\n",
|
|
" results = []\n",
|
|
" \n",
|
|
" # Loop over all metrics in t_stat_metric_definition\n",
|
|
" for metric_name, metric_definition in t_stat_metric_definition.items():\n",
|
|
" metric_avg_column = metric_definition['metric_avg_column']\n",
|
|
" metric_sdv_column = metric_definition['metric_sdv_column']\n",
|
|
" total_counts = metric_definition['total_counts']\n",
|
|
" \n",
|
|
" # Run the T-test for each metric\n",
|
|
" result = calculate_t_test(df, metric_name, variation_a=variations[0], variation_b=variations[1], \n",
|
|
" metric_avg_column=metric_avg_column, metric_sdv_column=metric_sdv_column, \n",
|
|
" total_counts=total_counts)\n",
|
|
" results.append(result)\n",
|
|
" \n",
|
|
" # Create a DataFrame from the results\n",
|
|
" results_df = pd.DataFrame(results)\n",
|
|
" \n",
|
|
" return results_df"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Specify the metric definition for Z-stat and T-stat tests"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 39,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Define the Z-test metric definitions (with both success_counts and total_counts)\n",
|
|
"z_stat_metric_definition = {\n",
|
|
" 'conversion_rate': {\n",
|
|
" 'success_counts': 'guest_journey_completed_count',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" },\n",
|
|
" 'payment_rate': {\n",
|
|
" 'success_counts': 'guest_journey_with_payment_count',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" },\n",
|
|
" 'waiver_payment_rate': {\n",
|
|
" 'success_counts': 'waiver_count',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" },\n",
|
|
" 'deposit_payment_rate': {\n",
|
|
" 'success_counts': 'deposit_count',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" },\n",
|
|
" 'CIH_payment_rate': {\n",
|
|
" 'success_counts': 'check_in_cover_count',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"# Define the T-test metric definitions (with both metric_avg_column and metric_sdv_column)\n",
|
|
"t_stat_metric_definition = {\n",
|
|
" 'avg_guest_revenue_per_gj': {\n",
|
|
" 'metric_avg_column': 'guest_revenue_avg_per_guest_journey',\n",
|
|
" 'metric_sdv_column': 'guest_revenue_sdv_per_guest_journey',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" },\n",
|
|
" 'avg_waiver_revenue_per_gj': {\n",
|
|
" 'metric_avg_column': 'waiver_avg_per_guest_journey',\n",
|
|
" 'metric_sdv_column': 'waiver_sdv_per_guest_journey',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" },\n",
|
|
" 'avg_deposit_revenue_per_gj': {\n",
|
|
" 'metric_avg_column': 'deposit_avg_per_guest_journey',\n",
|
|
" 'metric_sdv_column': 'deposit_sdv_per_guest_journey',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" },\n",
|
|
" 'avg_CIH_revenue_per_gj': {\n",
|
|
" 'metric_avg_column': 'check_in_cover_avg_per_guest_journey',\n",
|
|
" 'metric_sdv_column': 'check_in_cover_sdv_per_guest_journey',\n",
|
|
" 'total_counts': 'guest_journeys_count'\n",
|
|
" },\n",
|
|
" 'avg_csat_per_gj_with_response': {\n",
|
|
" 'metric_avg_column': 'csat_avg_per_guest_journey_with_response',\n",
|
|
" 'metric_sdv_column': 'csat_sdv_per_guest_journey_with_response',\n",
|
|
" 'total_counts': 'guest_journey_with_responses_count'\n",
|
|
" }\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"# Define the metrics that will be the main ones for this A/B test:\n",
|
|
"main_metrics = ['avg_guest_revenue_per_gj', 'conversion_rate', 'payment_rate']"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Run the computation of the metrics and statistical significance"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 40,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" metric relative_difference p_value\n",
|
|
"0 conversion_rate 0.056375 0.340432\n",
|
|
"1 payment_rate 0.123568 0.222161\n",
|
|
"2 waiver_payment_rate 0.164224 0.162833\n",
|
|
"3 deposit_payment_rate -0.054690 0.836406\n",
|
|
"4 CIH_payment_rate -0.262658 0.568235\n",
|
|
"5 avg_guest_revenue_per_gj 0.196380 0.110789\n",
|
|
"6 avg_waiver_revenue_per_gj 0.221537 0.092980\n",
|
|
"7 avg_deposit_revenue_per_gj -0.090651 0.750024\n",
|
|
"8 avg_CIH_revenue_per_gj -0.212677 0.656160\n",
|
|
"9 avg_csat_per_gj_with_response 0.054404 0.136008\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Call the function to calculate the Z-test for each metric and aggregate the results\n",
|
|
"z_test_results_df = run_z_tests(df, z_stat_metric_definition=z_stat_metric_definition, variations=variations)\n",
|
|
"\n",
|
|
"# Call the function to calculate the T-test for each metric and aggregate the results\n",
|
|
"t_test_results_df = run_t_tests(df, t_stat_metric_definition=t_stat_metric_definition, variations=variations)\n",
|
|
"\n",
|
|
"# Add a new column to identify whether it's from Z-test or T-test\n",
|
|
"z_test_results_df['test_type'] = 'Z-test'\n",
|
|
"t_test_results_df['test_type'] = 'T-test'\n",
|
|
"\n",
|
|
"# Combine the dataframes after adding the 'test_type' column\n",
|
|
"combined_results_df = pd.concat([z_test_results_df, t_test_results_df], ignore_index=True)\n",
|
|
"\n",
|
|
"# Print the main aggregated DataFrame\n",
|
|
"print(combined_results_df[['metric','relative_difference','p_value']])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Results\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 41,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"\n",
|
|
"ShowNewIllustrations results (last updated at 2025-02-26)\n",
|
|
"\n",
|
|
"Total Guest Journeys affected by this A/B test: 940 - Total Guest Revenue: 7517 GBP.\n",
|
|
" Variation OldIllustrations: Guest Journeys 466 (49.6%) - Guest Revenue: 3391 GBP (45.1%).\n",
|
|
" Variation NewIllustrations: Guest Journeys 474 (50.4%) - Guest Revenue: 4126 GBP (54.9%).\n",
|
|
"\n",
|
|
"Main Metrics - Comparing NewIllustrations vs. OldIllustrations.\n",
|
|
"\n",
|
|
"CONVERSION RATE (not significant): 57.8% vs. 54.7% (3.1% ppts.| 5.6%).\n",
|
|
"PAYMENT RATE (not significant): 33.8% vs. 30.0% (3.7% ppts.| 12.4%).\n",
|
|
"AVG GUEST REVENUE PER GJ (not significant): 8.71 vs. 7.28 (1.43 ppts.| 19.6%).\n",
|
|
"\n",
|
|
"Other Metrics\n",
|
|
"\n",
|
|
"WAIVER PAYMENT RATE (not significant): 28.5% vs. 24.5% (4.0% ppts.| 16.4%).\n",
|
|
"DEPOSIT PAYMENT RATE (not significant): 5.3% vs. 5.6% (-0.3% ppts.| -5.5%).\n",
|
|
"CIH PAYMENT RATE (not significant): 1.3% vs. 1.7% (-0.5% ppts.| -26.3%).\n",
|
|
"AVG WAIVER REVENUE PER GJ (not significant): 8.24 vs. 6.75 (1.49 ppts.| 22.2%).\n",
|
|
"AVG DEPOSIT REVENUE PER GJ (not significant): 0.35 vs. 0.38 (-0.03 ppts.| -9.1%).\n",
|
|
"AVG CIH REVENUE PER GJ (not significant): 0.12 vs. 0.15 (-0.03 ppts.| -21.3%).\n",
|
|
"AVG CSAT PER GJ WITH RESPONSE (not significant): 3.96 vs. 3.75 (0.2 ppts.| 5.4%).\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print('\\n{} results (last updated at {})\\n'.format(ab_test_name, last_update))\n",
|
|
"\n",
|
|
"# Get main volume indicators per variation\n",
|
|
"grouped_data = df.groupby('variation')[[\"guest_journeys_count\",\"guest_revenue_sum\"]].sum()\n",
|
|
"\n",
|
|
"# Find the totals over any variation\n",
|
|
"total_count = grouped_data.sum()\n",
|
|
"\n",
|
|
"# Print overall indicators for volumes\n",
|
|
"print('Total Guest Journeys affected by this A/B test: {} - Total Guest Revenue: {} GBP.'.format(int(total_count.loc[\"guest_journeys_count\"]), \n",
|
|
" int(total_count.loc[\"guest_revenue_sum\"])))\n",
|
|
"for var in variations:\n",
|
|
" print(' Variation {}: Guest Journeys {} ({}%) - Guest Revenue: {} GBP ({}%).'.format(\n",
|
|
" var, \n",
|
|
" int(grouped_data.loc[var,'guest_journeys_count']), \n",
|
|
" round(100*(grouped_data.loc[var,'guest_journeys_count']/total_count.loc[\"guest_journeys_count\"]),1),\n",
|
|
" int(grouped_data.loc[var,'guest_revenue_sum']),\n",
|
|
" round(100*(grouped_data.loc[var,'guest_revenue_sum']/total_count.loc[\"guest_revenue_sum\"]),1)\n",
|
|
" ))\n",
|
|
"\n",
|
|
"# Split results whether the metrics are main metrics or not\n",
|
|
"main_metrics_rows = combined_results_df[combined_results_df['metric'].isin(main_metrics)]\n",
|
|
"other_metrics_rows = combined_results_df[~combined_results_df['metric'].isin(main_metrics)]\n",
|
|
"\n",
|
|
"def print_metrics(df, header=None):\n",
|
|
" if header:\n",
|
|
" print(f'\\n{header}\\n')\n",
|
|
"\n",
|
|
" for row in df.iterrows():\n",
|
|
" metric = row[1]['metric'].upper().replace('_', ' ')\n",
|
|
" if row[1]['test_type'] == 'Z-test':\n",
|
|
" value_a = str(round(100 * row[1]['variation_A_value'], 1)) + '%'\n",
|
|
" value_b = str(round(100 * row[1]['variation_B_value'], 1)) + '%'\n",
|
|
" abs_diff = str(round(100 * row[1]['absolute_difference'], 1)) + '%'\n",
|
|
" else:\n",
|
|
" value_a = str(round(row[1]['variation_A_value'], 2))\n",
|
|
" value_b = str(round(row[1]['variation_B_value'], 2))\n",
|
|
" abs_diff = str(round(row[1]['absolute_difference'], 2))\n",
|
|
" rel_diff = str(round(100 * row[1]['relative_difference'], 1)) + '%'\n",
|
|
" stat_sign = row[1]['is_significant_95']\n",
|
|
"\n",
|
|
" if stat_sign:\n",
|
|
" print(f\"{metric} - SIGNIFICANT RESULT: {value_b} vs. {value_a} ({abs_diff} ppts.| {rel_diff}).\")\n",
|
|
" else:\n",
|
|
" print(f\"{metric} (not significant): {value_b} vs. {value_a} ({abs_diff} ppts.| {rel_diff}).\")\n",
|
|
"\n",
|
|
"# Print main metrics\n",
|
|
"print_metrics(main_metrics_rows, header=\"Main Metrics - Comparing {} vs. {}.\".format(var_B, var_A))\n",
|
|
"\n",
|
|
"# Print other metrics\n",
|
|
"print_metrics(other_metrics_rows, header=\"Other Metrics\")\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "venv",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"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.12.3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|