文章目录
- 一、算法和背景介绍
- 二、Python代码和Sentosa_DSML社区版算法实现对比
- (一) 数据读入与统计分析
- (二) 数据处理
- (三) 特征选择与相关性分析
- (四) 样本分区与模型训练
- (五) 模型评估和模型可视化
- 三、总结
一、算法和背景介绍
关于XGBoost的算法原理,已经进行了介绍与总结,相关内容可参考【机器学习(一)】分类和回归任务-XGBoost算法-Sentosa_DSML社区版一文。本文以预测二手车的交易价格为目标,通过Python代码和Sentosa_DSML社区版分别实现构建XGBoost回归预测模型,并对模型进行评估,包括评估指标的选择与分析。最后得出实验结论,确保模型在二手汽车价格回归预测中的有效性和准确性。
数据集介绍
以预测二手车的交易价格为任务,数据来自某交易平台的二手车交易记录,总数据量超过40w,包含31列变量信息,其中15列为匿名变量。数据集概况介绍:
二、Python代码和Sentosa_DSML社区版算法实现对比
(一) 数据读入与统计分析
1、python代码实现
import os
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from datetime import datetime
from matplotlib import rcParams
from sklearn.preprocessing import MaxAbsScaler
from sklearn.model_selection import train_test_split
from xgboost import XGBRegressor
from xgboost import plot_importance
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
数据读入
file_path = r'.\二手汽车价格.csv'
output_dir = r'.\xgb'
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件未找到: {file_path}")
if not os.path.exists(output_dir):
os.makedirs(output_dir)
df = pd.read_csv(file_path)
print(df.isnull().sum())
print(df.head())
>> SaleID name regDate model ... v_11 v_12 v_13 v_14
0 0 736 20040402 30.0 ... 2.804097 -2.420821 0.795292 0.914763
1 1 2262 20030301 40.0 ... 2.096338 -1.030483 -1.722674 0.245522
2 2 14874 20040403 115.0 ... 1.803559 1.565330 -0.832687 -0.229963
3 3 71865 19960908 109.0 ... 1.285940 -0.501868 -2.438353 -0.478699
4 4 111080 20120103 110.0 ... 0.910783 0.931110 2.834518 1.923482
统计分析
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['SimHei']
stats_df = pd.DataFrame(columns=[
'列名', '数据类型', '最大值', '最小值', '平均值', '非空值数量', '空值数量',
'众数', 'True数量', 'False数量', '标准差', '方差', '中位数', '峰度', '偏度',
'极值数量', '异常值数量'
])
def detect_extremes_and_outliers(column, extreme_factor=3, outlier_factor=5):
if not np.issubdtype(column.dtype, np.number):
return None, None
q1 = column.quantile(0.25)
q3 = column.quantile(0.75)
iqr = q3 - q1
lower_extreme = q1 - extreme_factor * iqr
upper_extreme = q3 + extreme_factor * iqr
lower_outlier = q1 - outlier_factor * iqr
upper_outlier = q3 + outlier_factor * iqr
extremes = column[(column < lower_extreme) | (column > upper_extreme)]
outliers = column[(column < lower_outlier) | (column > upper_outlier)]
return len(extremes), len(outliers)
for col in df.columns:
col_data = df[col]
dtype = col_data.dtype
if np.issubdtype(dtype, np.number):
max_value = col_data.max()
min_value = col_data.min()
mean_value = col_data.mean()
std_value = col_data.std()
var_value = col_data.var()
median_value = col_data.median()
kurtosis_value = col_data.kurt()
skew_value = col_data.skew()
extreme_count, outlier_count = detect_extremes_and_outliers(col_data)
else:
max_value = min_value = mean_value = std_value = var_value = median_value = kurtosis_value = skew_value = None
extreme_count = outlier_count = None
non_null_count = col_data.count()
null_count = col_data.isna().sum()
mode_value = col_data.mode().iloc[0] if not col_data.mode().empty else None
true_count = col_data[col_data == True].count() if dtype == 'bool' else None
false_count = col_data[col_data == False].count() if dtype == 'bool' else None
new_row = pd.DataFrame({
'列名': [col],
'数据类型': [dtype],
'最大值': [max_value],
'最小值': [min_value],
'平均值': [mean_value],
'非空值数量': [non_null_count],
'空值数量': [null_count],
'众数': [mode_value],
'True数量': [true_count],
'False数量': [false_count],
'标准差': [std_value],
'方差': [var_value],
'中位数': [median_value],
'峰度': [kurtosis_value],
'偏度': [skew_value],
'极值数量': [extreme_count],
'异常值数量': [outlier_count]
})
stats_df = pd.concat([stats_df, new_row], ignore_index=True)
print(stats_df)
def save_plot(filename):
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_path = os.path.join(output_dir, f"{filename}_{timestamp}.png")
plt.savefig(file_path)
plt.close()
for col in df.columns:
plt.figure(figsize=(10, 6))
df[col].dropna().hist(bins=30)
plt.title(f"{col} - 数据分布图")
plt.ylabel("频率")
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_name = f"{col}_数据分布图_{timestamp}.png"
file_path = os.path.join(output_dir, file_name)
plt.savefig(file_path)
plt.close()
if 'car_brand' in df.columns and 'price' in df.columns:
grouped_data = df.groupby('car_brand')['price'].count()
plt.figure(figsize=(8, 8))
plt.pie(grouped_data, labels=grouped_data.index, autopct='%1.1f%%', startangle=90, colors=plt.cm.Paired.colors)
plt.title("品牌和价格分布饼状图", fontsize=16)
plt.axis('equal')
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_name = f"car_brand_price_distribution_{timestamp}.png"
file_path = os.path.join(output_dir, file_name)
plt.savefig(file_path)
plt.close()
2、Sentosa_DSML社区版实现
首先,进行数据读入,利用文本算子直接对数据进行读取,右侧进行读取属性配置
接着,利用描述算子即可对数据进行统计分析,得到每一列数据的数据分布图、极值、异常值等结果。连接描述算子,右侧设置极值倍数为3,异常值倍数为5。
右击执行,得到数据统计分析结果,可以对数据每一列的数据分布图、存储类型,最大值、最小值、平均值、非空值数量、空值数量、众数、中位数、极值和异常值数量等进行计算并展示,结果如下所示:
描述算子执行结果有助于我们对于数据的理解和后续分析。
(二) 数据处理
1、python代码实现
进行数据处理操作
def handle_power(power, threshold=600, fill_value=600):
return fill_value if power > threshold else power
def handle_not_repaired_damage(damage, missing_value='-', fill_value=0.0):
return fill_value if damage == missing_value else damage
def extract_date_parts(date, part):
if part == 'year':
return str(date)[:4]
elif part == 'month':
return str(date)[4:6]
elif part == 'day':
return str(date)[6:8]
def fix_invalid_month(month, invalid_value='00', default='01'):
return default if month == invalid_value else month
columns_to_fill_with_mode = ['model', 'bodyType', 'fuelType', 'gearbox']
for col in columns_to_fill_with_mode:
mode_value = df[col].mode().iloc[0]
df[col].fillna(mode_value, inplace=True)
df = (
df.fillna({
'model': df['model'].mode()[0],
'bodyType': df['bodyType'].mode()[0],
'fuelType': df['fuelType'].mode()[0],
'gearbox': df['gearbox'].mode()[0]
})
.assign(power=lambda x: x['power'].apply(handle_power).fillna(600))
.assign(notRepairedDamage=lambda x: x['notRepairedDamage'].apply(handle_not_repaired_damage).astype(float))
.assign(
regDate_year=lambda x: x['regDate'].apply(lambda y: str(extract_date_parts(y, 'year'))),
regDate_month=lambda x: x['regDate'].apply(lambda y: str(extract_date_parts(y, 'month'))).apply(
fix_invalid_month),
regDate_day=lambda x: x['regDate'].apply(lambda y: str(extract_date_parts(y, 'day')))
)
.assign(
regDate=lambda x: pd.to_datetime(x['regDate_year'] + x['regDate_month'] + x['regDate_day'],
format='%Y%m%d', errors='coerce'),
creatDate=lambda x: pd.to_datetime(x['creatDate'].astype(str), format='%Y%m%d', errors='coerce')
)
.assign(
car_day=lambda x: (x['creatDate'] - x['regDate']).dt.days,
car_year=lambda x: (x['car_day'] / 365).round(2)
)
.assign(log1p_price=lambda x: np.log1p(x['price']))
)
print(df.head())
>> SaleID name regDate model ... regDate_day car_day car_year log1p_price
0 0 736 2004-04-02 30.0 ... 02 4385 12.01 7.523481
1 1 2262 2003-03-01 40.0 ... 01 4757 13.03 8.188967
2 2 14874 2004-04-03 115.0 ... 03 4382 12.01 8.736007
3 3 71865 1996-09-08 109.0 ... 08 7125 19.52 7.783641
4 4 111080 2012-01-03 110.0 ... 03 1531 4.19 8.556606
print(df.dtypes)
>>SaleID int64
name int64
regDate datetime64[ns]
model float64
brand int64
bodyType float64
fuelType float64
gearbox float64
power int64
kilometer float64
notRepairedDamage float64
regionCode int64
seller int64
offerType int64
creatDate datetime64[ns]
price int64
v_0 float64
v_1 float64
...
2、Sentosa_DSML社区版实现
通过描述算子的执行结果可以观察到,“model”,“bodyType”,“fuelType”,“gearbox”,“power”,"notRepairedDamage"列需要进行缺失值和异常值处理,首先,连接异常值缺失值填充算子,点击配置列选择,选择需要进行异常值缺失值处理的列。
然后,对配置列异常值缺失值填充方式进行选择。“model”,“bodyType”,“fuelType”,"gearbox"列选择保留异常值,利用众数填充缺失值,
"power"列选择输入规则处理异常值,指定异常值的检测规则为‘power
>600’,选择按照缺失值方法进行填充,使用固定值600对缺失值进行填充。
"notRepairedDamage"列选择输入规则处理异常值,指定异常值的检测规则为notRepairedDamage
== ‘-’,选择按照缺失值方法进行填充,使用固定值0.0对缺失值进行填充。
然后,利用生成列算子分别提取年,月和日信息,并生成对应的列,生成年,月和日列的表达式分别为:substr(regDate
,1,4)、substr(regDate
,5,2)、substr(regDate
,7,2)。
生成列算子处理完成后的结果如下所示:
为了处理无效或缺失的月份信息,利用填充算子对月份列regDate_月
进行处理,填充条件为regDate_月 == '00'
,使用填充值 ‘01’对 regDate
列进行填充。
对于有效的regDate列数据,利用concat(regDate_年, regDate_月, regDate_日) 来填充regDate列。通过从有效的年、月、日列填充一个新的regDate,可以修复原始数据中某些部分不完整或异常的情况。
通过格式算子将‘regDate’和‘creatData’列修改为String类型(Intege不能直接修改为Date类型),将‘notRepairedDamage’列修改为Double类型。
将‘regDate’和‘creatData’列修改为Date类型(格式:yyyyMMdd)。
格式修改完成后,利用生成列算子。
1、生成’car day’ 列,表达式为DATEDIFF(creatDate
, regDate
),计算汽车注册时间 (regDate) 与上线时间 (creatDate) 之间的日期差。
2、生成’car year’列表示汽车使用的年数。表达式为DATEDIFF(creatDate
, regDate
) / 365,计算使用的年数。
3、生成’log1p_price’列,表达式为log1p(price
),通过计算 price 的自然对数,避免价格为 0 时的错误。
生成列执行结果如下所示:
这些步骤为后续的建模和分析奠定了坚实的数据基础,减少了数据异常的影响,增强了模型对数据的理解能力。
(三) 特征选择与相关性分析
1、python代码实现
直方图和皮尔森相关性系数计算
def plot_log1p_price_distribution(df, column='log1p_price', bins=20,output_dir=None):
"""
绘制指定列的分布直方图及正态分布曲线
参数:
df: pd.DataFrame - 输入数据框
column: str - 要绘制的列名
bins: int - 直方图的组数
output_dir: str - 保存图片的路径
"""
sns.set(style="whitegrid")
plt.figure(figsize=(10, 6))
sns.histplot(df[column], bins=bins, kde=True, stat='density',
color='orange', edgecolor='black', alpha=0.6)
mean = df[column].mean()
std_dev = df[column].std()
x = np.linspace(df[column].min(), df[column].max(), 100)
p = np.exp(-0.5 * ((x - mean) / std_dev) ** 2) / (std_dev * np.sqrt(2 * np.pi))
plt.plot(x, p,