使用k-NN预测房价

427 阅读8分钟

使用k-NN预测房价

2022年4月29日 数据 编码 机器学习 python

分享到。

目录

绪论

本文的目的是展示如何轻松地使用Scikit Learn(一个流行的Python机器学习库)提供的k-NN回归器和分类器来预测英国的房价,并基于其位置。这是一个直观的、教学性很强的问题,公共数据集很容易获得。

作为奖励,我们还包括一个分类用例,在这个用例中,我们试图根据房子的位置和价格来预测房子的类型(Flat, Terraced, etc.)。

进口

底层库应该首先使用pip3 install <library> 来安装。

 1import pandas as pd
 2import numpy as np
 3import matplotlib
 4import matplotlib.colors as colors
 5import matplotlib.pyplot as plt
 6
 7from sklearn.model_selection import train_test_split
 8from sklearn.neighbors import KNeighborsClassifier
 9from sklearn.neighbors import KNeighborsRegressor
10from sklearn.model_selection import GridSearchCV

数据集

土地注册处的 "售出 "价格 2021年

我们的主要数据集是土地注册处2021年的 "已售 "数据集,其中包含该年的房屋交易,包括每个房产的地址、类型和价格。

我们只对价格、邮编、房产类型(D-独立式、S-半独立式、F-公寓、T-平房、O-其他)以及是否为新建房产(Y/N)感兴趣。我们对逐月和逐年的趋势都不感兴趣。

1house_prices_df = pd.read_csv("pp-2021.csv",
2                              usecols=[1,3,4,5],
3                              names=["price","postcode","type","new"])
4house_prices_df.head()
价格邮政编码类型新建
0246000NN5 6SQSN
1238000NN4 6LSTN
2320000NN29 7SFDN
3560000NN11 3TDSN
4265000NN5 6AHSN

邮政编码到经度和纬度

下一个数据集是邮编与地理坐标的映射,其形式为(邮编,纬度,经度)图元。

1postcodes_df = pd.read_csv("ukpostcodes.csv",usecols=[1,2,3])
2postcodes_df.head()
邮政编码纬度经度
0AB10 1XG57.144165-2.114848
1AB10 6RN57.137880-2.121487
2AB10 7JB57.124274-2.127190
3AB11 5QN57.142701-2.093295
4AB11 6UL57.137547-2.112233

我们最后的数据框架,简单地命名为df ,与每个交易的邮编和经纬度坐标相匹配,并将价格除以1000,这样我们就可以用'ks'(千)来推理,这就更容易。

1df = house_prices_df.merge(postcodes_df,how='inner',left_on='postcode',right_on='postcode')
2df['price'] = df['price'] / 1000
3df.head()
价格邮政编码类型纬度经度
0246.0NN5 6SQSN52.253869-0.946142
1215.0NN5 6SQSN52.253869-0.946142
2238.0NN4 6LSTN52.198000-0.880000
3311.0NN4 6LSTN52.198000-0.880000
4305.0NN4 6LSDN52.198000-0.880000
1df['price'].describe()
count    757488.000000
mean        379.990774
std        1972.340933
min           0.001000
25%         171.000000
50%         270.000000
75%         420.000000
max      932540.400000
Name: price, dtype: float64

视觉化

对我们的数据集有一个直观的认识是很有帮助的,我们将其可视化为一个热图,这证明了,正如预期的那样,大伦敦和曼彻斯特等地区的房价相对于英格兰其他地区要高。为了减少噪音,我们使用了一个色标来突出20万到 10万的价格,这样我们就不会因为廉价或昂贵的房子(分别低于20万和高于10万的房子)而导致可视化的扭曲。

 1def visualise(df, vmin, vmax):
 2    x = df['longitude']
 3    y = df['latitude']
 4    c = df['price'] 
 5
 6    plt.rcParams['figure.figsize'] = [5, 6]
 7    plt.rcParams['figure.dpi'] = 100 
 8
 9    plt.scatter(x, y, s=0.01, c=c.sort_values(), cmap='plasma_r', 
10                norm=colors.Normalize(vmin=vmin,vmax=vmax), alpha=0.8)
11    plt.colorbar()
12    plt.show()
13    
14visualise(df, 200, 1000)

