订单需求预估

之前写了一篇以基于elastic的需求预估的文章,只不过用的是R语言开发的,最近在学python,就仿照逻辑写了一篇python的,主要修改点如下:

  • 用决策树替换了elastic算法
  • 用分层抽样替换了组合抽样

需要看详细理论及思考过程参考链接:商品需求预估

python code如下:

1
2
3
4
5
6
7
8
9
# -*- coding:utf-8 -*-
import pandas as pd
import numpy as np
import random as rd
from sklearn import tree

# 读取数据
data_orgin = pd.read_table("C:/Users/17031877/Desktop/supermarket_second_hair_washing_train.txt")
data_deal_1 = data_orgin.drop(['aimed_date', 'member_id', 'age', 'gender', 'diff_rgst'], axis=1)

这边是常规的数据读取,删除了不必要的列


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#因变量单列
label = data_deal_1['label']

# 用户分量级
value00 = ['max_date_diff', 'aimed_max_date_diff']
data00 = data_deal_1[value00]

value01 = ['max_pay', 'per_pay', 'six_month_max_pay', 'six_month_per_pay', 'three_month_max_pay', 'three_month_per_pay',
'one_month_max_pay', 'one_month_per_pay', 'fifteen_day_max_pay', 'fifteen_day_per_pay', 'aimed_max_pay',
'aimed_per_pay', 'aimed_six_month_max_pay', 'aimed_six_month_per_pay', 'aimed_three_month_max_pay',
'aimed_three_month_per_pay', 'aimed_one_month_max_pay', 'aimed_one_month_per_pay',
'aimed_fifteen_day_max_pay', 'aimed_fifteen_day_per_pay', 'qty_drtn_seven', 'qty_drtn_fourteen']
data01 = data_deal_1[value01]

value02 = ['cnt_time', 'six_month_cnt_time', 'three_month_cnt_time', 'one_month_cnt_time', 'fifteen_day_cnt_time',
'aimed_cnt_time', 'aimed_six_month_cnt_time', 'aimed_three_month_cnt_time', 'aimed_one_month_cnt_time',
'aimed_fifteen_day_cnt_time', 'pv_times_seven', 'pv_times_fourteen', 'search_times_seven',
'search_times_fourteen', 'clc_times_seven', 'clc_times_fourteen', 'cart2_times_seven',
'cart2_times_fourteen', 'cart1_times_seven', 'cart1_times_fourteen', 'unpay_times_seven',
'unpay_times_fourteen']
data02 = data_deal_1[value02]

value03 = ['pv_visit_last_period', 'search_last_period', 'clc_last_period', 'cart2_last_period', 'cart1_last_period',
'unpay_last_period']
data03 = data_deal_1[value03]

因为不同量级的数据之后做异常点处理的时候截断位置不同,所有需要分割数据处理


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def test_function_one(x, l):
k = x.dropna(how='any')
y = k.quantile(l)
z = k.max()
x[x > y] = y
x = x.fillna(value=z)
return x
for i in range(len(data00.columns)):
data00.iloc[:, i] = test_function_one(data00.iloc[:, i], 0.98)

def test_function_two(x, l):
k = x.dropna(how='any')
y = k.quantile(l)
z = 0
x[x > y] = y
x = x.fillna(value=z)
return x
for i in range(len(data01.columns)):
data01.iloc[:, i] = test_function_two(data01.iloc[:, i], 0.95)
for i in range(len(data02.columns)):
data02.iloc[:, i] = test_function_two(data02.iloc[:, i], 0.99)

def test_function_three(x):
z = 14
x[x > z] = z
x = x.fillna(value=z)
return x
for i in range(len(data03.columns)):
data03.iloc[:, i] = test_function_three(data03.iloc[:, i])
# 数据合并
data_train = pd.concat([label, data00, data01, data02, data03], axis=1)

根据数据量的不同做数据分割,跑上面写完的code函数就可以


1
2
3
4
5
6
7
8
9
#数量级对比
zero_case = data_train[data_train['label'] == 0]['label'].count()
print '负样本数:%d' % zero_case
one_case = data_train[data_train['label'] == 1]['label'].count()
print '正样本数: %d' % (one_case)

负样本数:292936
正样本数: 3973
Backend TkAgg is interactive backend. Turning interactive mode on.

