今天要分析的一个kernel是一个关于elo的loyalty关于时间序列的关系的研究的kernel。
关于竞赛介绍及基础知识见:
我的上一篇内核分析:https://blog.csdn.net/ssswill/article/details/85217702
这篇kernel来自:
A Closer Look at Date Variables
写在前面:个人认为这篇kernel与比赛关系不大,之所以分析它是因为里面有很多图像可视化的画法,对后面的学习肯定是有帮助的,同时这篇kernel包含了很多对时间变量的处理,也是很常见的一种处理方式,基于这两点原因,我觉得还是做一些分析留作知识储备。
同时,kaggle系列博客会一直更新下去。但是仅适用于新手入门学习使用,在放假时,我会抽出时间写出kaggle很多实用的功能与小提示,例如数据集的创建,本地训练结果的提交等,网上关于这方面的东西还是很少的,我之前也是踩过一些坑。所以希望写出来希望可以帮助一些刚接触kaggle的人。
好了,话不多说,直接开始这篇kernel的学习吧!
我们再来稍微回顾下这个elo推荐竞赛,是给你一些用户的数据,让你来预测用户的忠诚度target。这是一个回归问题。同时,用户信息包含了一些时间序列,例如用户第一次活跃的时间,以及每一次消费的时间。这些肯定很重要,但是有一些时间量我个人认为这些不会影响target,或者影响的比较少。例如不能说一个经常在周二买东西的人忠诚度就会比经常在周三买的人高,也不能说经常早上消费的人忠诚度就要比晚上消费的高。但这都是直观看法,带着这些疑问来走进这篇kernel吧!
import warnings
import datetime
import calendar
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from datetime import time
from dateutil.relativedelta import relativedelta
# to ignore future warnings,忽略warning
warnings.simplefilter(action = 'ignore', category = FutureWarning)
# set size of seaborn plots
sns.set(rc={'figure.figsize':(10, 7)})
## read data
train = pd.read_csv('../input/train.csv', sep = ',')
test = pd.read_csv('../input/test.csv', sep = ',')
merchants = pd.read_csv('../input/merchants.csv', sep = ',')
new_merchant = pd.read_csv('../input/new_merchant_transactions.csv', sep = ',')
这里sep是分隔符的意思。默认分隔符就是逗号,所以不用在意。
train.head()
再来看看merchants(商人信息)与new_merchant(参考日期之后的购物记录)
## prepare data
# this is not a valid approach if you want to build models from the data
#有一些重复列都去掉了。
# drop some redundant columns
dropping = ['merchant_category_id', 'subsector_id', 'category_1', 'city_id', 'state_id',
'category_2']
for var in dropping:
merchants = merchants.drop(var, axis = 1)
# merge merchants with new_merchants
data = pd.merge(merchants, new_merchant, on = 'merchant_id')
# merge data with train data
data = pd.merge(data, train, on = 'card_id')
第一个data是融合了merchants, new_merchant的df,可以看到二者有相同的列,所以在上面代码中去掉了一些重复的列,合并后是一个扩展版的用户购物信息。
之后再与train合并。得到了最终研究的数据:
这两张图都是一个dataframe的。
这个df是用户在参考日期之后2个月购物信息的完整版。但是这里没有做history的信息,但是思路是类似的。
The variables in question here are *first_active_month*
and *purchase_time*.
Let's take a brief look at their number of unique values
and the first five values:
上面这些英文意思就是:虽然表格里信息很多,但是我们现在研究的是时间序列,所以只关注first_active_month 和purchase_time. 两列信息。first_active_month就是这张卡第一次激活或者活跃的时间,purchase_time是这张卡每次购物的时间。所以:来看看这两个量有多少个不同的值与大概信息
所以:
对于每个用户,或者每个卡,他们的first_active_month都只有一个,但是purchase_time可能不是唯一一个,因为每张卡不可能都只购物一次。
好了,理清楚这个了,继续往下走:
print(len(data['first_active_month'].unique()))
#print(data['first_active_month'].nunique())与上句一样意思
data['first_active_month'][:5]
74
0 2013-11
1 2013-11
2 2013-11
3 2013-11
4 2013-11
Name: first_active_month, dtype: object
data['first_active_month'].min()
输出:
'2011-11'
同样的:
print(len(data['purchase_date'].unique()))
data['purchase_date'][:5]
1082077
0 2018-04-11 11:45:08
1 2018-03-26 15:40:28
2 2018-04-20 20:43:36
3 2018-03-26 14:01:49
4 2018-03-31 07:55:33
Name: purchase_date, dtype: object
而一共有len(data)=1223315条消费信息。其中108万条的消费时间不同。怎么说呢。我觉得挺正常的的,因为这个数据是精确到秒的,在同一秒买东西的人本来肯定就不多,但是kernel作者说了:
This shows us that the *purchase_date* variable actually
carrys more information than it's name makes it sound like.
It's not only the date, but also the specific time.
Let's recode this into two variables:
他意思就是这个数据很重要。
# recode purchase_date
data['purchase_time'] = data['purchase_date'].str.split(' ')
data['purchase_date'] = data['purchase_time'].str[0]
data['purchase_time'] = data['purchase_time'].str[1]
上面的购买时间 2018-03-31 07:55:33是以空格分开的,前半段为日期,后半段为具体几点这样。
时间量找到后,就是对他进行处理了:
之后作者说:
Now there are two different strategies to use these time
variables in further models.
1.Recode them to a linear variable where the lowest number is the
day furthest in the past and the highest number the most recent day
2.Recode them to ordered categorical variables
Let's start with number 1:
有两种处理方式,作者先用方式1,就是把日期当成一个个从小到大排列的数。
为了理解下面的重要代码,必须要介绍两个函数:
由字符串格式转化为日期格式(tuple)的函数为: datetime.datetime.strptime()
由日期格式(tuple)转化为字符串格式的函数为: datetime.datetime.strftime()
https://blog.csdn.net/ljh0302/article/details/54882750
def dates_to_numeric(series, kind = 'month'):
# get all unique values
months = list(series.unique())
# sort them
if kind == 'month':
date_string = "%Y-%m"
elif kind == 'day':
date_string = "%Y-%m-%d"
# make them a datetime object
dates = [datetime.datetime.strptime(ts, date_string) for ts in months]
dates.sort()
sorteddates = [datetime.datetime.strftime(ts, date_string) for ts in dates]
# generate all month stamps between first and last
start_date = sorteddates[0]
end_date = sorteddates[len(sorteddates) - 1]
cur_date = start = datetime.datetime.strptime(start_date, date_string).date()
end = datetime.datetime.strptime(end_date, date_string).date()
months = []
while cur_date < end:
if kind == 'month':
months.append(str(cur_date)[:-3])
cur_date += relativedelta(months = 1)
elif kind == 'day':
months.append(str(cur_date))
cur_date += relativedelta(days = 1)
# create dict that maps new values to each month
map_dict = {}
keys = range(0, len(months))
for i in keys:
map_dict[i] = months[i]
# reverse dict keys / values for mapping
new_dict = {v: k for k, v in map_dict.items()}
return new_dict
new_dict = dates_to_numeric(data['first_active_month'])
data['first_active_month_numeric'] = data['first_active_month'].apply(lambda x: new_dict.get(x))
new_dict = dates_to_numeric(data['purchase_date'], kind = 'day')
data['purchase_date_numeric'] = data['purchase_date'].apply(lambda x: new_dict.get(x))
# recode timestamp to number of seconds passed since 00:00:00
def timestamp_to_seconds(time):
seconds = sum(x * int(t) for x, t in zip([3600, 60, 1], time.split(':')))
return seconds
data['purchase_seconds'] = data['purchase_time'].apply(lambda x: timestamp_to_seconds(x))
其实,关于这部分代码,内容量就十分丰富了,我觉得搞懂这个还是很有必要的,因为对于时间序列的处理这部分代码给了很好的范例,所以写到这里,我决定对这部分代码深挖,如果写的比较长,那么剩下的内容会发到另一篇博客。原因还是那句话,太长了我自己都不想看,别说别人了。
ok。先看这个函数名,date_to_numeircal就知道是把时间量转为数字量的。有两个参数,一个是series。可以理解为要喂进去的一个df的一个时间的列。kind就是strptime函数与strftime函数需要的参数。这两个函数都是把时间的字符串与时间量相互转换的函数,需要一个format。这个format就是说你要转的这个时间量是什么格式的,这个format在我们这就是kind参数。具体查看我上面给的链接。
# get all unique values
months = list(series.unique())
代码段第一句:把series中不重复的量转为一个list。举例:
months = list(data.first_active_month.unique())
months
输出如上图的一个时间列表。
接下来我以kind==month为例。
dates = [datetime.datetime.strptime(ts, date_string) for ts in months]
这句话就是把上面的时间量string转为元组型时间量。
这也就是strptime函数的用法啦
dates.sort()
sorteddates = [datetime.datetime.strftime(ts, date_string) for ts in dates]
排序之后,再用strftime函数转为字符串型时间。
继续分析:
# generate all month stamps between first and last
start_date = sorteddates[0]
end_date = sorteddates[len(sorteddates) - 1]
cur_date = start = datetime.datetime.strptime(start_date, date_string).date()
end = datetime.datetime.strptime(end_date, date_string).date()
print(cur_date)
print(end)
output:
2011-11-01
2018-01-01
关于时间戳,struct_time元组等知识建议参考:
http://www.runoob.com/python/python-date-time.html
python官网
https://docs.python.org/3/library/datetime.html#time-objects
继续:
months = []
while cur_date < end:
if kind == 'month':
months.append(str(cur_date)[:-3])
cur_date += relativedelta(months = 1)
elif kind == 'day':
months.append(str(cur_date))
cur_date += relativedelta(days = 1)
这个应该不难理解,为什么kind为month时候不取最后三位。而kind为day时候不会这样。因为对于kind=month是为了first_active_month服务的,这一列本来就没精确到一个月的第几天。所以只精确到月。
输出:
这一列打箭头的那个原数据是没有的。
# create dict that maps new values to each month
map_dict = {}
keys = range(0, len(months))
for i in keys:
map_dict[i] = months[i]
# reverse dict keys / values for mapping
new_dict = {v: k for k, v in map_dict.items()}
这个看似复杂的函数被我们解析之后,也就清晰很多了,剩下的就是调用这个函数了:
new_dict = dates_to_numeric(data['first_active_month'])
data['first_active_month_numeric'] = data['first_active_month'].apply(lambda x: new_dict.get(x))
new_dict = dates_to_numeric(data['purchase_date'], kind = 'day')
data['purchase_date_numeric'] = data['purchase_date'].apply(lambda x: new_dict.get(x))
# recode timestamp to number of seconds passed since 00:00:00
def timestamp_to_seconds(time):
seconds = sum(x * int(t) for x, t in zip([3600, 60, 1], time.split(':')))
return seconds
data['purchase_seconds'] = data['purchase_time'].apply(lambda x: timestamp_to_seconds(x))
也就是说。时序量被我们转化为了0,1,2,3这样的数字。最小为0,最大的为len()-1。单位为month,day。至于那个转化为秒的也很好理解。
新增加的三列:
在完成转化后,就可以做图分析规律了!
ax = sns.regplot(x = data['first_active_month_numeric'], y = data['target'], marker = "+",
lowess = True, line_kws = {'color': 'black'})
ax.set_title('Relationship of the target variable and linear first active month')
ax.set_xlabel('first active month linear')
可以看到,资历越老的用户,target的正负值的绝对值都越大。值得深思。同时基本关于target轴对称。最下面的那一行就是outlier了,有2207个呢,可见他们基本每个时间段也都有的。
同样的,对于购买时间:
ax = sns.regplot(x = data['purchase_date_numeric'], y = data['target'], marker = "+",
lowess = True, line_kws = {'color': 'black'})
ax.set_title('Relationship of the target variable and linear purchase date')
ax.set_xlabel('purchase date linear')
这个图,,规律就更难看出来了。同时,其实理性分析,这些图是有问题的,因为你无法保证这些横坐标也就是purchase day来自的客户是否一致,他们肯定很多都是不相同的,但是又有的是相同的。
最后,关于这个sns.regplot函数。还是有必要学习下的。
参考:
https://blog.csdn.net/wuwan5296/article/details/78698125
regplot()和lmplot()都是seaborn的函数,都可以绘制线性回归曲线。这两个函数非常相似,甚至共有一些核心功能。
在最简单的调用中,两个函数都会画出双变量的散点图,然后以y~x拟合回归方程和预测值95%置信区间并将其画出
sns.regplot(x="total_bill", y="tip", data=tips);
或者:
sns.lmplot(x="total_bill", y="tip", data=tips);
二者区别很小:
实际上官方手册这里解释的并不直观。两者区别在于sns.regplot(x=tips[‘total_bill’],y=tips[‘tip’])可以运行,data=~这个参数不必要。
但是sns.lmplot(x=tips[‘total_bill’], y=tips[‘tip’], data=tips)就必须传入data=~参数
seaborn官网:
http://seaborn.pydata.org/generated/seaborn.regplot.html
一些regplot画的图:
(实际上他就是在散点图的基础上近似画出一条直线(也可以是曲线))来拟合两个变量的关系,例如这样:
或者这样:
但是,为啥我们在这里值画出了散点图并没有一条拟合的线呢,其实不是没画,target=0那条线就是。。。
散点图长这样:
plt.scatter(x = data['first_active_month_numeric'], y = data['target'], marker = "+",)
These plot show no relationship between the metric versions of the three date variables and the target variable. By using the lowess smoother instead of a linear regression we also made sure there is no nonlinear relationship between the two variables.
作者说用了lowess参数确定了不仅是线性关系没有,非线性关系也没有。。。
这里提及到regplot的一个参数:lowess = True
从官网我们知道:
用statsmodels来拟合,感觉更高级了,赶集来看看statsmodles是啥:
http://www.statsmodels.org/stable/index.html
statsmodels是一个有很多统计模型的python库,能完成很多统计测试,数据探索以及可视化。它也包含一些经典的统计方法,比如贝叶斯方法和一个机器学习的模型。
statsmodels中的模型包括:
线性模型(linear models),广义线性模型(generalized linear models),鲁棒线性模型(robust linear models)
线性混合效应模型(Linear mixed effects models)
方差分析(ANOVA)方法(Analysis of variance (ANOVA) methods)
时间序列处理(Time series processes)和状态空间模型(state space models)
广义矩估计方法(Generalized method of moments)
来自:
https://blog.csdn.net/wuzlun/article/details/80305111
这么多模型也无法拟合,所以作者认为二者无关,那么这篇kernel就完了吗,显然没。kernel还有一部分内容,请见我下一篇博客吧,比较着急的可以直接看kernel。