«Это 28-й день моего участия в ноябрьском испытании обновлений. Подробную информацию об этом событии см.:Вызов последнего обновления 2021 г."
EDA в наборе данных:Наггетс.Талант/пост/703488…
Данные анализируются и добываются ниже, в основном для составления прогноза продаж продуктов, приобретаемых пользователями.
Настройки разделения набора данных
Учебный набор и машина проверки устанавливаются в соотношении 7: 3 и сохраняются, а распределение данных визуализируется.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split # 数据分割
import warnings##忽略警告
warnings.filterwarnings('ignore')
plt.style.use('ggplot')
# 设置画图的可显示中文字体
plt.rcParams['font.sans-serif'] = 'Microsoft YaHei'
df = pd.read_csv("product_data.csv")
X = df.drop(["Purchase"],axis=1)
y = df.Purchase
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.3)
# 训练集大小 测试集大小
print(X_train.shape,X_test.shape,y_train.shape,y_test.shape)
# 观察划分的数据集一致性,使用hist查看Purchase的分布
colors = ['salmon','lightskyblue','#FFF68F','palegreen','lightpink','silver','burlywood','plum','rosybrown']
plt.figure(figsize=(12,5))
plt.subplot(131)
ax1=plt.subplot(1, 3, 1)
ax1.hist(y_train,color=colors[0])
ax1.set_title("训练集分布图")
ax2=plt.subplot(1, 3, 2)
ax2.hist(y_test,color=colors[1])
ax2.set_title("测试集分布图")
ax3=plt.subplot(1, 3, 3)
ax3.hist(y_test,color=colors[0])
ax3.hist(y_test,color=colors[1])
ax3.set_title("训练集-测试集比较分布图")
plt.show()
df_train = pd.concat([X_train,y_train],axis=1)
df_test = pd.concat([X_test,y_test],axis=1)
df_train.to_csv("./data/train.csv",index=False)
df_test.to_csv("./data/test.csv",index=False)
Выход: (376303, 11) (161274, 11) (376303,) (161274,)
Очистка данных
# 导入基础包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import pandas_profiling as ppf
import warnings##忽略警告
warnings.filterwarnings('ignore')
# 设置画图的可显示中文字体
plt.rcParams['font.sans-serif'] = 'Microsoft YaHei'
plt.style.use('ggplot')
# 导入包做特征工程
from sklearn.base import BaseEstimator, TransformerMixin, RegressorMixin, clone
from sklearn.preprocessing import LabelEncoder#标签编码
from sklearn.preprocessing import RobustScaler, StandardScaler#去除异常值与数据标准化
from sklearn.pipeline import Pipeline, make_pipeline#构建管道
from scipy.stats import skew#偏度
from sklearn import impute
# 链接两张表
train = pd.read_csv("./data/train.csv")
test = pd.read_csv("./data/test.csv")
full = pd.concat([train,test],ignore_index=True)
full.head()
Очистка данных в основном делится на три части: 1. Заполните пропущенные значения 2. Исправьте неправильные значения 3. Работа с числовыми несоответствиями
Обработка пропущенных значений
Сначала удалите столбец ID и продажи.
full.drop(["User_ID","Product_ID","Purchase"],axis=1,inplace=True)
full.Product_Category_2.value_counts()
Product_Category_2 и Product_Category_3 являются категориальными переменными, сначала примените стратегию заполнения режима
# 采用众数填充,full[col].mode()[0]表示取众数
cols1 = ["Product_Category_2","Product_Category_3"]
for col in cols1:
full[col].fillna(full[col].mode()[0],inplace=True)
Струнные переменные кодируются как числовые переменные
Переменные строкового типа, такие как пол, возраст, класс города, год поселения и т. д., преобразуются в коды категорий. Пол: «Мужской»: 0, «Женский»: 1 Возраст: '0-17':0,'18-25':1,'26-35':2,'36-45':3,'46-50':4,'51-55':5, «55+»: 6 Класс города: «А»: 0, «В»: 1, «С»: 2 Год поселения: «0»: 0, «1»: 1, «2»: 2, «3»: 3, «4+»: 4.
# 性别
full.Gender = full.Gender.map({'F':0,"M":1}).astype(int)
# 年龄
full.Age = full.Age.map({'0-17':0,'18-25':1,'26-35':2,'36-45':3,'46-50':4,'51-55':5,'55+':6}).astype(int)
# 城市等级
full.City_Category = full.City_Category.map({'A':0,"B":1,"C":2}).astype(int)
# 定居年份
full.Stay_In_Current_City_Years = full.Stay_In_Current_City_Years.map({'0':0,'1':1,"2":2,"3":3,'4+':4}).astype(int)
Преобразование типов данных
Преобразование числового столбца в соответствующий тип данных
cols1 = ["Gender","Age","Occupation","City_Category","Stay_In_Current_City_Years",
"Marital_Status","Product_Category_1",]
for col in cols1:
full[col] = full[col].astype(np.int64)
Повторно разделить и нормализовать
стандартизация
n_train=train.shape[0]#训练集的行数
X = full[:n_train]#取出处理之后的训练集
test_X = full[n_train:]#取出n_train后的数据作为测试集
y= train.Purchase
X_scaled = StandardScaler().fit(X).transform(X)#做转换
y_log = np.log(train.Purchase)##这里要注意的是,更符合正态分布
#得到测试集
test_X_scaled = StandardScaler().fit_transform(test_X)
Выбор функций
Выбор признаков с использованием регрессии Лассо
from sklearn.linear_model import Lasso##运用算法来进行训练集的得到特征的重要性,特征选择的一个作用是,wrapper基础模型
lasso=Lasso(alpha=0.001)
lasso.fit(X_scaled,y_log)
FI_lasso = pd.DataFrame({"Feature Importance":lasso.coef_}, index=full.columns)#索引和重要性做成dataframe形式
FI_lasso.sort_values("Feature Importance",ascending=False)#由高到低进行排序
FI_lasso[FI_lasso["Feature Importance"]!=0].sort_values("Feature Importance").plot(kind="barh",color="salmon",figsize=(10,8))
plt.title("特征选择重要性表示图")
plt.xticks(rotation=90)
plt.show()##画图显示
Выбор функций не сильно отличается, или используйте все функции
Особенности строительства
Последующие прогнозы работают плохо, повторно анализируют данные, строят признаки
# 单位商品的平均销售额
avg_purchase_per_product=pd.DataFrame(full.groupby(['Product_ID'])['Purchase'].mean())
avg_purchase_per_product.reset_index(inplace=True)
# 单用户的平均销售额
avg_purchase_per_user=pd.DataFrame(full.groupby(['User_ID'])['Purchase'].mean())
avg_purchase_per_user.reset_index(inplace=True)
# 购买次数
product_count=pd.DataFrame(full['Product_ID'].value_counts())
product_count.reset_index(inplace=True)
product_count=product_count.rename(columns={'index':'Product_ID','Product_ID':'Product_count'})
# 添加 三个新特征
full['avg_purchase_per_product']=full['Product_ID'].map(avg_purchase_per_product.set_index('Product_ID')['Purchase'])
full['product_count']=full['Product_ID'].map(product_count.set_index('Product_ID')['Product_count'])
full['avg_purchase_per_user']=full['User_ID'].map(avg_purchase_per_user.set_index('User_ID')['Purchase'])
# 把隐形的商品类别损失也计算为一种特征
conditions = [
(full['Product_Category_1'] != 0) & (full['Product_Category_2'] == 0) & (full['Product_Category_3'] == 0),
(full['Product_Category_1'] != 0) & (full['Product_Category_2'] != 0) & (full['Product_Category_3'] == 0),
(full['Product_Category_1'] != 0) & (full['Product_Category_2'] != 0) & (full['Product_Category_3'] != 0)]
choices = [1, 2, 3]
# 添加商品类别计数
full['Category_Count'] = np.select(conditions, choices, default=0)
Только после вышеуказанной предварительной обработки данных и с помощью нескольких методов, таких как тестирование одной модели и слияние нескольких моделей, эффект модели кэшбэка для прогнозирования суммы потребления пользователем не идеален. Алгоритмическую модель было трудно улучшить, поэтому мы надеемся выполнить разработку признаков на уровне данных.Исходный набор данных имеет не так много измерений признаков, и важность признаков также очень слаба.Поэтому наша стратегия состоит в том, чтобы создать более высокий коэффициент корреляции посредством создания признаков. Всего было добавлено 4 измерения объектов. Шаги для создания измерения объекта следующие:
1. Количество категорий товаров Category_counts
Будь то тепловая карта визуализации данных или важные коэффициенты при выборе признаков, корреляция товарных категорий очень высока, что показывает, что товарные категории имеют определенную ценность для майнинга. Ранее мы всегда рассматривали категорию продуктов 2 и категорию продуктов 3 как отсутствующие значения, но также весьма вероятно, что потребители не покупали продукты категории продуктов 2 и категории продуктов 3, поэтому количество различных видов продуктов, имеющихся у каждого пользователя, является индикатор счета.
Общий лимит расходов Product_counts 2. Каждый из Product_ID
3. Средняя сумма потребления каждого User_ID avg_purchase_per_product
4. Средняя сумма потребления каждого Product_ID avg_purchase_per_product
Мы надеемся найти больше скрытой информации о продуктах. Product_ID — неиспользуемая функция. Общий объем этого набора данных составляет более 500 000, но реальных пользователей и продуктов всего несколько тысяч. Поэтому для User_ID и Product_ID статистические измерения должна иметь возможность получать больше скрытой информации, поэтому рассчитывается общая сумма потребления каждого Product_ID, а затем рассчитывается средняя сумма потребления каждого User_ID и средняя сумма потребления каждого Product_ID.
Прогнозирование продаж — проблема регрессии
# 导入基础包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings##忽略警告
warnings.filterwarnings('ignore')
# 设置画图的可显示中文字体
plt.rcParams['font.sans-serif'] = 'Microsoft YaHei'
plt.style.use('ggplot')
# 数据预处理函数
def Data_Clearing(full):
# 除去商品ID含的字符串
full['Product_ID']=full['Product_ID'].str.slice(2).astype(int)
# 采用零填充缺失值
cols1 = ["Product_Category_2","Product_Category_3"]
for col in cols1:
full[col].fillna(0,inplace=True)
# 性别
full.Gender = full.Gender.map({'F':0,"M":1}).astype(int)
# 年龄
full.Age = full.Age.map({'0-17':17,'18-25':25,'26-35':35,'36-45':45,'46-50':50,'51-55':55,'55+':60}).astype(int)
# 城市等级
full.City_Category = full.City_Category.map({'A':0,"B":1,"C":2}).astype(int)
# 定居年份
full.Stay_In_Current_City_Years = full.Stay_In_Current_City_Years.map({'0':0,'1':1,"2":2,"3":3,'4+':4}).astype(int)
# 特征生成
# 单位商品的平均销售额
avg_purchase_per_product=pd.DataFrame(full.groupby(['Product_ID'])['Purchase'].mean())
avg_purchase_per_product.reset_index(inplace=True)
# 单用户的平均销售额
avg_purchase_per_user=pd.DataFrame(full.groupby(['User_ID'])['Purchase'].mean())
avg_purchase_per_user.reset_index(inplace=True)
# 购买次数
product_count=pd.DataFrame(full['Product_ID'].value_counts())
product_count.reset_index(inplace=True)
product_count=product_count.rename(columns={'index':'Product_ID','Product_ID':'Product_count'})
# 添加 三个新特征
full['avg_purchase_per_product']=full['Product_ID'].map(avg_purchase_per_product.set_index('Product_ID')['Purchase'])
full['product_count']=full['Product_ID'].map(product_count.set_index('Product_ID')['Product_count'])
full['avg_purchase_per_user']=full['User_ID'].map(avg_purchase_per_user.set_index('User_ID')['Purchase'])
# 把隐形的商品类别损失也计算为一种特征
conditions = [
(full['Product_Category_1'] != 0) & (full['Product_Category_2'] == 0) & (full['Product_Category_3'] == 0),
(full['Product_Category_1'] != 0) & (full['Product_Category_2'] != 0) & (full['Product_Category_3'] == 0),
(full['Product_Category_1'] != 0) & (full['Product_Category_2'] != 0) & (full['Product_Category_3'] != 0)]
choices = [1, 2, 3]
# 添加商品类别计数
full['Category_Count'] = np.select(conditions, choices, default=0)
return full
# 读取数据集
train = pd.read_csv("./product_data.csv")
# 数据清洗和处理
data = Data_Clearing(train)
выбор модели
from sklearn.model_selection import cross_val_score, GridSearchCV, KFold,train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor, ExtraTreesRegressor
from sklearn.svm import SVR, LinearSVR
from sklearn.linear_model import ElasticNet, SGDRegressor, BayesianRidge
from sklearn.kernel_ridge import KernelRidge
from xgboost import XGBRegressor
import time
# 先准备好评价函数RMSE和R2,RMSE越小越好,R2越接近1越好
from sklearn.metrics import mean_squared_error as MSE ,r2_score as R2
# 评价函数
def print_metrics(predict):
mse = MSE(y_true=y_test,y_pred=predict)
rmse = np.sqrt(MSE(y_true=y_test,y_pred=predict))
r2 = R2(y_true=y_test,y_pred=predict)
print("MSE:",mse)
print("RMSE:",rmse)
print("R2:",r2)
# 划分训练集和测试集
X=train.drop('Purchase',axis=1)
y=train['Purchase']
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3,random_state=42,shuffle=True)
models = [LinearRegression(),
Ridge(),
Lasso(alpha=0.01,max_iter=10000),
RandomForestRegressor(),
GradientBoostingRegressor(),
ElasticNet(),
SGDRegressor(),
BayesianRidge(),
ExtraTreesRegressor(),
XGBRegressor()]
# 定义存储列表
pred_df = pd.DataFrame()
pred_df["predict"] = y_test
rmses = []
r2s = []
all = time.clock()
# ========================计算时间
names = ["LR", "Ridge", "Lasso", "RF", "GBR","Ela","SGD","Bay","Extra","XGB"]
for name, model in zip(names, models):
pre_start = time.clock()
#============================================
print("%s" % name)
# 训练模型
now_model = model.fit(X,y)
model_predict = now_model.predict(X_test)
# 得到rmse和r2的评分
rmse = np.sqrt(MSE(y_true=y_test,y_pred=model_predict))
r2 = R2(y_true=y_test,y_pred=model_predict)
# 存rmse和r2,以及模型预测的结果
pred_df["predict_"+name] = model_predict
rmses.append(rmse)
r2s.append(r2)
# 打印结果
print_metrics(model_predict)
elapsed = (time.clock() - pre_start)
#============================================
print("Time used:",elapsed)
print("-"*20)
# ========================计算时间
print("-"*20)
elapsed = (time.clock() - all)
print("Time used:",elapsed)
rmse и r2, полученные с помощью модели SGD, слишком велики, что серьезно мешает рисованию Мы можем изменить значение SGD, чтобы отразить плохой эффект SGD.
site = rmses.index(max(rmses))
rmses[site] = 4999
r2s[site] = -2.0
#添加画布,设置画布大小
plt.figure(figsize=(10,8),dpi=600)
plt.barh(range(len(rmses)),rmses,height=0.7, color='salmon', alpha=0.8) # 从下往上画
plt.yticks(range(len(rmses)),names)
plt.xlim(0,5500) #30~47,各1
plt.xlabel(u"RMSE")
plt.ylabel(u'Models')
plt.title(u"RMSE")
#这是设置柱状条上面的数值,来自RMSEs
for x_,y_ in enumerate(rmses):
plt.text(y_,x_-0.1, '%s' % y_)
plt.show()
слияние моделей
from sklearn import ensemble,linear_model
from mlxtend.regressor import StackingCVRegressor,StackingRegressor
# 采用stacking策略进行模型融合
# 第一层
clf1_1 = ensemble.RandomForestRegressor()
clf1_2 = ensemble.ExtraTreesRegressor()
# 第二层
clf2 = XGBRegressor()
np.random.seed(42)
stack = StackingRegressor(regressors=[clf1_1,clf1_2],meta_regressor=clf2)
start = time.clock()
# ========================计算时间
print("%s" % "stack")
names.append("stack")
now_model = stack.fit(X,y)
model_predict = now_model.predict(X_test)
rmse = np.sqrt(MSE(y_true=y_test,y_pred=model_predict))
r2 = R2(y_true=y_test,y_pred=model_predict)
pred_df["predict_stack"] = model_predict
rmses.append(rmse)
r2s.append(r2)
print_metrics(model_predict)
print("-"*20)
# ========================计算时间
elapsed = (time.clock() - start)
print("Time used:",elapsed)
#添加画布,设置画布大小
plt.figure(figsize=(12,8),dpi=600)
plt.barh(range(11), rmses, height=0.7, color='salmon', alpha=0.8) # 从下往上画
plt.yticks(range(11), names)
plt.xlim(0,5500) #30~47,各1
plt.xlabel(u"RMSE")
plt.ylabel(u'Models')
plt.title(u"RMSE")
#这是设置柱状条上面的数值,来自RMSEs
for x_,y_ in enumerate(rmses):
plt.text(y_+0.025, x_-0.1, '%s' % y_)
plt.show()
#添加画布,设置画布大小
plt.figure(figsize=(12,8),dpi=600)
plt.barh(range(11), r2s, height=0.7, color='salmon', alpha=0.8) # 从下往上画
plt.yticks(range(11), names)
plt.xlim(-2.5,2.5) #30~47,各1
plt.xlabel(u"R2")
plt.ylabel(u'Models')
plt.title(u"R2")
#这是设置柱状条上面的数值,来自RMSEs
for x_,y_ in enumerate(r2s):
plt.text(y_+0.025,x_-0.1, '%s' % y_)
plt.show()
Видно, что прогнозируемые продажи все еще очень близки.