1. 程式人生 > >R的爬蟲和迴歸模型案例-以北京自如房租價格為例

R的爬蟲和迴歸模型案例-以北京自如房租價格為例

一、背景

爬蟲向來不是R的專長,但並不代表R在此方面一事無成。正好在學習R的rvest爬蟲包,不如邊學邊做,自己來做一個案例。
作為一名消費者,自如不錯的管理服務和靠譜的房源使得在帝都想省事兒的我們,即使花高於周邊其他競爭者的價格,也願意租住自如。不過,只要是住過自如一兩年或看過自如幾次房子的小夥伴應該會注意到,自如的房價與我們實際所看到房間的自己預估價格,並不是都契合的,總是會存在被高估和被低估的房子。那麼,作為一名消費者,在與商家的博弈中,消費者手上有更為透明的資訊無疑會有更強的議價能力。
所以,本案例的目的在於,通過分析現有房租價格和房間資訊之間的關係,建立基於這些資訊的定價體系迴歸模型。

二、資料收集

自如作為一個現在最大的房屋租賃中介,已經在其官網上放置了海量的資料,所以,本案例分析的所有資料自然全部來自北京自如官網。
在開始介紹爬蟲之前,先就自如官網的資料資訊的幾個特點說明一下:

  • a、該網站資訊是處於實時更新狀態,本次爬蟲獲得的資料是2017年1月5日14:26獲得的;
  • b、該網站的租房資訊也不是那一時刻自如所有的資訊,自如有還多待租但還沒有上網的房源(是的,我的根據是每次找房時候自如管家總會神祕的對我說,『除了網上的,我們還有幾個沒上網的房間,要不看一下』);
  • c、本次分析的目標資訊是自如合租月付這類房子,整租、日付、自如寓等以後有精力再說;
  • d、本次分析的資料只包括北京城六區(東城、西城、朝陽、海淀、豐臺、石景山)外加昌平的自如合租月付資料,一方面是個人找房方位的喜好,一方面是其網頁設定的原因,待會兒會提到。

1、首先是找資料來源。
這裡寫圖片描述

2、其次是不斷觀察總結各個資料節點,找到目標資料所在節點。
這裡寫圖片描述
3、再次是對照包的引數,形成相應的爬蟲程式碼。

library(rvest)
library(stringr)
library(XML)
library(xml2)

