首页 > 其他分享 >项目四:AB实验实战

项目四:AB实验实战

时间:2024-05-31 10:58:44浏览次数:20  
标签:实战 AB df 01 实验 user new 2017 page

项目背景

为优化落地页设计并提升转化率,某电商公司计划通过 A/B 测试验证新页面设计。该公司历年转化率平均为13%。
目标将新页面的转化率提高2个百分点至15%。在全面推广前,公司将在一小部分用户群中进行测试,以确认新设计是否达到预期效果。

数据集具体字段如下:

 A/B 测试的基本流程

A/B测试,简单来说,就是同时对比两个或多个版本的网页或应用,以确定哪个版本更能实现特定的业绩目标。这里的关键步骤包括:
1.明确实验目标和指标:首先确定实验的目的,比如我们的案例中是要验证新落地页是否能提升2%的转化率。
2设计实验方案:包括实验的变量、持续时间、样本量以及如何合理地划分实验组和对照组,
3.选择分析方法:常见的方法有描述性统计、假设检验等。
4.执行实验并收集数据:这一步是整个过程中最为关键的,需要按照预定方案详细记录数据。
5.数据分析及假设检验:通过统计方法分析数据,验证假设是否成立。
6.得出结论及建议:根据分析结果,决定是否推广新设计。

实验详细步骤

1.提出假设

对您的网站界面进行重新设计,目标是提高用户的转化率。在投入大量资源前,如何验证这一改变确实有效呢?这时,您需要设定一个科学的假设作为实验的起点。

假设您预期这个新设计能够提升2%的转化率。根据这个目标,您可以选择不同的统计检验来支持您的决策

单尾检验:如果您坚信新设计将优于旧版,那么这种检验可以帮助您验证“新设计的转化率高于旧版”这一具体方向的预期。

双尾检验:如果您不确定新设计是否会带来正面或负面的影响,这种检验将验证新旧设计之间是否存在任何形式的差异。

选择哪种检验取决于您对改变的信心和您希望从实验中得到的信息类型。

2.分组实验

进行AB测试的下一步是将用户随机分配到两个不同的组:
对照组:这些用户将继续使用现有的旧版网页,
实验组:这部分用户将体验新设计的网页,
通过在网站上添加追踪代码,我们可以详细记录用户的行为,比如他们是否完成了购买。这些数据会被转化为简单的数字,0表示没有购买,1表示购买了产品。这样,我们就能计算出各组的平均转化率,直观地看到新旧网页设计在实际使用中的表现如何。

3.计算样本量及实验周期

实验样本量的确定

在设计AB测试时,确定合适的样本量是非常重要的。根据大数定律和中心极限定理,样本量越大,我们对结果的估计就越精确。然而,增加样本量也会增加实验的成本。因此,我们需要计算出进行实验所需的最小样本量,以保证成本效益。
通常我们可以使用以下简化的公式计算样本量:

参数解释: 

确定试验周期

确定试验的运行时间也是实验设计的一个重要部分。试验周期应足够长,以便收集到足够的数据进行有效的分析,但同时也不能过长以避免不必要的资源浪费。试验周期的长短会受到预期流量、用户参与度以及样本量需求的景响。一般来说,你需要根据网站或应用的用户流量来预估实验组和对照组能在多长时间内达到所需的样本量。


在此案例中PA=13%,PB=15%,每个组所需的最小样本量为计算过程如下:

在AB测试和其他统计试验中,当我们计算出“每组所需的最小样本量”时,通常这个数字指的是单个组(比如对照组或实验组)所需的样本量。如果我们有两个组(一个对照组和一个实验组),那么总的参与者数量将是每组所需样本量的两倍。


所以根据上面最小样本量的计算,我们知道此次AB测试至少需要4716*2=9432个用户参与测试,假如该落地页以往每天的平均浏览量为x,则实验周期至少需要的天数为:

试验周期=9432/x 

4.实施AB测试并收集数据

通过第一方或者第三方工具进行测试,收集用户行为数据。

5.数据分析及假设检验

代码实现:ab实验.ipynb

具体步骤如下:

# 导入依赖
import numpy as np
import pandas as pd
import scipy.stats as stats
import statsmodels.stats.api as sms
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

计算样本量

