CTR场景数据分析建模 问题汇总4 - 复盘比赛1

CTR场景数据分析建模 问题汇总4 - 复盘比赛1

比赛背景

赛题描述
本次大赛基于脱敏和采样后的约 40,000 条用户消费行为数据,预测在未来的一段时间内,用户对于某产品是否会有购买和收藏的行为。
参赛团队需要设计相应的算法进行数据分析和处理,比赛结果按照指定的评价指标使用在线评测数据进行评测和排名,得分最优者获胜。

数据背景
本数据集为经过脱敏和采样后用户在某网站的消费行为数据,其中大致包含了三大类信息,即用户的基础信息,商品的基础信息,用户的行为信息,其中用户的基础信息除了用户的id之外皆为脱敏后的具体行为信息,商品信息除卖家id、商品id 之外皆为脱敏后的具体商品信息, 用户的行为信息包含了两个用户的具体行为(收藏和购买),除此之外都是脱敏后的具体行为信息。

相关字段列表
日期信息进行统一编号,第一天编号为 01, 第二天为 02,以此类推。所有的特征(除上文中已经说明的之外)无具体指代。

列名 类型 说明 示例
user_id String 用户唯一标识(脱敏后) abc123
Product_id String 产品id(脱敏后) abc123
seller String 卖家id(脱敏后) abc123
day String 日期 12,…30
action_type Int 行为相关变量(脱敏后) 1
ProductInfo_X Int 产品相关变量 1,50,100
Webinfo_X Int 网络行为相关变量 1,50,100
UserInfo_X Int 用户相关变量 1,50,100
purchase Int 用户是否购买 0,1
favorite Int 用户是否收藏 0,1

数据提交方式与在线测评说明
在初赛期间,大赛提供约 1 万条用户在 30 天内的行为数据作为训练数据和约3千条的测试数据,训练目标为预测该批用户在未来7天内是否会产生消费行为的概率。在复赛期间,大赛提供约 3 万条性质相同训练数据和约1万条的测试数据。
参赛团队无需下载数据,可从大赛比赛系统内的线上训练工具直接调用大赛数据,进行算法调试,并在线提交结果。

结果提交格式
我们将考核对于在未来 7 天(即第 31 天至第 37 天)内,一名用户是否会购买和收藏一个产品的准确性。选手提交结果格式为:
列名 说明 示例
user_id product_id pred_favorite pred_purchase 用户购买和收藏的概率 0.2 0.6
要求每行一个 user_id。

评估标准
1.本次测评算法为: AUC(Area Under the Curve) ,我们将分别针对购买和收藏的预测值求出一个AUC。
最终的测评结果为 AUC* = (AUC(购买) + AUC(收藏)) / 2。
2.最终使用 AUC* 作为参赛选手得分。AUC* 越大,代表结果越优,该团队的排名越靠前。
比赛介绍链接

经验

复赛一开始曾经做到过20多名,但是当时很多大佬还没进场,最后45名结束比赛,最后几天因为与前面大佬差距过大基本放弃挣扎了。结果auc:0.630,其中收藏0.706,购买0.554。
群里能演的太多了,干货很少,不经意间有选手leak出

sub(pre_purchase)=-test(user_info_242)把这个提交,结果就有0.56多了。不谢。平均一下就0.53

结果自己没注意到,难过。下次记得在群聊记录里做几次关键字搜索

方法试了不少但是效果不好,本题最大的问题就是线上测试集的分布与训练集分布相差很大,线下构建验证集的难度很大,很多时候线下提升很高,但是线上直接给跪了,很多时候只能强行试。

EDA还是没有做好,这方面要继续努力。
异常值处理一直没有找到好的办法
试着换过几个模型(lgb、xgb、rf、libfm),但是就是没换到点上,最后大佬说购买使用catboost效果会好上很多

购买样本下采样没有采好,随机采样效果下降,应该按照day特征下采样。

不过总的来说在没有人指点的情况下能进到复赛拿一件T,还是很开心啦。

EDA

做了散点图、分布图、箱型图,及与收藏购买分布关系的关系图
通过散点图,可以看到有部分特征是二值化的:
在这里插入图片描述
通过一些分布图,可以发现异常值,甚至异常特征:
在这里插入图片描述
也能发现测试集与训练集分布有差异:(初赛图,11000编号后是测试集,前面是训练集)
在这里插入图片描述
具体画图代码,我前几篇博客都有。
关于箱型图+散点图的绘制,代码如下:

# (2)箱型图分析

