本人从事互联网产品数据分析工作,本项目解决某证券用户交流平台的自激活APP到首次付费的核心路径问题。
付费转化是任何一家互联网公司都必须面对的“流量-->收益”核心问题,付费问题可分为首次付费和复购。
不同的产品,付费模式不同,受到的影响因素不同。证券类收费产品,会受到外界因素如政策、大盘等影响,同时又会受到服务提供者(投顾)水平的影响,其中投顾水平是核心因素,主要表现为对投顾的判断是否精准。
而首次付费由于用户其实未真正体验投顾老师的水平(未买股票),所以该因素由投顾实际指导效果转化为用户对老师的印象,该印象来源于用户在站内(包括APP内)的使用体验,即老师的免费服务,如回答问题,直播互动,分析文章等。免费服务是投顾+站方共同为用户提供的,因此提升首次付费用户使用体验,进而提升付费转化,这是站方可以把控的。至于复购问题,如上述由于产品本身特性,受投顾的判断能力影响很大,而站方又无法做到短时期大幅度提升该能力,同时评判某领域专家对事物的判断是否精准,本身就是一个复杂的题目,基于这些原因,复购问题的复杂程度远高于首次付费,故本次分析不予涉及。笔者认为,复购分析最好是应用于刚性需求,且影响因素可控的付费分析中,比如电商洗发水分析,需求(洗头)为刚需,影响因素(产品调性、价格等)可由电商平台自行选择供应商解决,因此复购问题就能够较好地进行分析。
以上为进行此次分析的缘由。下面逐条介绍本次分析项目
分析逻辑:从APP用户从激活到支付的所有点击行为中,找到结果为“支付”的行为路径,从中筛选出发生数量最大的路径,并优化该路径,进而促进支付。(注:本方法与用户行为漏斗正好是反向的,应该用在漏斗行为之前,请思考为什么)
样本要求: 非羊毛党用户,即APP使用行为出于对APP本身的兴趣。某些渠道或活动引人的用户,比如下APP送话费的引流和ASO带来的用户就应该剔除。否则,后果如下:1.引人误导行为路径;2.无效样本过多,导致计算量过度膨胀,效率下降。
分析环境:R语言
分析代码:代码分为两部分:《关键点击建模分析》和《注册支付时间差分布和消费金额分布》,建议分脚本运行
《关键点击建模分析》
#####################导入模型包#############
library(arules)
library(arulesSequences)
library(dplyr)
library(sqldf)
library(tcltk)
library(DBI)
#####################读入数据(9.1-12.13点击)#############
root<-"E:\\R\\datamining\\appkick\\"
file<-paste(root,"kick-tab.txt",sep="")
# 文件中有些行的产品名是包括“#”符号的,在R中,”#“是默认注释符号,导致读入时认为"#"后面的
# 信息是注释不认为是数据,所以会出现“ line 20412 did not have 17 elements”这种认为某一行缺少
#元素的问题,所以要用“comment.char”这个参数确认没有注释
t.read<-read.table(file,header=TRUE,sep="\t",comment.char="",fileEncoding="UTF-8")
head(t.read)
###########先跟据id再根据event排序
#t.read$event
#class(t.read)
t.read<-t.read[order(t.read$"用户姓名",t.read$event),]
t.read_order<-data.frame(t.read)
#head(t.read_order)
write.csv(t.read_order,file="E:\\R\\datamining\\appkick\\appkicksequence_order.csv")
#####################处理item,形成矩阵 #############
#class(t.l)
t.l<-as.list(t.read$items)
# class(t.read$items)
t.df<-as(t.l,"itemMatrix")
#class(t.df)
#str(t.df[1:5])
t.tr <- as(t.df, "transactions")
#inspect(t.tr[1:5])
#class(t.tr)
#str(t.tr)
###########处理id和event 格式必须为数字,且必须以顺序排列的##############
id.new<-c(0)
event.new<-c(0)
length(t.read$"用户姓名")
id.new[1]<-1
event.new[1]<-1
for (i in 2:length(t.read$"用户姓名")){if (t.read$"用户姓名"[i]==t.read$"用户姓名"[i-1]) {id.new[i]<-id.new[i-1]
event.new[i]<-event.new[i-1]+1}
else {id.new[i]<-id.new[i-1]+1
event.new[i]<-1} }
#t.read$"用户姓名"[1:20]
#data.frame(t.read$"用户姓名",id.new,event.new)[1:20,]
#导入sequenceID和eventID
transactionInfo(t.tr)$sequenceID <-id.new
transactionInfo(t.tr)$eventID <- event.new
transactionInfo(t.tr)
#inspect(t.tr[1:130,])
#class(t.tr)
#str(t.tr)
data.t.tr<-as(t.tr,"data.frame")
#输出表格
write.csv(data.t.tr,file="E:\\R\\datamining\\appkick\\appkicksequence_transaction.csv")
################################
#有条件限制
t.cs <- cspade(t.tr, parameter = list(support = 0,
maxsize = 5,maxlen=2),
control=list(verbose = TRUE))
t.out<-as(t.cs,"data.frame")
write.csv(t.out, file="E:\\R\\datamining\\appkick\\appkicksequence_output.csv")
##############################正则表达式找到以“支付”为目标的序列###################################
t.cs<-sort(t.cs,by="support")
#t.cs<-as(t.cs,"data.frame")
#head(t.cs)
#t.cs$sequence
kick.pay<-".*未签约用户点击原价支付[^\\}]*\\}>"
t.cs.pay<-t.cs[grep(kick.pay,as(t.cs,"data.frame")$sequence)]
inspect(t.cs.pay)
t.cs.pay<-t.cs.pay[-1]
t.cs.pay.dataframe<-as(t.cs.pay,"data.frame")
################筛选重点页面######################
persent<-t.cs.pay.dataframe$support/sum(t.cs.pay.dataframe$support)
#累计计算支持占比,发现对占比影响最大的一部分点击按钮
sum.persent<-cumsum(persent)
t.cs.pay.dataframe<-cbind(t.cs.pay.dataframe,persent,sum.persent)
max.persent = 0.7#考核影响达到70%的
#整理表格,加入相关数据项
t.cs.pay.dataframe<-subset(t.cs.pay.dataframe,sum.persent<=max.persent)
#length(t.cs.pay.dataframe$sequence)
########找到引导到支付的重要前点击############
kick.ant<-0;#前项(Antecedent)
i<-1
for(i.seq in t.cs.pay.dataframe$sequence) {
head.seq1<-regexpr("<\\{",i.seq)+2
trail.seq1<-regexpr("\\}",i.seq)[1]-1
kick.ant[i]<-substr(i.seq,head.seq1,trail.seq1)
i<-i+1
}
#kick.ant
###############计算前项点击的人数################
i<-1;
#点击前项(Antecedent)的人,这里后项(Consequent)指“支付”,注意,这里是点击某个按钮的人数不是次数
kick.antpeople<-0
for(i.kick in kick.ant) {
kick.antpeople[i]<-length(unique(t.read$id[which(t.read$items==i.kick)]))
i<-i+1 }
################计算置信度############
#产生序列<点击i,未签约用户点击原价支付>的实例有多少,及该序列支尺度* 序列人数
kick.peoplenum<-t.cs.pay.dataframe$support*t.cs@info$nsequences
#计算影响到支付点击的置信度(confidence)
con.kick.affectingpay<-kick.peoplenum/kick.antpeople
#最终结果:将cspade出来并且优化后的序列结果,前项点击名称和置信度,放在一起。
result.final<-cbind(t.cs.pay.dataframe,antecedent.kick=kick.ant,confidence=con.kick.affectingpay)
head(result.final)
#class(result.final)
#输出
write.csv(result.final,file="E:\\R\\datamining\\appkick\\appkicksequence_resultfinal.csv")
#绘图
#支持度
barplot(as.matrix(result.final$support,nrow=1),ylim=c(1,0),beside=TRUE,xlab = "点击名称", main = "引导用户点击支付重要点击分析")
#画线
lines(0.5+c(1:nrow(result.final)),result.final$confidence,type="b",col = "red" )
text(0.5+c(1:nrow(result.final)),result.final$confidence,labels = paste(round(result.final$confidence*100,2),"%",sep = ""))
#坐标轴标签
axis(1,at=0.5+c(1:nrow(result.final)),labels = result.final$antecedent.kick,tick=FALSE)
《注册支付时间差分布和消费金额分布》
#####《注册支付时间差分布和消费金额分布》#####
#####################导入模型包#############
library(arules)
library(arulesSequences)
library(dplyr)
library(sqldf)
library(tcltk)
library(DBI)
library(ggplot2)
#####################读入数据(9.1-12.13点击)#############
root<-"E:\\R\\datamining\\appkick\\"
file<-paste(root,"kick-tab.txt",sep="")
# 文件中有些行的产品名是包括“#”符号的,在R中,”#“是默认注释符号,导致读入时认为"#"后面的
# 信息是注释不认为是数据,所以会出现“ line 20412 did not have 17 elements”这种认为某一行缺少
#元素的问题,所以要用“comment.char”这个参数确认没有注释
t.read<-read.table(file,header=TRUE,sep="\t",comment.char="",fileEncoding="UTF-8")
kNames<-names(t.read)#保留字段名
p<-t.read #下面用sqldf必须转为data.frame格式 但是t.read已经是data.frame格式了
####################计算筛选出有点击用户###########
kKickpay<-sqldf("select 用户姓名 from p where items like '未签约用户点击原价支付'")
kKickpay<-unique(kKickpay)
kKickall<-sqldf("select * from p where 用户姓名 IN kKickpay")
names(kKickall)<-kNames
t.read<-kKickall
length(t.read$用户姓名)
####################支付与注册时间差分布##############
lct <- Sys.getlocale("LC_TIME"); Sys.setlocale("LC_TIME", "C")
###############################################################
# 一定要注意时间表示格式,比如“Y”是“Year with century”如1919,#
# “y”是“Year without century (00–99),如19” #
###############################################################
kPaytime2<-strptime(t.read$支付时间,"%Y/%m/%d %H:%M:%S",tz = "GMT")
#kStartime<-strptime(t.read$激活时间,"%Y/%m/%d %H:%M:%S",tz = "GMT") #激活时间,有空值,弃用
kRegistractiontime2<-strptime(t.read$注册时间,"%Y/%m/%d %H:%M:%S",tz = "GMT")#注册时间
kdifftime1<-difftime(kPaytime2,kRegistractiontime2,units="days") #注册时间差
kdifftime2<-data.frame(as.integer(kdifftime1))
colnames(kdifftime2)<-"difftime"
t.read2<-cbind(t.read,kdifftime2)#合并原表和时间差列
#去掉一列中重复的行,'duplicated'返回一个逻辑值,判断一个数是不是会与它前面的数重复,
#这里用index建立一个索引
index<-duplicated(t.read2$用户姓名)
#注意利用索引去掉重复值
t.read3<-t.read2[!index,]
t.read3[1:5,]#有点击用户去重数据
#接下来画图
summary(t.read3$difftime)
#length(unique(which(t.read3$difftime<90)))
diff.mean<-mean(t.read3$difftime)#平均值
diff.sd<-sd(t.read3$difftime)#标准差
diff.var<-var(t.read3$difftime)#方差
#异常值范围
difftime.description1<- diff.mean+3*diff.sd
difftime.description2<- diff.mean-3*diff.sd
c(difftime.description1,difftime.description2)
i<-1
difftime.description<-c(0)
for (i in 1:length(t.read3$difftime)) {
if (t.read3$difftime[i]>=difftime.description1)
{difftime.description[i]=difftime.description1}
else
{difftime.description[i]=t.read3$difftime[i]}
}
summary(difftime.description)
t.description<-data.frame(difftime.description)
#导出时间差分布到表格
write.csv(t.description,file="E:\\R\\datamining\\appkick\\difftimedescription.csv")
#par(mfrow=c(1,1))
hist (difftime.description, breaks = seq(0,364,7),freq =TRUE, include.lowest = TRUE, main = "注册支付时间差分布",
xlab = "注册支付时间间隔天数" ,ylab ="频数",
xlim =c(0,400),ylim=c(0,60))
####################付费产品价格分布###########
head(t.read3)#有点击用户去重数据
#接下来画图
summary(t.read3$支付金额)
pay.description<-t.read3$支付金额;
hist (pay.description, breaks = c(1,3000,100),freq =TRUE, include.lowest = TRUE, main = "注册支付时间差分布",
xlab = "支付金额" ,ylab ="频数",
xlim =c(0,3000),ylim=c(0,60))
write.csv(pay.description,file = "E:\\R\\datamining\\appkick\\PAYdescription.csv")