# 设置基本参数
baseline_conversion_rate = 0.13  # 基准转化率
expected_conversion_rate = 0.15  # 期望转化率
statistical_power = 0.8          # 统计功效
significance_level = 0.05        # 显著性水平
control_to_treatment_ratio = 1   # 控制组与处理组的比例

# 计算效果量
effect_size = sms.proportion_effectsize(baseline_conversion_rate, expected_conversion_rate)
print(f"效果量为: {effect_size}")

# 计算所需的样本量
required_sample_size = sms.NormalIndPower().solve_power(
    effect_size,
    power=statistical_power,
    alpha=significance_level,
    ratio=control_to_treatment_ratio
)

# 向上取整以确保有足够的样本量
import math
required_sample_size = math.ceil(required_sample_size)

print(f"需要的最小样本量为: {required_sample_size}")

结果如下:

效果量为: -0.0576728617308947
需要的最小样本量为: 4720

导入数据集

df  = pd.read_csv("ab_data.csv")
df.head()

df.info()

 得到数据集 "ab_data.csv" 的字段描述,一共包含五个列:

数据列用途有效值
user_id用户唯一IDInt64值
timestamp用户访问网页的时间戳-
group在当前的A/B实验中,用户被分为两个主要组别。control组的用户应该访问old_page;treatment组的用户则对应new_page。但是,初始数据中存在一些不准确的行,例如control组用户访问了new_page。['control', 'treatment']
landing_page表示用户访问的是旧页面还是新页面。['old_page', 'new_page']
converted表示用户是否决定为公司的产品付费。这里,1表示用户购买了产品。[0, 1]

 数据清洗

# 1. 检查缺失值
missing_values = df.isnull().sum()
print("每一行的缺失数量为:\n", missing_values)

# 2. 检查并处理重复的行
duplicate_rows = df.duplicated().sum()
print(f"重复行数量为: {duplicate_rows}")
# 删除重复行
df.drop_duplicates(inplace=True)

# 3. 检查"user_id"的重复值
user_id_duplicates = df["user_id"].duplicated().sum()
print(f"重复的user_id数量为: {user_id_duplicates}")

# 显示重复的"user_id"
duplicate_user_ids = df[df["user_id"].duplicated()]["user_id"]
print("重复的user_id为:\n", duplicate_user_ids)

# 选取一个重复的"user_id"进行检查
sample_user_id = 773192  # 示例用户ID
print(f" {sample_user_id}:\n", df[df["user_id"] == sample_user_id])

# 存储所有重复的"user_id"
duplicate_user_ids_to_delete = df[df["user_id"].duplicated(keep=False)]["user_id"].unique()
print("移除所有的重复user_id:\n", duplicate_user_ids_to_delete)

# 删除所有重复的"user_id",保留第一次出现
df_new = df.drop_duplicates(subset='user_id', keep='first')
print("一处重复user_id后的数据集:\n", df_new)

输入以下:

每一行的缺失数量为:
 user_id         0
timestamp       0
group           0
landing_page    0
converted       0
dtype: int64
重复行数量为: 0
重复的user_id数量为: 3894
重复的user_id为:
 2656      698120
2893      773192
7500      899953
8036      790934
10218     633793
           ...  
294308    905197
294309    787083
294328    641570
294331    689637
294355    744456
Name: user_id, Length: 3894, dtype: int64
 773192:
       user_id                   timestamp      group landing_page  converted
1899   773192  2017-01-09 05:37:58.781806  treatment     new_page          0
2893   773192  2017-01-14 02:55:59.590927  treatment     new_page          0
移除所有的重复user_id:
 [767017 656468 773693 ... 921581 742781 638376]
一处重复user_id后的数据集:
         user_id                   timestamp      group landing_page  converted
0        851104  2017-01-21 22:11:48.556739    control     old_page          0
1        804228  2017-01-12 08:01:45.159739    control     old_page          0
2        661590  2017-01-11 16:55:06.154213  treatment     new_page          0
3        853541  2017-01-08 18:28:03.143765  treatment     new_page          0
4        864975  2017-01-21 01:52:26.210827    control     old_page          1
...         ...                         ...        ...          ...        ...
294473   751197  2017-01-03 22:28:38.630509    control     old_page          0
294474   945152  2017-01-12 00:51:57.078372    control     old_page          0
294475   734608  2017-01-22 11:45:03.439544    control     old_page          0
294476   697314  2017-01-15 01:20:28.957438    control     old_page          0
294477   715931  2017-01-16 12:40:24.467417  treatment     new_page          0