实际看下来,正负样本的差异的确还是很大,这个其实做多了就有经验,常规的来看,潜在的浏览、搜索到最后的成单,普遍自然转化不到1%,也正是这么低的转化,才需要一些算法来做信息抓去。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def case_sample(x, y, z):
diff_case = pd.DataFrame(x[y]).drop_duplicates([y])
result = []
result = pd.DataFrame(result)
for i in range(len(diff_case)):
k = np.array(diff_case)[i]
data_set = x[x[y] == k[0]]
nrow_nb = data_set.iloc[:, 0].count()
data_set.index = range(nrow_nb)
index_id = rd.sample(range(nrow_nb), int(nrow_nb * z))
result = pd.concat([result, data_set.iloc[index_id, :]], axis=0)
return result


zero_case = data_train[data_train['label'] == 0]
one_case = data_train[data_train['label'] == 1]
# 开始分层抽样
new_zero_case = case_sample(zero_case, 'unpay_last_period', 0.1)
# 新数量级对比
new_zero_case_count = new_zero_case[new_zero_case['label'] == 0]['label'].count()
# 数据集合并
new_data_train = pd.concat([new_zero_case, one_case], axis=0)

case_sample是一个简单的分层抽样的小函数,x是数据集,y是分层变量,z是抽样占比;新的样本new_data_train中正负样本比例在1:10左右,这边的样本比是我自己设置的,不一定是最合理的;且此处也不一定要求一定用分层抽样,只是我用来练练手的;推荐还是遵从奥卡姆原理,在未知的情况下,尽可能简单的解决问题,比如组合抽样就是很不错的方法。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#函数设置
clf = tree.DecisionTreeRegressor(criterion='mse', max_features='log2', random_state=1)

#函数拟合
y = new_data_train['label']
x = new_data_train.drop('label', 1)
clf.fit(x, y)

#数据预测
y_predict = clf.predict(x)

# 结果对比
y.index = range(len(y))
combined_date = pd.concat([y, pd.DataFrame(y_predict)], axis=1)
combined_date.columns = ['actual', 'predict']

这边稍微讲解一下,我认为的sklearnDecisionTreeRegressor中比较终于的参数设置,criterion这边为模型优化的标准,常规的有msemae,建议在数据量差异不大的时候多考虑msemax_features是每次训练用的特征个数,综合特征量级考虑,一般有log2sqrt,尽可能是抽取比例在70%;max_depth刚开始可以默认,第一类模型出来后,可在结果附近迭代,寻找out of bag最小的error下的值;另外,我没有发现有weight设置,可能是我不熟悉,但是如果sklearn这边不提供weight的化,我们在做数据预处理的时候一定要平衡数据,不然当数据集过偏的时候最后的结果会以“牺牲”少类的判断正确率去完善整体正确率。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
# case 1
x = []
y = []
for i in range(1, 10):
test_data = combined_date
i = i / float(10)
for j in range(combined_date['actual'].count()):
if test_data.iloc[j, 1] > i:
test_data.iloc[j, 1] = 1
else:
continue
z = test_data[test_data['actual'] == test_data['predict']]['actual'].count() / float(test_data['actual'].count())
x.append(i)
y.append(z)

这边写了检查函数,检查了分别0.1~1,以0.1为间隔的情况下的分割点,每个分割点下预测正确的数量/所有统计的样本数,也就是下面的accuracy.


1
2
3
4
5
6
7
8
# case 2
test_data = combined_date
aimed_data = test_data[test_data['predict']>0]
k1=aimed_data[aimed_data['actual']==1]['predict'].count()
k2=float(aimed_data['predict'].count())

print '所有预测可能下单用户中真实下单用户数:%d' %(k1)
print '所有预测可能下单用户数:%d' %(k2)

因为这边需要对用户营销,所以更关系topN的转化率,需要看一下实际正样本被覆盖了多数,以上即为code,这边的效果值为98.7%,还是比较高的,但是应该是过拟合了,所有一般不建议单纯使用决策树模型


所有的python code到这里就结束了,后续我做项目的同时会同时更新R及python两种code的思考,和大家讨论分享学习,谢谢。

参考文献:

打赏的大佬可以联系我,赠送超赞的算法资料