WebSpider <- function(m){

  url <- str_c(cp,"?p=",m)

  web <- read_html(url,encoding = "UTF-8")#抓取網頁資訊

  name_rough <- web %>% html_nodes("h3")  %>%  html_text() #獲取粗房屋名
area_rough <- web %>% html_nodes("h4") %>% html_text() #提取區位 price_rough <- web %>% html_nodes("p.price") %>% html_text() #提取價格 price <- str_extract(price_rough, "[0-9]+") %>% as.numeric()#提取精確價格 detail <- web %>% html_nodes("div.detail") %>% html_text() #提取其他資訊 #合併成資料框 data.frame(name_rough,area_rough,forward,mate_num,location,price,detail) }

不過,這就遇到了一個問題,在提取『detail』一塊時,無法再往下有區分的抓取了,因此造成最後抓到的資料是這樣的:
這裡寫圖片描述
只能在Excel裡操作整理完成了。
4、接著是觀察翻頁規律,然後遇到了一個坑。原以為之後的頁碼不過是http://www.ziroom.com/z/nl/z3.html的基礎上加上/1、/2……..,我依照這個思路抓了一番,也獲得了將近4000多條資料,原以為這大概就是全部吧。但我仔細看資料才發現,這樣下來的基本都是房山、大興和通州等的資料,基本沒有城六區的,城六區的只有選了區域選項後才會出現:

dc <- "http://www.ziroom.com/z/nl/z3-d23008614.html"
xc <- "http://www.ziroom.com/z/nl/z3-d23008626.html"
cy <- "http://www.ziroom.com/z/nl/z3-d23008613.html"
hd <- "http://www.ziroom.com/z/nl/z3-d23008618.html"
ft <- "http://www.ziroom.com/z/nl/z3-d23008617.html"
sjs <- "http://www.ziroom.com/z/nl/z3-d23008623.html"
cp <- "http://www.ziroom.com/z/nl/z3-d23008611.html"

這樣一來,只有逐區的來進行翻頁爬了。為此,只能選定部分割槽域來做分析了。

results_cp <- data.frame()
for(m in 1:118){ #118為昌平區資訊的總頁碼 
  results_cp <- rbind(results_cp,WebSpider(m))#合併單個區每一次迴圈輸出的資料
}
#依次重複獲得7個區的資料
results <- rbind(results_cp, results_cy,results_dc,results_ft,
                 results_hd,results_sjs,results_xc) #將所有各區資料的合併

最後,因為#3中遇到的問題,不得不在excel裡先進行一輪資料清洗和整理,大致就是篩選、分列和去重的工作,得到這樣的資料格式,最終獲得12250條資料(去重前14060)並匯入R。
這裡寫圖片描述

三、資料清洗和考察

在在excel裡清理過一次後,還是在R裡圍繞分析目標做一些資料整理。

library(readxl)
library(stringr)
library(dplyr)
library(psych)
library(ggplot2)
library(nortest)
library(gridExtra)
library(mice)
library(VIM)
library(corrplot)
library(DMwR)
library(car)

zr_data <- read_excel("~/Desktop/Über R/Excesise/ziroom-excel-V4.xlsx")

zr_data$method_of_pay <- str_replace(zr_data$method_of_pay, "[M]", "1")
zr_data$method_of_pay <- str_replace(zr_data$method_of_pay, "[D]", "2")%>% as.numeric()
zr_mp <- subset(zr_data, method_of_pay == "1")#subset the data of pay monthly
zr_dp <- zr_data[zr_data$method_of_pay == "2", ]#subset the data of pay dayly
zr_mp_c <- filter(zr_mp, method_of_rent == "合")#subset the data of cotenant from monthly paying

最終篩選獲得符合本次目標的資料集,自如合租月付(而非日付)的9712條資料:zr_mp_c。通過summary大致瞭解資料後,為資料集裡的資料做一個歸類。

#group each variable by the data types
norminal_data <- list(zr_mp_c$forward, zr_mp_c$district, zr_mp_c$area, zr_mp_c$layout)
ordinal_data <- list(zr_mp_c$stock)
ratio_data <- list(zr_mp_c$price, zr_mp_c$area_space, zr_mp_c$distance_from_ss, zr_mp_c$stock)

單變數探索

正態性檢驗

首先是房租價格、房屋面積、地鐵距離遠近和所在樓層數幾個連續性變數的正態性檢驗。

h1 <- ggplot(zr_mp_c, aes(x = price, y = ..density..)) + 
  geom_histogram(binwidth = 100) + 
  geom_density()+
  labs(title = "房租價格")+
  theme(text = element_text(family = "STSong"))
h2 <- ggplot(zr_mp_c, aes(x = area_space, y = ..density..)) + 
  geom_histogram(binwidth = 1) + 
  geom_density()+
  labs(title = "房屋面積")+
  theme(text = element_text(family = "STSong")) 
h3 <- ggplot(zr_mp_c, aes(x = distance_from_ss, y = ..density..)) + 
  geom_histogram(binwidth = 50) + 
  geom_density()+
  labs(title = "距離地鐵遠近")+
  theme(text = element_text(family = "STSong"))
h4 <- ggplot(zr_mp_c, aes(x = stock, y = ..density..)) + 
  geom_histogram(binwidth = 1) + 
  geom_density()+
  labs(title = "樓層分佈")+
  theme(text = element_text(family = "STSong"))

grid.arrange(h1, h2, h3, h4, nrow = 2)

這裡寫圖片描述

以上圖片大致可見,房租價格和房屋面積雖然不是標準的正態分佈,但符合正態性的特徵可大致確定,距離地鐵遠近和樓層分佈就不太好確定。
接下來是用函式來做更為確切的檢驗。我之前一般會用shapiro和ks.test來檢測,不過這次做這個案例也學到了一個點:ks.test可用於大樣本但要求樣本中不能出現相同值;而shapiro檢驗又不適合大樣本的檢驗(3~5000之間)。為此,我找到了nortest包的lillie.test,其是ks.test的修正。
程式碼和結果如下:

sapply(ratio_data, lillie.test)

> sapply(ratio_data, lillie.test)
          [price]                                            
statistic 0.05587243                                      
p.value   2.151243e-80                                    
method    "Lilliefors (Kolmogorov-Smirnov) normality test"                                    
          [area_space]                                            
statistic 0.1050441                                       
p.value   8.539925e-294                                   
method    "Lilliefors (Kolmogorov-Smirnov) normality test"                                      
          [distance_from_ss]                                            
statistic 0.09777697                                      
p.value   3.632065e-238                                   
method    "Lilliefors (Kolmogorov-Smirnov) normality test"  

lillie.test(zr_mp_c$stock)

> lillie.test(zr_mp_c$stock)

    Lilliefors (Kolmogorov-Smirnov) normality test
        D = 0.19381, p-value < 2.2e-16                     

結合函式的正態性檢驗,基本可以確定四個變數都是符合正態分佈的。不過結合觀察這幾個結果可以看到,距離地鐵遠近和樓層分佈並無異常。房租價格和房屋面積在右側有明顯長尾,說明可能有異常資料。
調出資料來看,這幾個不管是價格或面積高得異常的資料,回到原網站檢視,卻是存在,排除整理分析過程錯誤。至於是不是資料錄入錯誤,根據對於北京區位的瞭解,也覺得可能性很小。最後作為可信資料留下了。

這裡寫圖片描述

缺失值考察

接著是缺失值的考察。在開始之前,先需要挑選最終進入模型構建的變數,並對其中的離散型變數進行因子化處理。

 #construct as the final data frame
zr_mp_c_a <- subset(zr_mp_c, select = c("price", "area_space", "distance_from_ss", "stock", "forward", "district", "area", "layout"))
#form the new stock variable
table(stock2 <- cut(zr_mp_c_a$stock,breaks = c(1,6,12,20,33), 
                    labels = c("low","low midlle","high middle",
                    "high")))

        low  low midlle high middle        high 
       4697        2136        1508         630 
zr_mp_c_a$stock2 <- stock2

#transform the factor variables
zr_mp_c_a$forward <- factor(zr_mp_c_a$forward)
zr_mp_c_a$district <- factor(zr_mp_c_a$district)
zr_mp_c_a$area <- factor(zr_mp_c_a$area)
zr_mp_c_a$layout  <- factor(zr_mp_c_a$layout)

主要變數的缺失值狀況:

> sum(is.na(zr_mp_c_a$price))
[1] 0
> sum(is.na(zr_mp_c_a$distance_from_ss))
[1] 598
> sum(is.na(zr_mp_c_a$stock))
[1] 6
> sum(is.na(zr_mp_c_a$stock2))
[1] 6
> sum(is.na(zr_mp_c_a$area_space))
[1] 13
> sum(is.na(zr_mp_c_a$forward))
[1] 5
> sum(is.na(zr_mp_c_a$district))
[1] 0
> sum(is.na(zr_mp_c_a$area))
[1] 1
> sum(is.na(zr_mp_c_a$layout))
[1] 0

可以看到在諸多指標中,距離地鐵遠近這個指標的缺失值最多,佔總值的6%左右,其餘的缺失值都很少。接著可以藉助VIM包的matrixplot看一下缺失值的分佈狀況。

matrixplot(zr_mp_c_a)

這裡寫圖片描述

通過分佈可以看到,距離地鐵遠近這個指標的缺失值分佈較為集中,因為資料整體是以區為單位排布的,基本可以確定是某幾個區(大致是昌平和海淀吧)在這個指標的資訊錄入上有問題。這也就意味著有必要處理缺失值,不然會影響到特定指標的特徵。
本次原本打算用knnImputation方便快捷地來插補的,不過一直報invalid “time” arguments的錯誤,遂繼續嘗試用更為智慧的mice。其大致原理是

mice包提供了多種先進的缺失值處理方法。它使用一種不同尋常的方法來進行兩步插值:首先利用mice函式建模再用complete函式生成完整資料。mice(df)會返回df的多個完整副本,每個副本都對缺失的資料插補了不同的值。complete()函式則會返回這些資料集中的一個(預設)或多個。

micemod <- mice(zr_mp_c_a[1:4]))
zr_mp_c_a[1:4] <- complete(micemod)

