0 了解数据
来源:本数据来自国内某房地产网络平台。我们使用了 requests 和 parsel 库来获取西安碑林区租房房源数据,并利用百度地图 API 来获取每个房源的经纬度。
数据集信息:
时间节点:2022.2
去掉重复后数据条数:2117条
我们将使用租房数据中的'type', 'layout', 'bc', 'distance', 'rent_area', 'rent_price'字段建立多元线性回归模型,使其可以根据租房类型、房屋布局、所在区域、距离最近地铁站距离和出租面积来预测出租价格。
1 导入数据
使用pandas库中的read_csv()方法读取数据文件,并将其保存在名为'df'的DataFrame对象中。在其中选择我们所需要的字段('type', 'layout', 'bc', 'distance', 'rent_area', 'rent_price'),生成新的 DataFrame 。
# 1 从csv文件中读取数据集
df = pd.read_csv('xablzufang_without_duplicates.csv',encoding='utf-8-sig')
df = df[['type', 'layout', 'bc', 'distance', 'rent_area', 'rent_price']]
print(df.head())
2 数据预处理
数据预处理需要根据各个数据的具体情况来制定不同的处理方案。我们首先要查看各字段的具体情况,然后判断需要进行哪些处理。常见的数据预处理操作包括缺失值、异常值和重复值的处理,以及分类数据编码处理,数值数据规范化和标准化,还有处理不平衡的数据集。
2.1 对type字段的处理
针对字段type取值中包含的三种情况(整租、合租和未知),我们需要进行数据清洗和转化处理。因为未知数据对于后续的数据分析和建模是没有意义的,所以我们选择将其删除。对于整租和合租这两种取值,我们可以将它们分别转化为二元变量,即用1表示整租,用0表示合租。这样可以方便地在后续的分析和建模过程中使用,并且有助于提高算法的准确性和可解释性。
#2.1 对type的处理
df = df[df['type'] != '未知']
df['type']=df['type'].map({'整租':1,'合租':0})
pd.set_option('display.max_columns', 50) # 显示完整的列
print(df['type'].value_counts())
2.2 对layout字段的处理
layout字段中存在多个出现频次仅为1次的取值,如下图所示,这些可能会导致模型预测能力下降,因为模型没有足够的数据进行学习。我们将低频率的取值合并为一个“其他”类别,这样可以减少特征的维度,并将低频率的取值统一为一个类别,提高模型的预测能力。
#2.2 对layout的处理
df=df[df['layout']!='未知室0厅']
# 统计每个取值出现的频次
counts = df['layout'].value_counts()
# 获取出现频次低于50的取值列表
low_frequency = counts[counts < 50].index.tolist()
# 将出现频次低于50的取值合并为“其他”类别
df['layout'] = df['layout'].apply(lambda x: '其他' if x in low_frequency else x)
合并后,layout各取值统计如下:
2.3 对分类型数据进行哑编码和对数值型数据进行规范化
分类型数据是指具有离散取值的数据,例如地区。为了在机器学习算法中应用这些分类数据,需要将其转换为数值型数据。通常使用哑编码的方法进行处理,哑编码将一个有k个不同取值的分类变量转换成k个新变量,每个变量代表原来的分类变量中的一种取值。对于原始数据中的每个分类变量取值,哑编码后生成一个新的二元变量,其中1表示该取值存在,0表示该取值不存在。
数值型数据是指具有连续取值的数据,例如出租面积和出租价格。由于不同的数值型数据的单位、量纲和量级可能不同,因此在对它们进行机器学习建模时,需要将其进行规范化处理。最常用的规范化方法是最小-最大规范化,它将数据缩放到 [0,1] 的区间内。这种规范化方法将数据映射到一个固定的区间内,使得不同的数值型数据之间具有可比性,从而方便在后续的数据分析和建模过程中使用。
哑编码和规范化后的前五行数据:
def dummy_variables(df, columns):
# 哑编码
for col in columns:
# 将 "unknown" 替换为 NaN
df[col] = df[col].replace('unknown', np.nan)
# 进行哑编码
dummies = pd.get_dummies(df[col], prefix=col, dummy_na=True)
# 将原始列删除,并将编码列添加到 DataFrame 中
df = df.drop(col, axis=1)
df = pd.concat([df, dummies], axis=1)
return df
def normalize_columns(df, columns):
# 进行规范化
scaler = MinMaxScaler()
for col in columns:
if df[col].dtype != 'object':
# 从 DataFrame 中提取出该列,并将其转换为二维数组
col_data = df[[col]].values
# 调用 MinMaxScaler 对象的 fit_transform() 方法进行规范化
normalized_data = scaler.fit_transform(col_data)
# 将规范化后的数组转换回一维,并将其添加回 DataFrame 中
df[col] = normalized_data.flatten()
return df
#2.3 对分类数据进行哑编码,对数值型数据进行规范化
categorical_columns = ['layout','bc']
numerical_columns=['rent_area','rent_price']
df=dummy_variables(df,categorical_columns)
df=normalize_columns(df,numerical_columns)
2.4 对distance字段进行处理
在原始数据中,distance字段包含数值和单位(m)。如果附近没有地铁站,则使用"None"表示。为了能够对这些数据进行机器学习处理,我们需要先将单位去掉,转换为数值,并对"None"进行处理。
# 2.4 对distance的处理
#删除非None的数据中的单位“m”
df.loc[df['distance'] != 'None', 'distance'] = df.loc[df['distance'] != 'None', 'distance'].str[:-1].astype(float)
在对"None"进行处理的过程中,为了选择效果最好的处理方法,我们采用了三种方法:特殊值填充、平均值填充和利用随机森林预测缺失值。然后,我们根据模型训练结果选择出最好的处理方法。
def impute_missing_col_rf(df, col_name):
# 使用随机森林预测缺失值
# 分为已知和未知两部分
known = df.loc[df[col_name].notnull()]
unknown = df.loc[df[col_name].isnull()]
# 构造训练集和测试集
X_train = known.drop(col_name, axis=1)
y_train = known[col_name]
X_test = unknown.drop(col_name, axis=1)
# 使用随机森林算法进行训练和预测
rf = RandomForestRegressor(random_state=42)
rf.fit(X_train, y_train)
predicted = rf.predict(X_test)
# 将预测结果填充到缺失值中
df.loc[df[col_name].isnull(), col_name] = predicted
return df
def split_and_model(df):
# 分离自变量和因变量
X = df.drop(columns=['rent_price'])
y = df['rent_price']
# 划分数据集为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 创建线性回归模型对象
model = LinearRegression()
# 使用fit()方法进行拟合
model.fit(X_train, y_train)
# 对测试集进行预测
y_pred = model.predict(X_test)
# 计算MAE
mae = mean_absolute_error(y_test, y_pred)
print("MAE:",mae)
#对distance中“None”设置不同的处理方法
df1 = df.copy()
df2 = df.copy()
df3 = df.copy().replace('None', np.nan)
df4 = df.copy().replace('None', np.nan)
df1['distance']=df1["distance"].replace("None", "100000000").astype(float)#填充一个特别大的数
df2['distance']=df2["distance"].replace("None", "0").astype(float)#填充0
df3 = df3.fillna(df3.mean()) #填充平均数
df4=impute_missing_col_rf(df4,'distance') #随机森林算法预测缺失值
for df in [df1,df2,df3,df4]:
split_and_model(df)
经过实验比较,我们发现使用一个特别大的数来填充缺失值最终建立的模型的MAE最小,因此我们选择了该方法对缺失值进行填充。
各种方法建立模型所得MAE:
#选择填充特别大的数据来处理缺失值
df['distance']=df["distance"].replace("None", "100000000").astype(float)
df=normalize_columns(df,['distance'])
3 模型建立
为了获得多元线性回归模型的最优参数,我们将使用交叉验证的方法。我们使用交叉验证,将数据分为5个子集,然后使用MAE作为评估指标,找到最优参数。最后,我们使用最优参数构建线性回归模型,并在测试集上评估模型表现,输出MAE。
# 3 确立最佳参数并建模
# 拆分特征和目标变量
X = df.drop('rent_price', axis=1)
y = df['rent_price']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
# 定义模型
model = LinearRegression()
# 定义参数范围
params = {'fit_intercept': [True, False],
'copy_X': [True, False]}
# 定义评估指标
scoring = 'neg_mean_absolute_error'
# 定义交叉验证对象
grid = GridSearchCV(model, params, cv=5, scoring=scoring)
# 在训练集上进行交叉验证
grid.fit(X_train, y_train)
# 输出最优参数
print("最优参数:",grid.best_params_)
# 使用最优参数构建模型
best_model = LinearRegression(fit_intercept=grid.best_params_['fit_intercept'],
copy_X=grid.best_params_['copy_X'])
# 在测试集上评估模型表现
y_pred = best_model.fit(X_train, y_train).predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
print('基于最优参数建立模型的MAE:', mae)