data-jupyter-notebooks/ab_test_guest_journey_monitoring.ipynb
Oriol Roqué Paniagua b87403d8dc Merged PR 3780: Adding A/B test guest journey monitoring
Adding A/B test guest journey monitoring

Related work items: #25146
2024-12-04 17:24:37 +00:00

707 lines
66 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": 1,
"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": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/home/uri/.superhog-dwh/credentials.yml\n"
]
}
],
"source": [
"CREDS_FILEPATH = pathlib.Path.home() / \".superhog-dwh\" / \"credentials.yml\"\n",
"print(CREDS_FILEPATH)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"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": 4,
"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": [
"## 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": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" ab_test_name variation last_update guest_journeys_count \\\n",
"0 AAVariantTest A 2024-12-04 470 \n",
"1 AAVariantTest B 2024-12-04 478 \n",
"\n",
" guest_journey_started_count guest_journey_completed_count \\\n",
"0 470 284 \n",
"1 478 270 \n",
"\n",
" guest_journey_with_responses_count guest_journey_with_payment_count \\\n",
"0 75 146 \n",
"1 72 143 \n",
"\n",
" guest_revenue_count deposit_count ... \\\n",
"0 146 29 ... \n",
"1 143 24 ... \n",
"\n",
" guest_revenue_avg_per_guest_journey guest_revenue_sdv_per_guest_journey \\\n",
"0 7.836050 13.857327 \n",
"1 7.631339 13.202289 \n",
"\n",
" deposit_avg_per_guest_journey deposit_sdv_per_guest_journey \\\n",
"0 0.496000 2.104774 \n",
"1 0.367761 1.715542 \n",
"\n",
" waiver_avg_per_guest_journey waiver_sdv_per_guest_journey \\\n",
"0 7.180958 13.653492 \n",
"1 7.090684 12.993245 \n",
"\n",
" check_in_cover_avg_per_guest_journey check_in_cover_sdv_per_guest_journey \\\n",
"0 0.159091 1.210487 \n",
"1 0.172894 1.253337 \n",
"\n",
" csat_avg_per_guest_journey_with_response \\\n",
"0 3.853333 \n",
"1 3.972222 \n",
"\n",
" csat_sdv_per_guest_journey_with_response \n",
"0 1.204646 \n",
"1 1.020525 \n",
"\n",
"[2 rows x 26 columns]\n"
]
}
],
"source": [
"# A/B test name to measure\n",
"ab_test_name = \"AAVariantTest\"\n",
"\n",
"# 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",
"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": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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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": 7,
"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": 8,
"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": 22,
"metadata": {},
"outputs": [],
"source": [
"# Define the variations in which we want to run the tests\n",
"var_A = 'A'\n",
"var_B = 'B'\n",
"variations = [var_A, var_B]\n",
"\n",
"# 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": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" metric relative_difference is_significant_95\n",
"0 conversion_rate -0.065207 False\n",
"1 payment_rate -0.036940 False\n",
"2 waiver_payment_rate 0.000072 False\n",
"3 deposit_payment_rate -0.186265 False\n",
"4 CIH_payment_rate 0.106172 False\n",
"5 avg_guest_revenue_per_gj -0.026124 False\n",
"6 avg_waiver_revenue_per_gj -0.012571 False\n",
"7 avg_deposit_revenue_per_gj -0.258547 False\n",
"8 avg_CIH_revenue_per_gj 0.086757 False\n",
"9 avg_csat_per_gj_with_response 0.030854 False\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','is_significant_95']])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Results\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"AAVariantTest results (last updated at 2024-12-04)\n",
"\n",
"Total Guest Journeys affected by this A/B test: 948 - Total Guest Revenue: 7330 GBP.\n",
" Variation A: Guest Journeys 470 (49.6%) - Guest Revenue: 3682 GBP (50.2%).\n",
" Variation B: Guest Journeys 478 (50.4%) - Guest Revenue: 3647 GBP (49.8%).\n",
"\n",
"Main Metrics - Comparing B vs. A.\n",
"\n",
"CONVERSION RATE (not significant): 56.5% vs. 60.4% (-3.9% | -6.5%).\n",
"PAYMENT RATE (not significant): 29.9% vs. 31.1% (-1.1% | -3.7%).\n",
"AVG GUEST REVENUE PER GJ (not significant): 7.63 vs. 7.84 (-0.2 | -2.6%).\n",
"\n",
"Other Metrics\n",
"\n",
"WAIVER PAYMENT RATE (not significant): 24.9% vs. 24.9% (0.0% | 0.0%).\n",
"DEPOSIT PAYMENT RATE (not significant): 5.0% vs. 6.2% (-1.1% | -18.6%).\n",
"CIH PAYMENT RATE (not significant): 1.9% vs. 1.7% (0.2% | 10.6%).\n",
"AVG WAIVER REVENUE PER GJ (not significant): 7.09 vs. 7.18 (-0.09 | -1.3%).\n",
"AVG DEPOSIT REVENUE PER GJ (not significant): 0.37 vs. 0.5 (-0.13 | -25.9%).\n",
"AVG CIH REVENUE PER GJ (not significant): 0.17 vs. 0.16 (0.01 | 8.7%).\n",
"AVG CSAT PER GJ WITH RESPONSE (not significant): 3.97 vs. 3.85 (0.12 | 3.1%).\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} | {rel_diff}).\")\n",
" else:\n",
" print(f\"{metric} (not significant): {value_b} vs. {value_a} ({abs_diff} | {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"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"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
}