fig = plt.figure(figsize = (10,6))
ax1 = fig.add_subplot(2,1,1)
color = dict(boxes='DarkGreen', whiskers='DarkOrange', medians='DarkBlue', caps='Gray')
data.plot.box(vert=False, grid = True,color = color,ax = ax1,label = '样本数据')
# 箱型图看数据分布情况
# 以内限为界

s = data.describe()
print(s)
print('------')
# 基本统计量

q1 = s['25%']
q3 = s['75%']
iqr = q3 - q1
mi = q1 - 1.5*iqr
ma = q3 + 1.5*iqr
print('分位差为:%.3f,下限为:%.3f,上限为:%.3f' % (iqr,mi,ma))
print('------')
# 计算分位差

ax2 = fig.add_subplot(2,1,2)
error = data[(data < mi) | (data > ma)]
data_c = data[(data >= mi) & (data <= ma)]
print('异常值共%i条' % len(error))
# 筛选出异常值error、剔除异常值之后的数据data_c

plt.scatter(data_c.index,data_c,color = 'k',marker='.',alpha = 0.3)
plt.scatter(error.index,error,color = 'r',marker='.',alpha = 0.5)
plt.xlim([-10,10010])
plt.grid()
# 图表表达

在这里插入图片描述
eda分析与收藏购买的相关程度

def eda_gen(train_lxy,str1 = 'user_id_nunique_under_seller',str2 = 'favorite'):
    list_for_eda = []
    a = train_lxy[str1].unique()#unique():返回参数数组中所有不同的值,并按照从小到大排序
    a.sort()
    #print(len(a))
    for i in a:
        temp_mean = np.mean(np.array(train_lxy[train_lxy[str1] == i][str2]))
        list_for_eda.append([i,temp_mean])
    list_for_eda = np.array(list_for_eda)
    plt.title(str1+'_to_'+str2)
    plt.bar(x = list_for_eda[:,0],height=list_for_eda[:,1])
    plt.savefig(str1+'_to_'+str2+'.png')
    plt.close('all')

效果:
在这里插入图片描述

预处理

类别特征打标签

将诸如 id seller等类别特征转换为简单易懂的数字标签

from sklearn.preprocessing import LabelEncoder
data_lxy['seller'].fillna('-1',inplace=True)
data_lxy['Product_id'].fillna('-1',inplace=True)
data_lxy['user_id'].fillna('-1',inplace=True)

lb_seller=LabelEncoder()
lb_product=LabelEncoder()
lb_user=LabelEncoder()
lb_seller.fit(list(data_lxy['seller']))
lb_product.fit(list(data_lxy['Product_id']))
lb_user.fit(list(data_lxy['user_id'])) #将特征转换为标签形式int64
data_lxy['seller'] = lb_seller.transform(data_lxy['seller'])
data_lxy['Product_id'] = lb_product.transform(data_lxy['Product_id'])
data_lxy['user_id'] = lb_user.transform(data_lxy['user_id'])

并在后面使用astype方法将其性质转换为类别

data_lxy['seller']=data_lxy['seller'].astype('category')
data_lxy['Product_id']=data_lxy['Product_id'].astype('category')
data_lxy['user_id']=data_lxy['user_id'].astype('category')

填充na值

#类别型
data_lxy['seller'].fillna('-1',inplace=True)
data_lxy['Product_id'].fillna('-1',inplace=True)
data_lxy['user_id'].fillna('-1',inplace=True)
#数值型(发现填充为-1对购买效果更好,不知为何)
for i in float_feature:
    data_lxy[i].fillna(0,inplace=True) 
for i in int_feature:
    data_lxy[i].fillna(0,inplace=True) 

创建特征

1、首先按照day特征创建weekday特征,星期几对收藏购买还是有影响的

#特征 星期几
data_lxy['weekday']=data_lxy['day']%7+1 #取7的余数

2、统计所有该day(包括该day)之前出现的样本数

day_count_dict = dict(data_lxy.day.value_counts())#分day属性统计数量 
weekday_count_dict = dict(data_lxy.weekday.value_counts())#分day属性统计数量 
#例: 1: 52,第一天有52条 2: 93,第2天93条

#这个特征是统计所有该day(包括该day)之前出现的样本数 例: 如果day=2 则day_before_sum=52+93=145
def day_before_count(day):
    day_before_sum = 0
    day_before_list = list(range(1,day+1))
    for i in day_before_list:
        day_before_sum += day_count_dict[i]
    return day_before_sum

def weekday_before_count(day):
    day_before_sum = 0
    day_before_list = list(range(1,day+1))
    for i in day_before_list:
        day_before_sum += weekday_count_dict[i]
    return day_before_sum
    