[290584 rows x 5 columns]
# 1. 将时间戳转换为日期格式
df_new['date'] = pd.to_datetime(df_new['timestamp']).dt.date

# 打印日期列以检查
print("转换后的日期数据:\n", df_new['date'])

# 2. 检查不同日期的数量
unique_dates_count = len(df_new['date'].unique())
print(f"不同日期的天数:{unique_dates_count}")

# 3. 检查DataFrame的信息,确保数据类型正确对应
print("数据框的详细信息:")
df_new.info()

# 4. 提取并打印日期部分,以确保每条记录正确匹配
df_new['date_from_timestamp'] = df_new['timestamp'].str.split(" ").str[0]

# 打印提取的日期部分
print("从时间戳中提取的日期部分:\n", df_new['date_from_timestamp'])

得到以下输出:

转换后的日期数据:
 0         2017-01-21
1         2017-01-12
2         2017-01-11
3         2017-01-08
4         2017-01-21
             ...    
294473    2017-01-03
294474    2017-01-12
294475    2017-01-22
294476    2017-01-15
294477    2017-01-16
Name: date, Length: 290584, dtype: object
不同日期的天数:23
数据框的详细信息:
<class 'pandas.core.frame.DataFrame'>
Index: 290584 entries, 0 to 294477
Data columns (total 6 columns):
 #   Column        Non-Null Count   Dtype 
---  ------        --------------   ----- 
 0   user_id       290584 non-null  int64 
 1   timestamp     290584 non-null  object
 2   group         290584 non-null  object
 3   landing_page  290584 non-null  object
 4   converted     290584 non-null  int64 
 5   date          290584 non-null  object
dtypes: int64(2), object(4)
memory usage: 15.5+ MB
从时间戳中提取的日期部分:
 0         2017-01-21
1         2017-01-12
2         2017-01-11
3         2017-01-08
4         2017-01-21
             ...    
294473    2017-01-03
294474    2017-01-12
294475    2017-01-22
294476    2017-01-15
294477    2017-01-16
Name: date_from_timestamp, Length: 290584, dtype: object

# 确保control组每个用户看到的都是old_page,treatment组看到的都是new_page
# 移除不一致的数据
df_clean = df_new[
    ((df_new["group"] == "treatment") & (df_new["landing_page"] == "new_page")) |
    ((df_new["group"] == "control") & (df_new["landing_page"] == "old_page"))
]

# 再次生成交叉表以验证清理后的情况
crosstab_result_clean = pd.crosstab(df_clean["group"], df_clean["landing_page"])
print("清理后的交叉表结果:\n", crosstab_result_clean)

# 计算被移除的数据量
removed_entries = len(df_new) - len(df_clean)
print(f"被移除的数据条目数:{removed_entries}")

 得到以下结果:

清理后的交叉表结果:
 landing_page  new_page  old_page
group                           
control              0    144226
treatment       144314         0
被移除的数据条目数:2044

抽样

根据前面最小样本量的计算,我们至少需要每组4720个样本,这里我们选择每组抽样5000个(实际工作中不需要抽样这一步) 

df_new[df_new["group"] == "control"].sample(n = 5000, random_state = 22)

 数据可视化

# 加载数据
#ab_test = pd.read_csv("ab_data.csv")

# 设置中文字体和防止符号显示问题
plt.rcParams["font.family"] = "SimHei"
plt.rcParams["axes.unicode_minus"] = False

# 计算每组的转化率和标准差
conversion_rates = df_new.groupby("group")["converted"].agg(['mean', 'std']).reset_index()
conversion_rates.columns = ["group", "conversion_rate", "std_deviation"]

# 创建可视化
plt.figure(figsize=(8,6), dpi=60)
sns.barplot(x="group", y="conversion_rate", data=conversion_rates, errorbar=None)

# 设置y轴的限制和标签/标题大小
plt.ylim(0, 0.17)
plt.title("分组转化率", fontsize=20)
plt.xlabel("分组", fontsize=15)
plt.ylabel("转化(比例)", fontsize=15)

