20250418
不動産情報のスクレイピングと重回帰分析
1, Rの準備
以下ではPosit Cloudを前提に話を進めていく(自分のコンピュータにR Studioがインストールされている学生は、すでに設定等はわかっているはず)
Posit cloudを初めて使う場合には、サインアップが必要
https://gyazo.com/1b85a48f9fb47460174fe5325c04c889
左下のGET STARTEDか右上のSign Upを押す
https://gyazo.com/b5b032902d32f75937ad5deb1d547459
Cloud Freeを選択する
https://gyazo.com/ed17d4caa95d31d922b07db74155b53c
Sign Upを選択
https://gyazo.com/a435dbdbb273c5190f7773b5a83ef296
Sign up with Googleを選択
https://gyazo.com/e448f9bb66850fcedbfe6dcdb5d710fc
大学から提供されている***@g.nihon-u.ac.jpのアドレスは、Gmailなので、大学のアドレスを選択
Gmailの確認画面を経て、ログイン画面が表示される
https://gyazo.com/97c3fa274f195c4159354d98cd86eb15
次へを選択
https://gyazo.com/a83a6a47f28b3ad750ddae27dfe171b3
Posit Cloudが利用可能な状況になった
https://gyazo.com/1a4438e8bca0460e8de349e353c209f3
右上のNew Projectを選択
https://gyazo.com/7afb77617309c19f39399e4daee3fbfb
New RStuio Projectを選択
Deploying Project画面で少し待つ
https://gyazo.com/1cf5449feeeb180f0c55f4d071e0a880
RStudioの画面になった
試しに使ってみる
https://gyazo.com/15064171180bd1a1da39285fa745ee98
左上の+ボタンからR Scriptを選ぶ
https://gyazo.com/681bdbf3ae85083685a4f47e41d7be55
画面の左上に新しい領域が展開された
https://gyazo.com/177dffc3cf38b52b3aae21c62876e98b
ここに
code:R
2+3
と記入してみる
https://gyazo.com/e0ad37b5ae03ea5f90f4538050ee7232
緑の右矢印のボタンを押す( WindowsならCtrl + Enter、Macの場合はCommand + Returnでも実行できる)
https://gyazo.com/386c48133f48d4843ac0ebbe930e3b0c
計算結果が左下のConsole画面に表示される
他の計算、例えば9/3や16^3などをやってみよう
2、Suumoで売買物件のurlを確保
https://gyazo.com/52ce0b979cd5e81d6420940e849e0eb7
賃貸物件を選択
https://gyazo.com/56569f8b342c8053990a3235674ee9c4
神奈川県のエリアを選択
https://gyazo.com/2abd40d67a42af32871d19903219dfca
川崎市川崎区を選ぶ
https://gyazo.com/3a43cd1101cc137c9d5d45a3b99c3090
その上で下にスクロールして条件を追加する
https://gyazo.com/8e17c8b3ecc537d7c86f9cdaafe0fef6
間取りとして、今回は単身者向けということでワンルーム、1K、1DKを選択
駅からの距離も15分以内に限定する(物件数が多すぎると時間がかかるので、これは授業時間内に実施することからの制約)
下の検索するボタンを押す
https://gyazo.com/54b38f72ac40ccd32c65c3d0ab9c6ea6
そうすると4月17日時点で8,600件がヒットした
一軒目について詳細を見るを押す
https://gyazo.com/6a8b3be12d75f5c3ac82fca693625a1f
この条件で、賃料と管理費合計で8.5万円
元の画面に戻って、次の物件を見る
https://gyazo.com/f66ed1f796d602ebb863b3e6a8074fb8
この物件は、合計で9.1万円
こんな確認を手作業で行って、条件をエクセルにまとめていくのは気が遠くなるような作業
そこでスクレイピングを行う
売買物件の検索条件を決めて、最初のページのurlをコピーする
条件は、川崎市川崎区のワンルーム、1K、1DKで徒歩15分以内
https://gyazo.com/54b38f72ac40ccd32c65c3d0ab9c6ea6
urlは
3、Rでスクレイピング
rvest (Easily Harvest (Scrape) Web Pages)がスクレイピングのパッケージ
詳しく知りたい人はReference manual: rvest.pdfを読むと良い
# 川崎市(川崎区)のベースURLのところを先ほどのurlで置き換える
code:R
# パッケージの読み込み
install.packages(c("rvest", "dplyr", "purrr", "stringr", "magrittr", "tidyr", "lubridate")) # ← 追加で tidyr, lubridate も必要
library(rvest)
library(dplyr)
library(purrr)
library(stringr)
library(magrittr)
library(tidyr)
library(lubridate)
# 川崎市(川崎区)のベースURL
# スクレイピング関数(1ページ分)
scrape_suumo_page <- function(url) {
message("アクセス中: ", url)
page <- tryCatch({
read_html(url)
}, error = function(e) {
warning("読み込み失敗: ", url)
return(tibble())
})
listings <- page %>% html_elements(".cassetteitem")
if (length(listings) == 0) {
warning("物件が見つかりませんでした: ", url)
return(tibble())
}
map_dfr(listings, function(listing) {
tibble(
name = listing %>% html_element(".cassetteitem_content-title") %>% html_text(trim = TRUE),
address = listing %>% html_element(".cassetteitem_detail-col1") %>% html_text(trim = TRUE),
access = listing %>% html_element(".cassetteitem_detail-col2") %>% html_text(trim = TRUE),
age = listing %>% html_element(".cassetteitem_detail-col3") %>% html_text(trim = TRUE),
rent = listing %>% html_element(".cassetteitem_price--rent") %>% html_text(trim = TRUE),
management_fee = listing %>% html_element(".cassetteitem_price--administration") %>% html_text(trim = TRUE),
deposit = listing %>% html_element(".cassetteitem_price--deposit") %>% html_text(trim = TRUE),
key_money = listing %>% html_element(".cassetteitem_price--gratuity") %>% html_text(trim = TRUE),
layout = listing %>% html_element(".cassetteitem_madori") %>% html_text(trim = TRUE),
area = listing %>% html_element(".cassetteitem_menseki") %>% html_text(trim = TRUE)
)
})
}
# 最大ページ数を取得してからURLを作る
get_max_page <- function(url) {
page <- read_html(url)
page %>%
html_elements(".pagination-parts a") %>%
html_text(trim = TRUE) %>%
as.integer() %>%
max(na.rm = TRUE)
}
max_page <- get_max_page(base_url)
page_urls <- paste0(base_url, "&page=", 1:max_page)
# 全ページ分まとめて取得
suumo_data <- map_dfr(page_urls, scrape_suumo_page)
# ---------- ここからデータ整形処理を追加 ----------
suumo_data <- suumo_data %>%
mutate(
# 家賃("6.8万円")→ 68000円に変換("-" や "相談" 対応)
rent_value = ifelse(str_detect(rent, "^\\d+(\\.\\d+)?万円$"),
as.numeric(str_remove(rent, "万円")) * 10000,
NA_real_),
# 管理費("2000円", "5000円", "-")→ 数値(円)に変換
management_fee_value = ifelse(str_detect(management_fee, "^\\d+円$"),
as.numeric(str_remove(management_fee, "円")),
0), # "-"や空欄は0円と仮定
# 家賃+管理費の合計
total_cost = rent_value + management_fee_value,
# 面積("25.3m2")を数値に変換
area = as.numeric(str_extract(area, "\\d+(\\.\\d+)?")),
# 徒歩分数("歩3分", "歩10分", …)をすべて抽出して、その中の最小値
walk_from_station = str_extract_all(access, "歩\\d+分"),
walk_from_station = map_int(walk_from_station, function(x) {
mins <- as.integer(str_extract(x, "\\d+"))
if (length(mins) == 0 || all(is.na(mins))) return(NA_integer_)
min(mins, na.rm = TRUE)
}),
# 築年数("築10年" → 10, "新築" → 0)
age_of_building = case_when(
str_detect(age, "新築") ~ 0L,
str_detect(age, "築\\d+年") ~ as.integer(str_extract(age, "\\d+")),
TRUE ~ NA_integer_
)
) %>%
# 駅名と路線名を分離("〇〇線「△△」徒歩x分" に対応)
separate(access, sep = "「", into = c("line", "station"), fill = "right") %>%
separate(station, sep = "」", into = c("station", "drop"), fill = "right") %>%
select(-drop) %>% # ← 一時列を削除
# 不要な元データを削除(rent, management_fee, deposit, key_money)
select(-c(rent, management_fee, deposit, key_money))
# ---------- 整形ここまで ----------
# データ確認
print(head(suumo_data, 5))
print(nrow(suumo_data)) # 件数
# CSVに保存
write.csv(suumo_data, "suumo_kawasaki001.csv", row.names = FALSE, fileEncoding = "UTF-8")
cat("✅ suumo_kawasaki001.csv を保存しました!右下のFilesタブから選択して、歯車マークを押してExport→Downloadできます。\n")
ここで右下のFilesタブから選択して、歯車マークを押してExport→Downloadできます。
4, 簡単なグラフを描くことで傾向をみる
予想される傾向として、
広いと家賃が高い
新しいと家賃が高い
駅から近いと家賃が高い
散布図を描いてみる
部屋の広さと賃料の関係を散布図に書く
code:R
plot(suumo_data$area,suumo_data$total_cost)
https://gyazo.com/cf98f772885d305ae75e18c2538b5e51
zoomのボタンを押すと、図が拡大される
https://gyazo.com/77aa32bd60bd25f27658db599ec50081
確かに面積が広い部屋は賃料も高いという右上がりの関係がある
右上がりの関係を直線で近似した回帰直線を描く
code:R
plot(suumo_data$area,suumo_data$total_cost)
plot(suumo_data$walk_from_station,suumo_data$total_cost)
plot(suumo_data$age_of_building,suumo_data$total_cost)
https://gyazo.com/b6afe370637bdfa964a17758510cc6e2
最寄駅からの時間と賃料の関係についても同様に描いてみよう
駅に近い方が利便性が高く、賃料は高いはず
code:R
plot(suumo_data$walk_from_station,suumo_data$total_cost)
n<-lm(suumo_data$total_cost~suumo_data$walk_from_station)
abline(n)
https://gyazo.com/1c97ae39bc6aa3382b6bce7b60e6ab3d
ここでは駅から遠い方が賃料が高い関係が見られる
なぜか検討してみよう
築年数と賃料の関係を散布図に書く
おそらく右下がりの関係があるはず
code:R
plot(suumo_data$age_of_building,suumo_data$total_cost)
l<-lm(suumo_data$total_cost~suumo_data$age_of_building)
abline(l)
https://gyazo.com/b45ccfa4e05e7f30a9118fe73bd1ec3f
5、重回帰分析をやってみる
賃料がどのような要因で決まるのかを考えるために、広さ、築年数、駅からの徒歩時間の3つの要因で説明することを考える
被説明変数が賃料と管理費を足したtotal_cost, 説明変数がarea, walk_from_station, age_of_building
code:R
model <- lm(total_cost ~ area + walk_from_station + age_of_building, data = suumo_data)
summary(model)
https://gyazo.com/b4b289c3dabb5203e40355f75fbc5a65
6、結果の解釈
total_cost ≈ 39175.82 + 1970.60 × area + 531.09 × walk_from_station - 571.42 × age_of_building
https://gyazo.com/90c381c1ceb647cd9fe5caba5cf4abb5
https://gyazo.com/7a398e841a4a020a6f565009700f2c77
https://gyazo.com/19f881e0a4c2d092b9e164341a5d95d1
7、予測
今回の結果から、広さが$ 25m^2で駅から徒歩10分、築年数10年の物件の賃料として予想される金額は?
code:R
39175.82+1970.60*25+531.09*10-571.42*10
結果として88037.52円
待てるのであれば、この金額よりも強気な(高めな)価格づけをする
早く決めたいのであれば、この金額よりも弱気な(低めな)価格づけをする
8、今回考慮できていない要素は?
同じ川崎市川崎区でもエリアによって魅力が異なる
近隣にある施設
同じマンションの何階にあるのか
方角
駐車場の有無
室内のリノベーションの有無