data_lxy['day_before_count'] = data_lxy.apply(lambda x:day_before_count(int(x['day'])), axis = 1 )
data_lxy['weekday_before_count'] = data_lxy.apply(lambda x:weekday_before_count(int(x['weekday'])), axis = 1 )

3、统计单个特征个数

#统计单个特征个数

# nuinque()这个函数分别统计每一列属性各自有多少个不同值。比如day属性 nuinque()后就只有30,因为所有数据都是30天内的
#可用其来做编码的准备工作

def feature_count(data, features=[], is_feature=True):
    new_feature = 'count'
    nunique = []
    for i in features:
        nunique.append(data[i].nunique())
        #new_feature += '_' + i.replace('add_', '')#str.replace(old, new[, max]) max次数 感觉这句话没用
        new_feature += '_' + i
    #print(new_feature)
    temp = data.groupby(features).size().reset_index().rename(columns={0: new_feature})
    data = data.merge(temp, 'left', on=features) #.merge合并特征表
    return data

#统计个数 具有相同两个特征的有几个  比如和这一条数据相同的 productid和sellerid的有多少个
def cross_count_features(df, col):
    for col in combinations(col, 2):  #使用combinations实现排列组合 两两组合
        le = LabelEncoder()

        df['tmp'] = df[col[0]].map(str) + '_' + df[col[1]].map(str)
        df['_'.join(col)] = le.fit_transform(df['tmp'])  #str.join(xxx) 用str连接xxx中的字符串 输出连接后的字符串

        try:
            dic_ = df['tmp'].value_counts().to_dict()
            df['_'.join(col) + '_count'] = df['tmp'].apply(lambda x: dic_[x])
        except:
            dic_ = df['tmp'].fillna(-1).value_counts().to_dict()
            df['_'.join(col) + '_count'] = df['tmp'].fillna('nan').apply(lambda x: dic_[x])
        #print('_'.join(col) + '_count')
    del df['tmp']
    return df

count_cols = ['seller','Product_id','day','user_id','action_type','weekday']
for i in count_cols:
    data_lxy = feature_count(data_lxy,[i])
data_lxy = cross_count_features(data_lxy, count_cols)

4、同一个卖家 单用户的点击数量
同一个产品 用户的点击数量
同一个用户 浏览产品的数量
同一个卖家 商品被浏览的数量
groupby特征鼻祖
能凸显出卖家与商品的受欢迎度
也能显示用户的活跃度

def feature_nunique_double(data_all,feature_1 = '',feature_2 = '',feature_3 = ''): 
    gp = data_all.groupby([feature_1,feature_2])[feature_3].nunique().reset_index().rename(columns={feature_3: feature_3+'_nunique_under_'+feature_1+'_'+feature_2})
    data_all = pd.merge(data_all, gp, how='left', on=[feature_1,feature_2])
    print(feature_3+'_nunique_under_'+feature_1+'_'+feature_2)
    return data_all
def feature_nunique_single(data_all,feature_1 = '',feature_2 = ''): 
    gp = data_all.groupby([feature_1])[feature_2].nunique().reset_index().rename(columns={feature_2: feature_2+'_nunique_under_'+feature_1})
    #print(gp)
    data_all = pd.merge(data_all, gp, how='left', on=[feature_1])#以左边(data_all)为主,对应feature_1(seller)
    #print(data_all.groupby([feature_1])[feature_2])
    return data_all

data_lxy = feature_nunique_single(data_lxy,'seller','user_id') #同一个卖家 单用户的点击数量
# data_lxy = feature_nunique_single(data_lxy,'user_id','seller') 
data_lxy = feature_nunique_single(data_lxy,'Product_id','user_id') #自己加的 同一个产品 用户的点击数量
data_lxy = feature_nunique_single(data_lxy,'user_id','Product_id') #貌似fav有用 同一个用户 浏览产品的数量
#data_lxy = feature_nunique_single(data_lxy,'weekday','action_type')
data_lxy = feature_nunique_single(data_lxy,'seller','Product_id')#貌似有用 同一个卖家 商品被浏览的数量
data_lxy = feature_nunique_single(data_lxy,'user_id','weekday')
#data_lxy=feature_nunique_double(data_lxy,'seller','Product_id','user_id')

通过eda分析相关度。下图为同一个卖家 商品被浏览的数量特征与收藏的相关程度,可以看出有些线性关系
在这里插入图片描述
5、用户浏览产品的频率,去重浏览件数/总浏览数 结果越小说明浏览频率越高 反之越低