按地点预测房价

第一个目标是创建一个函数,如下所示。

位置 -> 价格

在现实世界中(例如,Rightmove和Zoopla价格估算器的工作方式),我们将包括房屋号码、房间数量等,再加上 "趋势 "方面的考虑,进行计算。

因此,我们基本上是把整个房屋地址归结为它的地理位置,但不仅仅是这样。房子不仅仅是'房子',它们是公寓、独立屋等等。因此,我们将进一步减少我们的搜索空间,只专注于联排别墅。因此,为了简单起见,假设房价预测只适用于排屋。

1# Terraced houses only (i.e., Townhouses in the US)
2prices_df = df[(df['type'] == 'T')]
3# Obtain the average price paid in each postcode
4prices_df = prices_df.groupby(["postcode","latitude","longitude"], as_index=False)["price"].mean()
5prices_df.head()
邮政编码纬度经度价格
0AL1 1AS51.749072-0.335471608.75
1辽宁沈阳51.747109-0.336542505.00
2辽宁省51.749497-0.336918720.00
3辽宁沈阳51.749318-0.339532775.00
4AL1 1PA51.748662-0.334472785.00

使用k-NN

我们的第一个模型是k-NNN(k-NN)。特别是来自SciKit-Learn的KNeighborsRegressor ,鉴于价格是连续的值。

让我们先把数据集分成训练集和测试集。

1X = prices_df[['latitude','longitude']]
2y = prices_df['price']
3
4X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

k-NN并不是放手不管的。我们需要假设什么是最理想的邻居数量(超参数k),以及每个邻居的距离是否应该均匀,换句话说,超参数的权重。一种方法是用蛮力,也就是说,通过尝试不同的值来找出答案,如下所示。

 1uniform  = []
 2distance = []
 3r = range (1,21,2)
 4
 5for k in r:
 6    
 7    # Euclidan, 'straight' distance
 8    model = KNeighborsRegressor(n_neighbors = k, weights='uniform')
 9    model.fit(X_train.values, y_train.values)
10    uniform.append(model.score(X_test.values,y_test.values))
11
12    # Distance is inversely proportional (to lessen the weight of outliers)
13    model = KNeighborsRegressor(n_neighbors = k, weights='distance') 
14    model.fit(X_train.values, y_train.values)
15    distance.append(model.score(X_test.values,y_test.values))
16
17uniform = np.array(uniform)
18distance = np.array(distance)
19
20plt.rcParams['figure.figsize'] = [10, 3]
21plt.rcParams['figure.dpi'] = 100 # 200 e.g. is really fine, but slower
22plt.plot(r,uniform,label='uniform',color='blue')
23plt.plot(r,distance,label='distance',color='red')
24plt.legend()
25plt.gca().set_xticks(r)
26plt.show()

结果表明,使用超参数k = 15 ,和weight='distance' 将提供最大的准确性。

1pd.DataFrame({"k" : r, "uniform" : uniform, "distance" : distance})
k均匀距离
010.5574520.557452
130.6089000.616880
250.6267240.630256
370.6559160.651094
490.6568270.656976
5110.6611210.663031
6130.6571530.663963
7150.6603010.667635
8170.6580130.667308
9190.6542040.666773

不过,这个过程很繁琐,而且对训练数据集做了严格的假设。进一步清洗训练数据集的方法是通过一个叫做交叉验证的过程。GridSearchCV 函数结合了定义分割的数量和通过各种超参数进行迭代的能力。

在我们的例子中,我们没有定义上面看到的迂回循环,而是简单地指定我们想要尝试的数值范围。

1params = {'n_neighbors':range(1,21,2),'weights':['uniform','distance']}

下一步是将k-NN实例、我们的参数(param)束和分割数(cv=5)传递给GridSearchCV