在完成插補後,也就獲得了一個較為完全的資料集。

變數間關係的探索

關於變數間相關性的探索,可以使用psych包的corr.test一次性的完成相關程度和顯著性的檢驗。

corr.test(zr_mp_c_a[ ,1:4])

這裡寫圖片描述

可以看到,幾個連續性變數之間除了樓層數量未通過顯著性檢驗完外,其他三個都具有顯著性。同時,各變數間的相關程度也不算高,沒有明顯的變數共線情況。

四、模型建構

模型對比

準備工作做的差不多了,就直接開始上模型。

fit1 <-  lm(price ~ ., data = zr_mp_c_a)
#compare the influence of "area" and "districk"
fit2 <-  lm(price ~ area_space + distance_from_ss + forward + stock + area + layout, data = zr_mp_c_a)
fit3 <-  lm(price ~ area_space + distance_from_ss + forward + stock +
              district + layout, data = zr_mp_c_a)
#drop "stock" and "area"
fit4 <-  lm(price ~ area_space + distance_from_ss + forward + district+ layout, data = zr_mp_c_a)

fit1放入了所有預測變數,雖然獲得0.7086的R方值,但「area」變數龐雜的變數類別使得模型散失了可解讀性,所以在fit2和fit3中,對比了這兩個變數剔除後的變化。此外,在雙變數對比時發現「stock」與「price」的相關性沒有顯著性,在fit4裡將其剔除。最終,fit4的模型特徵如下:
這裡寫圖片描述