def user_frequency(data,user_col,product_col):
    #用户浏览产品的频率,去重浏览件数/总浏览数 结果越小说明浏览频率越高 反之越低
    user_all_counts = data.groupby([user_col])[product_col].count().reset_index().rename(columns={product_col: user_col+'_allcounts'})
    #按照用户统计各个用户浏览的产品总数
    data = pd.merge(data,user_all_counts,on=[user_col],how = 'left')
    de_repetition = data.groupby([user_col])[product_col].nunique().reset_index().rename(columns={product_col: user_col+'_de_repetition_frequency'})
    #按照用户统计各个用户浏览的去重产品数
    data = pd.merge(data,de_repetition,on=[user_col],how = 'left')
    data[user_col+'_'+product_col+'_frequency'] = data.apply(lambda x:x[user_col+'_de_repetition_frequency']/x[user_col+'_allcounts'],axis = 1)
    #相除 得到用户浏览频率 可代表活跃度
    del data[user_col+'_allcounts']
    del data[user_col+'_de_repetition_frequency']
    print(user_col+'_'+product_col+'_frequency')
    return data
data_lxy = user_frequency(data_lxy,'user_id','Product_id')
data_lxy = user_frequency(data_lxy,'user_id','seller')

6、统计当天之前该xx出现的次数

#统计当天之前该卖家出现的次数
day_seller_count_dict=data_lxy.groupby(['seller'])['day'].value_counts() #生成groupby数据
day_seller_count_dict=pd.DataFrame(day_seller_count_dict).rename(columns={'day': 'new'})
#转换为dataframe格式 将新统计的列值改名为new,方便后续操作

#统计当天之前该卖家出现的次数
day_user_id_count_dict=data_lxy.groupby(['user_id'])['day'].value_counts() #生成groupby数据
day_user_id_count_dict=pd.DataFrame(day_user_id_count_dict).rename(columns={'day': 'new'})
#转换为dataframe格式 将新统计的列值改名为new,方便后续操作

#统计当天之前该卖家出现的次数
day_Product_id_count_dict=data_lxy.groupby(['Product_id'])['day'].value_counts() #生成groupby数据
day_Product_id_count_dict=pd.DataFrame(day_Product_id_count_dict).rename(columns={'day': 'new'})
#转换为dataframe格式 将新统计的列值改名为new,方便后续操作

def day_before_f_count(feature,time,dic_f=day_seller_count_dict):

    db_count_dict = dict(dic_f['new'][feature])#建立day:counts的词典
    sumtemp=0 #建立每天的存储temp变量
    thisday_before_seller_count_dict=[k for (k, v) in db_count_dict.items() if k <= time] 
    #把词典中的键提取出来,由于函数目的是统计该day之前出现的卖家次数,所以加上限定条件k(键小于参数day)
    #使list中只包含小于day的键
    for j in thisday_before_seller_count_dict:#对键的list内的值进行累加 并输出
        sumtemp+=db_count_dict[j]
    return sumtemp

data_lxy['day_before_seller_count'] = data_lxy.apply(lambda x:day_before_f_count(int(x['seller']),int(x['day']),day_seller_count_dict), axis = 1 )
data_lxy['day_before_user_id_count'] = data_lxy.apply(lambda x:day_before_f_count(int(x['user_id']),int(x['day']),day_user_id_count_dict), axis = 1)
data_lxy['day_before_Product_id_count'] = data_lxy.apply(lambda x:day_before_f_count(int(x['Product_id']),int(x['day']),day_Product_id_count_dict), axis = 1)
#'seller','Product_id','day','user_id','action_type','weekday'

7、用户浏览产品的时间 与 产品第一次出现时间的差别

def early_day(data,lookup_col,day = 'day'):
    #用户浏览产品的时间 与 产品第一次出现时间的差别
    early = data.groupby([lookup_col])[day].min().reset_index().rename(columns={day: lookup_col+'_earliest_day'})
    data = pd.merge(data,early,on=[lookup_col],how = 'left')
    data[lookup_col+'_'+day+'howlong'] = data.apply(lambda x:x[day]-x[lookup_col+'_earliest_day'],axis = 1)
    del data[lookup_col+'_earliest_day']
    print(lookup_col+'_'+day+'howlong')
    return data
data_lxy = early_day(data_lxy,'Product_id','day')
#data_lxy = early_day(data_lxy,'user_id','day')
#data_lxy = early_day(data_lxy,'seller','day')

剩下的下一篇补充。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值