话不多说,先看数据。
import pandas as pd
import numpy as np
df = pd.read_excel(r"C:\Users\dai\Desktop\序列行为.xlsx")
df.head()
数据为用户通过不同渠道在不同的时间访问公司网页的记录。
本次要研究的一是如何选择广告渠道以及选择渠道组合做广告投放,如选择了baidu做为第一个投放站点,第二个投放站点选择哪个站点与baidu组合,从而最大化渠道效果;二是对用户访问次数进行预估;三是做渠道效果整体评价。
len(df) #271
df.info()
可以发现没有缺失值
选择渠道有两个因素,一是通过该渠道访问网页的用户数,第二是上次访问距离当前时间点的间隔;通过该渠道访问网页的用户数越多,访问时间间隔越短,则说明该渠道越重要。
先研究渠道组合的逻辑,为了研究渠道组合,需要知道用户访问渠道的时间顺序,即路径,如第一次访问的baidu,下一次访问网页是通过哪个渠道访问的,访问间隔是多长,如果访问渠道在时间上隔得越近说明渠道序列关联性越强。如客户先通过baidu访问没下单,下一次通过360访问就下了单,那么baidu和360的关联性就是很强的。
先构建一个辅助函数,计算两个时间点的相隔时间。
def get_interview(time2,time1):
date2 = time2
date1 = time1
seconds = (date2 - date1).total_seconds() #秒
minutes = (date2 - date1).total_seconds()/60 #分
hours = (date2 - date1).total_seconds()/3600 #小时
days = (date2 - date1).total_seconds()/3600/24 #天
return seconds,minutes,hours,days
构建辅助函数后下面来构建一个主函数,函数目的是构建路径,函数较长,可以先看下效果,即针对每一个ID生成当前渠道路径与下次渠道路径,同时计算路径之间的间隔时间。
如果只访问了一次,那么下次路径设为Just_one,后面将其筛选掉。
def cut_column(df):
df2 = pd.DataFrame()
id_list = pd.Series(np.unique(df["CookieID"])).to_list()
for i in id_list:
data_cut = pd.DataFrame()
data = df[df["CookieID"]==i]
data = data.sort_values("date")
data = data.reset_index(drop=True)
length = len(data)
way_now = []
way_next = []
seconds = []
minutes = []
days = []
hours = []
if length==1:
way_now.append(data.loc[0,"渠道"])
way_next.append("just_one")
seconds.append(0)
minutes.append(0)
hours.append(0)
days.append(0)
elif length==2:
way_now.append(data.loc[0,"渠道"])
way_next.append(data.loc[1,"渠道"])
seconds.append(get_interview(data.loc[1,"date"],data.loc[0,"date"])[0])
minutes.append(get_interview(data.loc[1,"date"],data.loc[0,"date"])[1])
hours.append(get_interview(data.loc[1,"date"],data.loc[0,"date"])[2])
days.append(get_interview(data.loc[1,"date"],data.loc[0,"date"])[3])
else:
row_index= 0
while row_index<length-1:
way_now.append(data.loc[row_index,"渠道"])
way_next.append(data.loc[row_index+1,"渠道"])
seconds.append(get_interview(data.loc[row_index+1,"date"],data.loc[row_index,"date"])[0])
minutes.append(get_interview(data.loc[row_index+1,"date"],data.loc[row_index,"date"])[1])
hours.append(get_interview(data.loc[row_index+1,"date"],data.loc[row_index,"date"])[2])
days.append(get_interview(data.loc[row_index+1,"date"],data.loc[row_index,"date"])[3])
row_index+=1
data_cut["当前路径"] = way_now
data_cut["下次路径"] = way_next
data_cut["CookieID"] = pd.Series(np.unique(df["CookieID"])).to_list()[0]
data_cut["seconds"] = seconds
data_cut["minutes"] = minutes
data_cut["hours"] = hours
data_cut["days"] = days
df2= df2.append(data_cut)
df2 = df2.reset_index(drop=True)
return df2
df2 = cut_column(df)
len(df2) #255
df2.head()
df2.describe([0.01,0.05,0.1,0.25,0.5,0.75,0.9,0.95,0.97,0.98])
数据在97%分位的值为1.7天,说明97%的用户通过这个渠道访问下个渠道都在1.7天以内。
同时可以看出,98%分位的值直接到了4.3天,所以下面可以将days大于1.7天间隔的记录删掉。
再看下间隔hours的分布。
df2 = df2[df2["days"]<1.71]
len(df2) #247,只减少了8个值
import seaborn as sns
sns.distplot(pd.DataFrame(pd.Series(np.round(df2["hours"]))).reset_index(drop=True)["hours"])
通过这个分布图,有了意外的发现,就是用户从上个渠道到下个渠道的访问时长呈现指数形式下降,而牛顿冷却定律刚好也是指数下降的形式,同时渠道的访问间隔刚好跟牛顿冷却定律中的温度下降的间隔时长性质一致,所以接下来我们可以用牛顿冷却定律来对用户访问间隔时长进行打分。
通过研究渠道组合的意外发现,现在研究单渠道策略就简单了。
首先获取源数据中网页最后一次被访问的时间,然后增加各用户每次渠道访问时间到最后一次访问时间的间隔时长,也就是牛顿冷却定律里的冷却时间。
date_max = np.max(df["date"])
df["minus"] = date_max - df["date"]
计算出来之后将minus列转为天数,如7days03:44就转为7天+3/24天
hour = df["minus"].apply(lambda x:str(x).split(" ")[2]).apply(lambda x:str(x).split(":")[0])
day = df["minus"].dt.days
hour = hour.apply(lambda x:int(x))
hour_to_day = hour/24
day_fina= day+hour_to_day
df["day_fina"] = day_fina
因为研究的是单渠道策略,一个用户可能在某个时间段分别通过baidu、360多次访问网页。
这里存在两种单渠道的研究:
方式一:
只取该用户最后一次通过360访问网页的记录和最后一次通过baidu访问网页的记录。
这样做的目的是因为我们要选择某个渠道,如果一个用户一天内100次通过baidu访问网页,另一个用户一天内只有1次通过baidu访问网页;那么对于渠道的贡献来说,实际上这两个用户的贡献值是一样的,因为我们最终是想多个用户访问,而不是一个用户访问多次。
这样每个用户都会对自己访问的渠道做出评分贡献,各渠道的评分贡献 = 访问间隔时长*牛顿冷却系数。
这种方式会造成一个问题,那就是最后会导致每个用户对所有渠道整体的贡献不一致,比如张三昨天通过baidu访问了2次,通过360\hao123访问了网页,而李四昨天只通过baidu进行了访问。那么对渠道整体来说张三不仅对baidu的评分做出了与李四相同的贡献,而且对360和hao123这个渠道也做出了贡献。而我们选择渠道的目的是想多个用户访问,而不是一个用户通过各种渠道访问多次。
方式二:
取该用户所有通过360访问网页的记录和所有通过baidu访问网页的记录。
首先给所有用户都赋予初始值为1,再同时给每次访问记录的间隔时长都乘以一个冷却系数。以方式一的张三李四为例,张三的初始值为1,对于张三来说:
百度的初始得分 = 百度的各次访问间隔时长*冷却系数
360的初始得分 = 360的各次访问间隔时长*冷却系数
hao123的初始得分 = hao123的各次访问间隔时长*冷却系数
所有渠道初始得分 = 百度的初始得分+360的初始得分+hao123的初始得分
百度的最终得分 =百度的初始得分/所有渠道初始得分,360与hao123类似。
因为百度访问了两次,所以百度的得分会更高,为360/hao123 的两倍。
对于李四来说:
百度的最终得分 = 1
似乎方式二更为科学,不过方式二也存在一个问题,如果张三李四访问的时间不一样,比如李四是昨天访问的,而张三是100天前访问的,现在给张三李四都赋值为1,显然是不科学的,因为张三100天前的访问记录对现在的渠道选择毫无作用。
选择方式二需要用户访问时间间隔不是太分散,可以用来研究在特定访问时间区间的用户贡献评价。方式一虽然每个用户对渠道整体选择贡献值不一样,但是每次访问间隔时长是不一样的,如果某个渠道访问间隔时间比较长,乘以牛顿冷却系数后,该渠道的贡献值会比较小。
就算访问间隔时间不长,张三昨天访问的baidu\hao123\360与李四访问的baidu最后让baidu 的得分变为了hao123与360的两倍,我们也可认为这是有一定道理的。
设想一个场景,我们在hao123\360\baidu都投放了广告,张三在三个渠道里都看到了这个广告,并且点击了进来。而我们做广告投放并没有在投放前就规定好如果张三在360看到了这个广告,那么就会让他在baidu也看到这个广告,因为我们的数据来源于样本,而非总体,我们是用现在的样本去推断总体,此时我们可把张三baidu的访问和360的访问都抽象为一次用户行为,不论这个行为是不是发生在相同用户身上,因为广告投放了多个渠道,你在这个渠道看到了,那可以相信这个渠道有相同的概率被其他用户看到,而不是说你在这个渠道上看到了,因为你在其他渠道也看到了,然后就降低这个渠道的分值,这不符合样本推断总体的思想。
所以我们最后选择方式一。
明白了这个道理之后选择CookieID、渠道对源数据进行去重
df_orginal = df.sort_values("date",ascending=False)
df_orginal = df_orginal.drop_duplicates(subset=["CookieID","渠道"],keep="first").reset_index(drop=True)
df_orginal.head(10)
现在的数据只剩下每个用户通过各个渠道访问的最后一条记录,以及相应的最后一条记录距离最后一次访问网页的那条记录的时间间隔。
依据牛顿冷却定律,现在来对时间间隔进行评分。
import math
from matplotlib import pyplot as plt
plt.style.use('fivethirtyeight')
number = np.max(df_unique["minus"].value_counts().index)
for i in np.linspace(3.2, 3.3, num=10):
y_list = []
x_list = []
for x in np.linspace(0, 1.7, num=20):
y = 1/math.exp(i * x)
y_list.append(y)
x_list.append(x)
print(y)
print(i)
plt.figure(figsize=[20,5])
plt.plot(x_list,y_list)
plt.show()
其实牛顿冷却定律跟指数分布的思想有异曲同工之妙。下面来看看指数分布的形式:
是不是跟牛顿冷却定律的形式看起来很像,而指数分布的重要作用就是根据随机事件发生一次的平均等待时间来推断某个时间段内,随机事件发生的概率。
经过学习曲线调参,确定了冷却系数为3.222。将牛顿冷却曲线与原来的渠道路径间隔天数的分位数比较是出奇的吻合。间隔0.1天只剩下75%的用户,0.2天只剩下50%的用户,0.4天只剩下25%的用户,0.7天则只剩下10%的用户了。此处的冷却系数的调参选择逻辑就是对照用户渠道路径访问间隔天数留存百分比的吻合程度来选择的。
根据上面的冷却系数,现在对间隔时长打分,得出各个用户对各个渠道的贡献分数
df_orginal["score"] = df_orginal["day_fina"].map(lambda x:1/np.exp(3.222 * x))
df_orginal
pd.DataFrame(df_orginal.groupby("渠道")["score"].sum()).reset_index().sort_values("score",ascending=False)
再将各用户的分数求和即可得出渠道排名,从而进行单渠道广告投放选择。
与不使用牛顿冷却定律打分进行比较,即用渠道访问用户数进行渠道排名。
pd.DataFrame(df_orginal.groupby("渠道")["CookieID"].count()).reset_index().sort_values("CookieID",ascending=False)
结果是大相径庭,直接使用渠道访问用户数评估渠道是有问题的,将导致一个错误的策略,因为这没考虑时间因素的影响,通过观察源数据也可以发现baidu其中有三次的访问时间是10天以前。
除了对单渠道选择进行评价的作用外,我们还可利用上述公式,对所有用户在未来的时间段还会有多少人因为上次访问而接下来继续访问进行预测。
如此处假设所有样本访问次数(假设100次)的最后一次访问就是前0.1days之内,那么因为现在的这次访问,我们可以预计,12小时内,还会因为这次访问带来100-20=80次的访问量。
cookieid_all=len(df_orginal["CookieID"])
day_width = []
cookid_number = []
print(cookieid_all)
for x in np.linspace(0, 3, num=20):
y = cookieid_all/math.exp(3.222 * x)
day_width.append(x)
cookid_number.append(y)
plt.figure(figsize=[20,5])
plt.plot(day_width,cookid_number)
plt.show()
如果接下来的0.1days网站有了50次访问,那说明其中有20次(算法见下文)访问与0.1days前的访问需求无关,有新的因素导致访问次数增加,此时我们就可以依据这个方法对各个渠道的真实效果进行各个时间段的评估了。
现在来模拟个数据试下。
time =np.linspace(0,7,70) #访问时间
view_number =np.random.randint(1500,2000,size=(70,)) #访问用户数
data = pd.DataFrame({"time":time,"view_number":view_number})
data.head()
模拟70条数据,view_number表示不同时间段有多少的访问量,这里的time为上面0.1days的概念。
data["score"] = data["time"].map(lambda x:1/np.exp(3.222 * x))
for i in np.arange(len(data)):
while i<len(data)-1:
data.loc[i+1,"score2"] = data.loc[i,"score"] - data.loc[i+1,"score"]
i=i+1
data = data.fillna(0)
data.head()
这里的score就是冷却系数,score2比如1616次访问在0days访问了,那么在0.1days这个时间段就只会剩下70%的概率进行访问,在0.2days只会剩下50%的概率访问,一直到某个时间只剩下0%的概率进行访问。将概率转换为访问次数的话,也就是不同时间段的访问贡献是不一样的,0-0.1days在概率变为0的所有时间段里占的权重就是30%,0.1days到0.2days占的权重就是20% ,直接做差可得,以此类推。
接下来计算历史访问次数对当前访问次数的影响
result= []
for loop_ in np.arange(len(data)):
view_remain = 0
i = 0
for number_ in data["view_number"][0:loop_+1]:
calculate = data.loc[loop_-i,"score2"] * number_
i=i+1
view_remain = view_remain+calculate
result.append(view_remain)
result
result代表的就是当前时间段有多少次访问是受到上一时间段的影响才在本次进行了访问
将访问次数减去历史时间段的访问影响次数,就可以得到新因素造成的访问次数了。
data["real_new"] = data["view_number"] -data["result"]
plt.plot(data.time,data.real_new)
结果就出来了,大部分数据都在0上下震荡,这是因为我们观察的间隔时间太短了,间隔时间只有0.1days。
至此,单渠道策略已全部分析完毕,你学废了吗?
总结下上述分析方法的作用:
1、单渠道选择
2、用户访问次数预估
3、真实渠道效果在各时间段的评价
此方法经过了三个晚上的思索,过程多次修改,如果你觉得本文存在逻辑漏洞或者有更好的方法,请在文章留言,我们一起探讨。
下篇文章探索多渠道组合策略的问题