1model = GridSearchCV(KNeighborsRegressor(), params, cv=5)
2model.fit(X_train.values,y_train.values)
3model.best_params_
{'n_neighbors': 19, 'weights': 'uniform'}

以上,GridSearchCV ,建议weight = uniform ,就像我们的蛮力过程一样,但k = 19 ,而不是k = 15 。这是因为交叉验证过程做了许多不同轮的采样,不像我们的刚性循环。

1model.score(X_test.values,y_test.values)
0.6542038862362233

我们现在定义我们的位置->价格函数如下。

1def price(description,lat,lon):
2    features = [[lat,lon]]
3    print("{:30s} -> {:5.0f}k ".format(description,float(model.predict(features))))
4
5# Examples
6price('Oxford Circus, London', 51.515276, -0.142038)
7price('Harrods (B. Road), London', 51.499814, -0.163366)
8price('Peak District, National Park', 53.328508, -1.783416)
Oxford Circus, London          ->  5531k 
Harrods (B. Road), London      ->  3956k 
Peak District, National Park   ->   270k 

由位置和价格决定的房屋类型

在上一节中,我们观察了使用k-NN回归器来预测房价的情况。现在让我们使用相同的数据集来处理一个分类问题。我们的目标是根据位置和价格预测房屋类型(独立式、半独立式、平房式等),具体如下。

(位置,价格)->类型

让我们首先创建一个数据集,在这个数据集中,我们可以得到给定位置的每种房屋类型的平均价格。

1
2types_df = df.groupby(["latitude","longitude","type"], as_index=False)["price"].mean()
3types_df.head()
纬度经度类型价格
049.912272-6.300022T625.0
149.913984-6.309107S475.0
249.914130-6.312967O440.0
349.914392-6.315107F280.0
449.914397-6.311555F255.0

接下来,我们将发现最理想的邻居数和权重,使用GridSearchCV

 1params = {'n_neighbors':range(1,21,2),'weights':['uniform','distance']}
 2
 3X = types_df[['latitude','longitude','price']]
 4y = types_df['type']
 5
 6X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 0)
 7
 8model = GridSearchCV(KNeighborsClassifier(), params, cv=5)
 9model.fit(X_train.values,y_train.values)
10model.best_params_
{'n_neighbors': 19, 'weights': 'distance'}

得分没有价格预测那么好,但仍有50%的得分,对于第一次尝试来说,这还算不错,没有以任何方式对数据集进行处理。

1model.score(X_test.values,y_test.values)
0.5336038203296112

最后,我们声明一个函数,可以看到预测的实际效果。

1ht = { 'F' : 'Flat', 'T' : 'Terraced', 'S' : 'Semi', 'D' : 'Detached', 'O' : 'Other'}
2
3def house_type(description,lat,lon,price):
4    features = [[lat,lon,price]]
5    print("{:30s} {:5.0f}k -> {}".format(description,price,ht[(model.predict(features)[0])]))
6
7house_type('Oxford Circus, London', 51.515276, -0.142038, 500)
8house_type('Harrods (B. Road), London', 51.499814, -0.163366, 5500)
9house_type('Peak District, National Park', 53.328508, -1.783416, 100)
Oxford Circus, London            500k -> Flat
Harrods (B. Road), London       5500k -> Other
Peak District, National Park     100k -> Terraced

结论

Scikit Learn在预测连续值和类别方面做得很好,分别使用了KNeighborsRegressor() ,和KNeighborsClassifier() ,对于一个关键特征是地理坐标的用例,其中,"距离 "的概念是非常直观的。

使用所有可用的土地注册处的记录,(而不是只有2021年的数据集)将有助于预测 "未来的价格",以及分析新的有趣的模式。例如,公寓和排屋往往在连续的房屋号码方面形成集群。基于门牌号和位置(不含价格)可以实现对房产类型的有效预测,而不一定要有特定门牌号/邮编对的 "售出 "记录。

在你离开之前

🤘 订阅我的100%无垃圾邮件的时事通讯!

电子邮件。