# 显示图形
plt.show()

从上面的统计数据来看,新旧两版落地页的表现结果非常相近,相比于旧版落地页,新版落地页的转化率略微好一点点。那么,这种差异在统计学上显著么?我们可以直接说,新版落地页更好么?

假设检验

我们分析的最后一步就是假设检验了。那么,具体选择哪一种假设检验呢?在统计学中,当样本容量较大时(一般是大于30),我们可以使用Z检验或者t检验。

在这个案例中,由于我们的样本非常大,所以我们使用Z检验。Python中的statsmodels.stats.proportion模块可以来计算P值和置信区间:

from statsmodels.stats.proportion import proportions_ztest, proportion_confint
# 分离控制组和实验组的转化数据
control_results = df_new[df_new['group'] == 'control']['converted']
treatment_results = df_new[df_new['group'] == 'treatment']['converted']
# 计算每组的样本数量
n_con = control_results.count()
n_treat = treatment_results.count()

# 计算每组的成功次数
successes = [control_results.sum(), treatment_results.sum()]
nobs = [n_con, n_treat]

# 进行Z检验
z_stat, pval = proportions_ztest(successes, nobs=nobs)

# 计算95%置信区间
(lower_con, lower_treat), (upper_con, upper_treat) = proportion_confint(successes, nobs=nobs, alpha=0.05)

# 中文输出结果
print(f'Z统计量: {z_stat:.2f}')
print(f'P值: {pval:.3f}')
print(f'控制组95%置信区间: [{lower_con:.3f}, {upper_con:.3f}]')
print(f'实验组95%置信区间: [{lower_treat:.3f}, {upper_treat:.3f}]')

得出结果如下: 

Z统计量: 1.24
P值: 0.216
控制组95%置信区间: [0.119, 0.122]
实验组95%置信区间: [0.117, 0.121]

结果解释

  1. Z统计量 (1.24): Z统计量表示控制组与实验组之间转化率差异的标准偏差数量。数值1.24表明实验组的转化率略高于控制组,但差异不是非常显著。

  2. P值 (0.216): P值用于评估观察到的数据在零假设(即两组之间没有差异)成立的情况下出现的概率。在这里,P值为0.216,远高于常用的显著性水平0.05。这意味着我们没有足够的证据拒绝零假设,即两组之间的转化率没有显著差异。

  3. 95%置信区间:

    • 控制组的95%置信区间为[0.119, 0.122],这意味着如果重复实验多次,大约95%的实验中,控制组的转化率将落在这个区间内。
    • 实验组的95%置信区间为[0.117, 0.121],情况与控制组相似。

6.结论与建议

A/B测试结果的解读

在最近完成的A/B测试中,我们对比了新旧两版网页设计的表现。测试的核心是检验新版页面是否能显著提升用户的转化率。转化率,简单来说,就是访问者执行我们期望行为(如购买、注册)的比例。
测试结果的统计显著性由P值衡量,而此次我们得到的P值为0.216。在统计学中,如果P值低于预设阈值阿尔法a (本测试中为0.05),我们才认为结果具有统计显著性。显然,P值0.216远高于0.05,意味着新版网页的效果与旧版没有显著差异。换句话说,数据没有给我们足够的信心去断言新设计带来了改进。


关于置信区间的讨论
进一步分析新版页面的性能,我们观察到其置信区间为[0.117,0.121]。这个区间提供了关于新页面转化率的可能范围,而且它涵盖了现行转化率基准线13%。这一发现表明,尽管新页面未能显著提升转化率,但它至少保持了业绩稳定。
然而,我们的目标是将转化率提高至15%,而当前的置信区间并未触及这一目标。这提示我们,如果要达到15%的转化率目标,单靠当前的页面设计可能是不够的。

下一步的策略

虽然这次的A/B测试结果可能令人略感失望,但每一次数据的反馈都是向前迈进的一步。以下是我们可以考虑的几个方向:
1.页面设计再优化:根据用户反馈和行为数据进一步细化设计元素
2.进行更多的A/B测试:测试其他变量组合,看是否能找到更有效的解决方案。
3.深入分析用户行为:使用高级分析工具,如热图和行为流分析,以更深入地理解用户与页面的互动。