模型評估

繪製fit4 的診斷圖

opar <- par(no.readonly = TRUE)
par(mfrow = c(2,2))
plot(fit4)
par(opar)

這裡寫圖片描述
對照模型擬合情況診斷圖,逐一來看:

  • a、首先是正態性(右上圖)大部分都落到了直線上, 可大致認為分佈通過檢驗;
  • b、獨立性:診斷圖無法獲得,那就藉助car包裡的durbinWatsonTest函式來判斷
   > durbinWatsonTest(fit4)  lag Autocorrelation D-W Statistic p-value    1   0.07321289996    1.85339171       0  Alternative hypothesis: rho!= 0 

p值不顯著說明無自相關性,獨立性可獲得認可;

  • c、線性:(左上圖)的基本算是一條直線,可算是直線關係,不必做進一步的處理。
  • d、位置尺度圖:(左下圖)存在較多離散的點,可以考慮對響應變數進行對數處理。
fit4 <- lm(log(price) ~ area_space + distance_from_ss + forward + district+
               layout, data = zr_mp_c_a)

再次對比診斷圖:
這裡寫圖片描述
響應變數的離散情況有所改善。

診斷多重共線情況

> vif(fit4)
                        GVIF Df GVIF^(1/(2*Df))
area_space       1.136749561  1     1.066184581
distance_from_ss 1.126070012  1     1.061164461
forward          1.198440060  9     1.010107448
district         1.259109540  6     1.019385909
layout           1.172449041 17     1.004690222
> sqrt(vif(fit4)) > 2
                  GVIF    Df GVIF^(1/(2*Df))
area_space       FALSE FALSE           FALSE
distance_from_ss FALSE FALSE           FALSE
forward          FALSE  TRUE           FALSE
district         FALSE  TRUE           FALSE
layout           FALSE  TRUE           FALSE

總體來說幾個預測變數的方差膨脹因子都 <2,說明不存在多重共線情況。