赞
踩
在互联网不断发展的今天,各领域公司都在拓展互联网获客渠道,为公司产品引入新鲜活跃的用户,提高用户购买产品的欲望,提升公司的品牌影响力。但如何判别高质量的用户和渠道,优化营销成本一直都是各公司的痛点;这需要对用户的行为数据进行分析,判别用户的价值,进而对用户制定专门的营销策略,实现小成本促销,达到提高用户转化率的目标。
现有用户信息表 (user_info.csv) ,用户登录情况表(login_day.csv),用户访问统计表(visit_info.csv),用户下单表(result.csv),利用数据对用户行为进行数据统计与分析,并判断用户是否会下单购买。
任务 1:获取数据并进行预处理,提高数据质量;
任务 2:对用户的各城市分布情况、登录情况进行分析,并分别将结果进行多种形式的可视化展现;
任务 3:构建模型判断用户最终是否会下单购买或下单购买的概率,并将模型结果输出为 csv 文件(参照结果输出样例sample_output.csv)。要求模型的效果达到 85%以上;
任务 4:通过用户消费行为价值分析,给企业提出合理的建议。
互联网的快速发展给各领域公司提供了获客渠道。为实现判别高质量的用户、优化公司的营销成本,实现小成本促销,达到提高用户转化率的目标。本文拟解决以下任务。
针对任务一,本文首先将所给的 4 张用户表根据用户信息表中‘user_id’拼接得到一张完整的用户详情表。其次,针对数据中的缺失值问题,对存在缺失值的特征列,按非缺失的特征值比例,随机填充缺失值。然后,针对体验课下单时间、城市特征列,使用 LabelEncoder 编码方法对其进行类别编码处理,将特征列转化成连续的数值型变量。最后,针对用户详情表中的异常数据,采用正态分布检测方法检测各个特征列的异常数据,再将检测到的异常数据按照该特征列的众数进行替换。
针对任务二,本文分别对用户的的各城市分布情况、登录情况进行可视化分析。首先,绘制用户在各城市分布情况的热力图和玫瑰饼图,直观反映出用户广泛分布在全国各个城市,热点城市主要集中在重庆市(11.62%),成都市(3.37%),运城市(3.32%),广州市(2.97%),北京市(2.42%)等城市。然后,针对登录情况,分别对登录天数、登录间隔、登录距离期末天数、登录时长等特征列以柱状图、表格和玫瑰饼图的方式进行多种可视化展现,在购买用户和非购买用户中,购买用户比非购买用户的平均登录天数更多,登录间隔和登录距离期末天数更短,登录时长更长。
针对任务三,本文采用高效率、高性能的 LightGBM 模型进行预测。首先,本文将数据按比例 8:2 随机划分为训练集和测试集,在测试集上的 AUC 和 F1-Score 值分别为:0.992、0.987。最后,运用训练好的模型预测所有用户是否购买课程并与真实情况对比,其中正样本预测准确率、召回率、F1-Score 分别为:0.9572、0.9643、0.9607,负样本预测准确率、召回率、F1-Score 分别为:0.9987、0.9985、0.9986,满足任务三的模型效果要求。
针对任务四,本文使用 LightGBM 模型得到各个特征对用户是否购买的重要程度,从而分析已购买用户各个重要特征的分布情况,最终向企业提合理性建议。通过分析登录距期末天数、手机型号、年龄、城市、登录时长共 5 个重要特征的分布情况可知,登录距期末天数的用户数量大部分集中在 0-40,占比为 72.11%;手机型号的用户数量集中在 0-20,占比为 93.32%;年龄的用户数据主要集中在 36-144(3 岁-12 岁),占比为95.24%;城市的用户数量主要集中在一线或超一线城市,前 5 个城市的用户占比为21.6%。因此,企业在向用户推荐相关课程时,可重点安排营销人员向登录距期末天数在 0-40,手机型号在 0-20,年龄在 36-144(3 岁-12 岁),用户所在城市为一线或者超一线的用户进行推荐。除此之外,当用户的登录时长大于 10 时且登录时长增加时,用户的下单比例会呈下降的趋势,故企业需尽可能在用户登录时间为早期阶段时,给用户推荐相关课程。
在数学建模的过程中,为了把实际问题简化抽象并建立数学模型,本文做了一些必要的前提假设:
1、认为以下特征为类别特征:体验课价格(first_order_price)、城市(city_num)、设备(platform_num)、手机型号(model_num)、APP 激活(app_num)、关注公众号 1(chinese_subscribe_num)、关注公众号 2(math_subscribe_num)、添加销售好友(add_friend)、进群(add_group)、课程重复学习(study_num),其余特征为非类别特征。
2、假设除类别特征外其余特征符合高斯分布。
3、假设类别特征数据基本合理。
问题一要求获取数据并进行预处理,进而提高数据的质量。在给定的数据中,包含四张表格用户信息表 (user_info.csv) 、用户登录情况表(login_day.csv)、用户访问统计表(visit_info.csv)、用户下单表(result.csv)。首先,本文根据‘user_id’将 4 张表拼接成一张完整的用户详情表。其次,由于用户登录情况表和用户访问统计表以及用户下单表中的用户 id 存在缺失情况,导致拼接后的用户详情表存在数据缺失;以及在原本的四张表格中也存在数据缺失和噪声数据等问题。因此,本文根据不良数据的类型采取相应的数据预处理策略。
任务二要求对用户的各城市分布情况、登录情况进行分析,并分别将结果进行多种形式的可视化展现。对于城市分布情况和登录情况,本文分别对城市(city_num)、登录天数(login_day)、登录间隔(login_diff_time)、登录距离期末天数(distance_day)、登录时长(login_time)等相关列进行分析并实现可视化。
任务三要求构建模型预测用户最终是否会下单购买或下单购买的概率。所给的数据即用户信息、用户登录信息、用户观看统计信息中,主要存在以下三大问题:
1. 不同特征量纲差别较大,如年龄,城市等。
2. 存在大量噪声数据,如年龄为 24245。
3. 严重数据不平衡,总样本 135968,其中,负样本 131329 个,正样本 4639 个。
针对上面三个问题,问题一可进行数据标准化解决,问题二可进行异常值检测解决,问题三可通过样本采样解决。但是这样做过于繁琐且效果不一定能得到保证,而LightGBM 却能将好的解决上述问题,并且有较高的性能和效率,其原因有如下三个:
1. 首先,LightGBM 低层学习器为决策树,各特征在训练和预测时皆不参与数值计算只是用来作简单的大小比较即进行节点分裂,因此不要求各特征量纲一致。
2. 其次,LightGBM 属于 Boosting 类型的集成学习,因此具有较好的泛化性和鲁棒性,因此在一定噪声数据下也能保持较好的预测精度。
3. 最后,LightGBM 能较好的处理数据不平衡问题。综上,本文将采用 LightGBM 模型解决任务三,并统计各特征的重要程度,以供任务四参考。
任务四的目的是依据用户信息表 (user_info.csv) 、用户登录情况表(login_day.csv)、用户访问统计表(visit_info.csv)、用户下单表(result.csv)的数据,分析用户的消费行为,从而给企业提出营销建议。本文在该问题上的分析策略如下。在任务三的模型训练中,我们得到了用户各个特征对用户是否购买的重要程度。依据表 6 可知,前 10 个重要的特征依次是最后登录距期末天数(distance_day)、体验课下单时间(first_order_time)、手机型号(model_num)、年龄(age_month)、城市(city_num)、登录时长(login_time)、首页访问数(main_home2)、登录间隔(login_diff_time)、首页访问数(main_home)、界面继续访问数(video_read)。本文通过分析上述登录距期末天数(distance_day)、手机型号(model_num)、年龄(age_month)、城市(city_num)、登录时长(login_time)共 5 个重要特征的分布情况,并作出玫瑰饼图[10],从而给企业提出合理性建议。
import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder,Imputer import matplotlib.pyplot as plt from pyecharts import Line, Pie, Grid, Timeline, Bar,Style,Page import math plt.rcParams['font.sans-serif']=['SimHei'] plt.rcParams['axes.unicode_minus']=False def load_data(file_dir): user_info = pd.read_csv(file_dir+"user_info.csv") login_day = pd.read_csv(file_dir+"login_day.csv") visit_info = pd.read_csv(file_dir+"visit_info.csv") result = pd.read_csv(file_dir+"result.csv") tmp = [login_day,visit_info,result] all_data = user_info for i in tmp: all_data=pd.merge(all_data,i,on = "user_id",how = "left") ####后续实验结果显示这几列为主要影响因素,因此不能轻易修改 # all_data["age_month"] = [np.nan if i > 1000 else i for i in all_data["age_month"]] # all_data["age_month"] = [np.nan if i < 0 else i for i in all_data["age_month"]] # all_data["login_day"] = [np.nan if i < 0 else i for i in all_data["login_day"]] # all_data["login_dif _time"] = [np.nan if i < 0 else i for i in all_data["login_dif _time"]] # all_data["distance_day"] = [np.nan if i < 0 else i for i in all_data["distance_day"]] all_data["city_num"] = [np.nan if i== "error" else i for i in all_data["city_num"]] ##统计 null 个数 null_count = all_data.isnull().sum(axis=0) print(null_count) plt.figure(figsize=(10,7),dpi=150) plt.bar(null_count.index[:-1],null_count.values[:-1]) plt.title("特征的空值个数统计") plt.xlabel("特征") plt.xticks(rotation=-90,fontsize=12) plt.ylabel("空值个数") plt.text("city_num",null_count.values[4] + 0.05,'%d' % null_count.values[4], ha='center',va='bottom') plt.text("login_day", null_count.values[8] + 0.05, '%d' % null_count.values[8], ha='center', va='bottom') # plt.text("login_dif _time", null_count.values[9] + 0.05, '%d' % null_count.values[9], ha='center', va='bottom') # plt.text("distance_day", null_count.values[10] + 0.05, '%d' % null_count.values[10], ha='center', va='bottom') # plt.text("login_time", null_count.values[11] + 0.05, '%d' % null_count.values[11], ha='center', va='bottom') plt.show() all_data.to_csv("./data/data_first_filter.csv",index=None) return all_data def Gauss_test(data,col,m,s,fill_value,error_cols): error_num = 0 for i in range(len(data)): if m - 3 * s < data[i] and data[i] < m + 3 * s: continue else: error_num += 1 data[i] = fill_value error_cols[col] = error_num return data ####总数据预处理 def data_pre(all_data,Anomaly_detection="",k=1.5): all_data["first_order_time"] = [i.split(" ")[0] for i in all_data["first_order_time"]] label_coder_time = LabelEncoder() all_data["first_order_time"] = label_coder_time.fit_transform(all_data["first_order_time"]) all_data["result"] = all_data["result"].fillna(value=0).astype(int) # for col in all_data.columns: # if not all_data[col].notnull().all(): # if col == "login_dif _time": # m = all_data["login_dif _time"].mean() # print("login_dif _time 的均值:",m) # for i in all_data.index: # if pd.isnull(all_data.loc[i][col]): # all_data.loc[i,col] = m # # null_num = len(all_data[all_data[col].isnull()][col]) # print("%s 有缺失值%d 个!"%(col,null_num)) # count_col = all_data[all_data[col].notnull()][col].value_counts().sort_index(ascending=True) # unique_col = list(count_col.index) # print(type(count_col)) # #print("现有值统计:",count_col) # #print("现有值:",unique_col) # sum_ = sum(count_col) # #print(sum_) # supplement_col = [math.ceil((i / sum_) * null_num) for i in count_col] # #print("每类要补充多少个:",supplement_col) # for i in all_data.index: # if pd.isnull(all_data.loc[i][col]): # #print("##################") # s_l = np.random.randint(0, len(supplement_col)) # while supplement_col[s_l] <= 0: # s_l = np.random.randint(0, len(supplement_col)) # all_data.loc[i,col] = unique_col[s_l] ####修改值用 loc[i,col],查询值用 loc[i][col] # supplement_col[s_l] -= 1 # #print(unique_col[s_l]) # print("补充后缺失值个数:",len(all_data[all_data[col].isnull()])) # else: # print("没有缺失值:{}".format(col)) for col in all_data.columns: if all_data[col].isnull().any(): if col == "login_diff_time": m = all_data[col].mean() all_data[col].fillna(value = m, inplace=True) else: m = all_data[col].mode().values[0] all_data[col].fillna(value = m , inplace=True) else: continue #print(all_data.isnull().any()) #print(all_data["city_num"]) label_coder_city = LabelEncoder() all_data["city_num"] = label_coder_city.fit_transform(all_data["city_num"]) error_cols = {} for col in all_data.columns: # 类别变量不做检查 if col in ["user_id", "first_order_price", "city_num", "platform_num", "model_num", "app_num", "chinese_subscribe_num", "math_subscribe_num", "add_friend", "add_group", "study_num", "result", "first_order_time"]: continue else: # print(col) # print(all_data[col].mean(), all_data[col].std()) # print(all_data[col].mode().values) m, s, fill_value = all_data[col].mean(), all_data[col].std(), all_data[col].mode().values[0] # print(all_data[col].mode()) all_data[col] = Gauss_test(list(all_data[col]), col, m, s, fill_value, error_cols) if col in ["login_diff_time", "first_order_price", "platform_num", "model_num"]: all_data[col] = all_data[col].astype("float32") else: all_data[col] = all_data[col].astype("int32") plt.figure(figsize=(10, 7), dpi=150) plt.bar(error_cols.keys(), error_cols.values()) plt.title("特征的异常值个数统计") plt.xlabel("特征") plt.xticks(rotation=-90, fontsize=12) plt.ylabel("异常值个数") for a, b in zip(error_cols.keys(), error_cols.values()): # 柱子上的数字显示 plt.text(a, b, '%d' % b, ha='center', va='bottom', fontsize=10) plt.show() all_data.to_csv(r"./data/pre_data.csv",index=None) return all_data,error_cols def location_analysis(k=25): city_count = pd.read_csv("./data/city_counts.csv").drop("Unnamed: 0",axis=1).sort_values(["city_num"],ascending=True) sum_num = sum(city_count["city_num"]) city_count["proportion"]=[i/sum_num for i in city_count["city_num"]] print(city_count) print(city_count[:k]["proportion"].sum()) conut_surplus = sum(city_count[:-k]["city_num"]) country = list(city_count.sort_values(["city_num"],ascending=True)[-k:]["index"])+["其他"] country_count = list(city_count.sort_values(["city_num"],ascending=True)[-k:]["city_num"])+[conut_surplus] country.reverse() country_count.reverse() line = Line() line.add("", country, country_count, mark_point=["max", "min"], mark_line=["average"],xaxis_rotate="90") pie = Pie("", title_pos="55%") pie.add("", country, country_count, radius=[45, 65], center=[74, 50], legend_pos="80%", legend_orient="vertical", rosetype="radius", is_legend_show=False, is_label_show=True) grid = Grid(width=900) grid.add(line, grid_right="55%") grid.add(pie, grid_left="50%") grid.render("./data/city_num.html") def login_analysis_first_order_time(): login = pd.read_csv("./data/user_info.csv") login["first_order_time"] = [i.split(" ")[0] for i in login["first_order_time"]] print(login) print(login.groupby(by='first_order_time').user_id.count().index.tolist()) print(login.groupby(by='first_order_time').user_id.count().values.tolist()) page = Page() line = Line() line.add("", login.groupby(by='first_order_time').user_id.count().index.tolist(), login.groupby( by='first_order_time').user_id.count().values.tolist(), mark_point=["max", "min"], mark_line=["average"], xaxis_rotate="90" ,xaxis_name="首次体验课下单时间",yaxis_name="用户数",xaxis_name_gap=2, yaxis_name_gap = 50) page.add(line) return page ###登录时间分析 def login_analysis_login_day(): login = pd.read_csv("./data/pre_data.csv") login_1 = login[login["result"] == 1] login_2 = login[login["result"] == 0] print("均值_all,均值_1,均值_0: ",login["login_day"].mean(),login_1["login_day"].mean(),login_2["login_day"].mean()) print(login["login_day"].value_counts().sort_values()) print(login_1["login_day"].value_counts().sort_values()) print(login_2["login_day"].value_counts().sort_values()) count_1 = login_1["login_day"].value_counts().sort_index() print(count_1) count_2 = login_2["login_day"].value_counts().sort_index() print(count_2) x = count_2.index page = Page() style = Style(height=600, width=1400) bar = Bar('', **style.init_style, background_color=['white']) bar.add('已购买', x ,count_1, mark_line=['average'], mark_point=['min', 'max'],xaxis_name="登录天数 ",yaxis_name="用户数") bar.add('未购买', x, count_2, mark_line=['average'], mark_point=['min', 'max'], is_legend_show=True,xaxis_name="登录天数",yaxis_name="用户数") page.add(bar) return page ####登录间隔分析 def login_analysis_login_diff_time(): login = pd.read_csv("./data/pre_data.csv") print(login["login_diff_time"].mean()) print(login["login_diff_time"].value_counts().sort_values()) unique_login_diff_time = sorted(login["login_diff_time"].unique().tolist()) df_1 = pd.DataFrame({"value":unique_login_diff_time}) df_2 = pd.DataFrame({"value": unique_login_diff_time}) login_1 = login[login["result"] == 1] login_2 = login[login["result"] == 0] print(login_1["login_diff_time"].mean()) print(login_2["login_diff_time"].mean()) print(login_1["login_diff_time"].value_counts().sort_values()) print(login_2["login_diff_time"].value_counts().sort_values()) count_1 = login_1["login_diff_time"].value_counts().sort_index().to_frame().rename(columns={"login_diff_time":"sup plement"}) count_2 = login_2["login_diff_time"].value_counts().sort_index().to_frame().rename(columns={"login_diff_time":"sup plement"}) # print(count_1) # print(count_2) count_1["value"] = count_1.index count_2["value"] = count_2.index count_1.reset_index(drop = True, inplace=True) count_2.reset_index(drop = True, inplace=True) count_1 = pd.merge(df_1,count_1,on = "value",how = "left") count_2 = pd.merge(df_2,count_2,on = "value",how = "left") count_1.fillna({"supplement":0},inplace = True) count_2.fillna({"supplement":0},inplace = True) # print(count_1) # print(count_2) count_sort_1 = count_1.sort_values(by="supplement") count_sort_2 = count_2.sort_values(by="supplement") qt_1 = sum(count_sort_1["supplement"][:-20]) qt_2 = sum(count_sort_2["supplement"][:-20]) count_1 = count_sort_1["supplement"][-20:] count_2 = count_sort_2["supplement"][-20:] # print(count_1) # print(count_2) x_1 = list(count_sort_2["value"][-20:]) + ["其他"] x_2 = list(count_sort_2["value"][-20:]) + ["其他"] print(x_1,list(count_1) + [qt_1]) print(x_2,list(count_2) + [qt_2]) pie = Pie("已购买", title_pos="20%") pie.add("", x_1, list(count_1) + [qt_1], radius=[45, 65], center=[24, 50], legend_pos="80%", legend_orient="vertical", rosetype="radius", is_legend_show=False, is_label_show=True,xaxis_interval=0) pie1 = Pie("未购买", title_pos="55%") pie1.add("", x_2, list(count_2) + [qt_2], radius=[45, 65], center=[58,50], legend_pos="80%", legend_orient="vertical", rosetype="radius", is_legend_show=False, is_label_show=True,xaxis_interval=0) grid = Grid(width=1300) grid.add(pie) grid.add(pie1) return grid ####登录距离期末天数分析 def login_analysis_distance_day(): login = pd.read_csv("./data/pre_data.csv") login["distance_day"] = [i-365 if i > 365 else i for i in login["distance_day"]] login["distance_day"] = [i - 180 if i > 180 else i for i in login["distance_day"]] login["distance_day"] = [i - 180 if i > 180 else i for i in login["distance_day"]] print(login["distance_day"].mean()) print(login["distance_day"].value_counts().sort_values()) unique_login_diff_time = sorted(login["distance_day"].unique().tolist()) #print(unique_login_dif _time) df_1 = pd.DataFrame({"value":unique_login_diff_time}) df_2 = pd.DataFrame({"value": unique_login_diff_time}) login_1 = login[login["result"] == 1] login_2 = login[login["result"] == 0] print(login_1["distance_day"].mean()) print(login_2["distance_day"].mean()) print(login_1["distance_day"].value_counts().sort_values()) print(login_2["distance_day"].value_counts().sort_values()) count_1 = login_1["distance_day"].value_counts().sort_index().to_frame().rename(columns={"distance_day":"supplem ent"}) count_2 = login_2["distance_day"].value_counts().sort_index().to_frame().rename(columns={"distance_day":"supplem ent"}) count_1["value"] = count_1.index count_2["value"] = count_2.index count_1.reset_index(drop = True, inplace=True) count_2.reset_index(drop = True, inplace=True) count_1 = pd.merge(df_1,count_1,on = "value",how = "left") count_2 = pd.merge(df_2,count_2,on = "value",how = "left") count_1.fillna({"supplement":0},inplace = True) count_2.fillna({"supplement":0},inplace = True) print(count_1) print(count_2) x = count_1["value"].values page = Page() style = Style(height=600, width=1400) bar = Bar('', **style.init_style, background_color=['white']) bar.add('已购买', x , count_1["supplement"], mark_line=['average'], mark_point=['min', 'max'],xaxis_name="登录距离期末天数",yaxis_name="用户数") bar.add('未购买', x, count_2["supplement"], mark_line=['average'], mark_point=['min', 'max'], is_legend_show=True,xaxis_name="登录距离期末天数",yaxis_name="用户数") page.add(bar) return page ####登陆时长分析 def login_analysis_login_time(): login = pd.read_csv("./data/pre_data.csv") print(login["login_time"].mean()) print(login["login_time"].value_counts().sort_values(ascending=True)) unique_login_diff_time = sorted(login["login_time"].unique().tolist()) df_1 = pd.DataFrame({"value":unique_login_diff_time}) df_2 = pd.DataFrame({"value": unique_login_diff_time}) login_1 = login[login["result"] == 1] login_2 = login[login["result"] == 0] print(login_1["login_time"].mean()) print(login_2["login_time"].mean()) print(login_1["login_time"].value_counts().sort_values(ascending=True)) print(login_2["login_time"].value_counts().sort_values(ascending=True)) count_1 = login_1["login_time"].value_counts().sort_index().to_frame().rename(columns={"login_time":"supplement"} ) count_2 = login_2["login_time"].value_counts().sort_index().to_frame().rename(columns={"login_time":"supplement"} ) count_1["value"] = count_1.index count_2["value"] = count_2.index count_1.reset_index(drop = True, inplace=True) count_2.reset_index(drop = True, inplace=True) count_1 = pd.merge(df_1,count_1,on = "value",how = "left") count_2 = pd.merge(df_2,count_2,on = "value",how = "left") count_1.fillna({"supplement":0},inplace = True) count_2.fillna({"supplement":0},inplace = True) # print(count_2["supplement"].sort_values(ascending=True)) # print(count_1["supplement"].sort_values(ascending=True)) x = count_1["value"].values page = Page() style = Style(height=600, width=1400) bar = Bar('', **style.init_style, background_color=['white']) bar.add('已购买', x , count_1["supplement"], mark_line=['average'], mark_point=['min', 'max'],xaxis_name="登录时长",yaxis_name="用户数",yaxis_name_gap=50) bar.add('未购买', x, count_2["supplement"], mark_line=['average'], mark_point=['min', 'max'], is_legend_show=True,xaxis_name="登录时长",yaxis_name="用户数" ,yaxis_name_gap=50) page.add(bar) return page
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。