标签:实战,AB,df,01,实验,user,new,2017,page
From: https://blog.csdn.net/zfyzfw/article/details/139232334

相关文章

  • 四、zabbix7.0推送告警至钉钉webhook机器人
    一、前提条件1、zabbix服务器能够访问钉钉的服务器,具体说是能访问https://oapi.dingtalk.com/robot/send 2、钉钉的webhook是有安全要求的,我采用的是ip的方式,我的zabbix服务器在内网,我的网络出口有多个固定公网ip,所以这样做省事,其他方式也可以你自己考虑。下图是webhook机器......
  • SAP ABAP 对工作区列遍历或按条件访问
    需要对字段数量多的工作区或动态工作区进行数据处理时,列遍历可使代码更加的简洁高效。(๑¯ω¯๑)重新发一遍,丢合集里示例代码:点击查看代码TYPES:BEGINOFtyp_kna1,kunnrTYPEkna1-kunnr,"客户编号name1TYPEkna1-name1,"送达方名......
  • SAP ABAP 字符串去除重复字符的两种方法
    ABAP里如何去除字符串内的重复字符,在这提供两种方法。第一种是直接对字符串多次循环进行排除,但考虑到性能问题要尽量减少循环次数。第二种是把字符串里字符转成内表一列,去重后拼回一个字符串。方法一示例代码:点击查看代码DATAmarkTYPEc.......
  • WireShark抓包软件的使用 上海商学院 计算机网络 实验作业3
    实验目的(1)熟悉wireShark软件操作界面和操作步骤;(2)学会捕获过滤器的设置方法;(3)学会显示过滤器的设置方法;(4)学会使用捕获报文的统计;(5)分析IP数据报文内容。2.实验要求学生各自应独立完成,严格禁止抄袭;文档命名要求:学号-姓名-专业班级-实验报告号;(示例:12345678-张三-计科191班-......
  • MATLAB求解混合整数线性规划问题(MILP)
    文章目录前言一、混合整数线性规划模型(MILP)的概念二、matlab求解方法1.求解说明2.求解代码总结前言本文主要介绍混合整数线性规划模型(MILP)的概念及利用matlab进行求解。一、混合整数线性规划模型(MILP)的概念线性规划模型(LinearProgramming,LP):LP的定义比较简单......
  • 基于稀疏辅助信号平滑的心电信号降噪方法(Matlab R2021B)
    基于形态成分分析理论(MCA)的稀疏辅助信号分解方法是由信号的形态多样性来分解信号中添加性的混合信号成分,它最早被应用在图像处理领域,后来被引入到一维信号的处理中。在基于MCA稀疏辅助的信号分析模型中,总变差方法TV是其中一个原型,稀疏辅助平滑方法结合并统一了传统的LTI低通滤......
  • Nginx 实战-01-nginx ubuntu(windows WSL2) 安装笔记
    前言大家好,我是老马。很高兴遇到你。我们为java开发者实现了java版本的nginxhttps://github.com/houbb/nginx4j如果你想知道servlet如何处理的,可以参考我的另一个项目:手写从零实现简易版tomcatminicat手写nginx系列如果你对nginx原理感兴趣,可以阅读:从零......
  • 软考高级架构师/分析师论文【论基于架构的软件设计方法/ABSD】
    一、摘要  2020年4月,某互联网公司开始了基础架构管理平台项目的实施,该项目主要为基础架构团队提供基础设施、中间件、负载均衡、任务管理等功能,我作为该项目的架构师,主要负责架构设计、架构评估等工作。本文以该项目为例,主要论述基于架构的软件设计方法在该项目中的具体......
  • hashtable的常用方法
    哈希表的定义和查找方法就不再赘述,此随笔主要写代码中的用法加深自己印象。声明哈希表:#include<unordered_map>unordered_map<eleType_1,eleType_2>var_name;unordered_map<int,int>map;//或者之前用过的charint类型,char为key,int为char的值。后面的变量为两个unordered......
  • Docker 安装RabbitMq
    Docker安装RabbitMq文章目录Docker安装RabbitMq拉取镜像启动进入容器启动后台浏览器访问总结拉取镜像dockerpullrabbitmq启动dockerrun-d--hostnamemy-rabbit--namerabbit-p15672:15672-p5673:5672rabbitmq-d后台运行容器;–name指定......