我如何用R實現資料視覺化
端傳媒數據記者林佳賢
什麼是資料新聞?
R是什麼?
- 開源的統計程式語言
- 資料科學家的主要工具之一
- 有大量開發者貢獻的package
- 有RStudio等友善開發工具
R CRAN
RStudio
R如何幫助我完成資料新聞
- 搜集資料
- 清理資料
- 彙整資料
- 繪製圖表
實際操作展示R的功能
我想知道「台北市各行政區的套房租金價位」
先從搜集資料開始
用R從網路搜集資料
從網路搜集資料的行為,通稱「網路爬蟲」。寫網路爬蟲通常用到的是Python,但R語言也有相應的package,可以完成不輸給Python的爬蟲任務。
R網路爬蟲會用到的package
- httr
- rvest
- jsonlite
httr
- 類似Python的requests
- 在從網頁中取得資料之前,需要先取得網頁原始碼
- httr提供各種工具,讓我們能取得各種網頁內容
- 常用的功能:GET、POST、content。
# load httr package
library(httr)
# 用GET功能把591租屋網搜尋台北市租屋的結果拿下來
doc <- GET("https://rent.591.com.tw/index.php?module=search&action=rslist&is_new_list=1&type=1&searchtype=1®ion=1&orderType=desc&listview=txt&firstRow=120&totalRows=12674")
# 用content功能觀察剛剛拿下來的網頁內容
content(doc, "text")
content(doc, "text")的內容
jsonlite
- 如果拿下來的網頁內容是json格式,需要使用jsonlite處理。
- jsonlite的fromJSON功能可以快速整理json格式的網頁內容,最後轉成容易處理的格式。
# load httr package
library(httr)
library(jsonlite)
# 用GET功能把591租屋網搜尋台北市租屋的結果拿下來
doc <- GET("https://rent.591.com.tw/index.php?module=search&action=rslist&is_new_list=1&type=1&searchtype=1®ion=1&orderType=desc&listview=txt&firstRow=120&totalRows=12674")
# 用content功能觀察剛剛拿下來的網頁內容
content(doc, "text")
# 把剛剛拿下來的網頁內容,從json格式轉成R容易處理的格式
df <- fromJSON(content(doc, "text"))
df長什麼樣子
# load httr package
library(httr)
library(jsonlite)
# 用GET功能把591租屋網搜尋台北市租屋的結果拿下來
doc <- GET("https://rent.591.com.tw/index.php?module=search&action=rslist&is_new_list=1&type=1&searchtype=1®ion=1&orderType=desc&listview=txt&firstRow=120&totalRows=12674")
# 用content功能觀察剛剛拿下來的網頁內容
content(doc, "text")
# 把剛剛拿下來的網頁內容,從json格式轉成R容易處理的格式
df <- fromJSON(content(doc, "text"))
# 我們還要從df這個list中,找到需要的那一部分
rent_data <- df[["main"]]
# 觀察一下rent_data
rent_data
rent_data長什麼樣子
rvest
- 類似Python的Beautiful soup 4
- 提供方便的工具,從雜亂的網頁原始碼中找到需要的資訊
- 需要搭配CSS selector或Xpath使用
- rvest作者推薦使用selector gadget
- 常用的功能:read_html、html_nodes、html_table。
# load package
library(httr)
library(jsonlite)
library(rvest)
# 用GET功能把591租屋網搜尋台北市租屋的結果拿下來
doc <- GET("https://rent.591.com.tw/index.php?module=search&action=rslist&is_new_list=1&type=1&searchtype=1®ion=1&orderType=desc&listview=txt&firstRow=120&totalRows=12674")
# 用content功能觀察剛剛拿下來的網頁內容
content(doc, "text")
# 把剛剛拿下來的網頁內容,從json格式轉成R容易處理的格式
df <- fromJSON(content(doc, "text"))
# 我們還要從df這個list中,找到需要的那一部分
rent_data <- df[["main"]]
# 觀察一下rent_data
rent_data
# 用rvest的read_html功能,整理rent_data
rent_html <- read_html(rent_data)
# load package
library(httr)
library(jsonlite)
library(rvest)
# 用GET功能把591租屋網搜尋台北市租屋的結果拿下來
doc <- GET("https://rent.591.com.tw/index.php?module=search&action=rslist&is_new_list=1&type=1&searchtype=1®ion=1&orderType=desc&listview=txt&firstRow=120&totalRows=12674")
# 用content功能觀察剛剛拿下來的網頁內容
content(doc, "text")
# 把剛剛拿下來的網頁內容,從json格式轉成R容易處理的格式
df <- fromJSON(content(doc, "text"))
# 我們還要從df這個list中,找到需要的那一部分
rent_data <- df[["main"]]
# 觀察一下rent_data
rent_data
# 用rvest的read_html功能,整理rent_data
rent_html <- read_html(rent_data)
# 用html_nodes、html_text和html_attr功能,把需要的資料拿出來
rent_df <- data.frame(
county = html_text(html_nodes(rent_html, ".shTxInfo .txt-sh-region")),
town = html_text(html_nodes(rent_html, ".shTxInfo .txt-sh-section")),
name = html_attr(html_nodes(rent_html, ".shTxInfo .address a"), "title"),
area = html_text(html_nodes(rent_html, ".shTxInfo .area")),
price = html_text(html_nodes(rent_html, ".shTxInfo .price .fc-org"))
)
可以使用Chrome的插件「selector gadget」取得 selector
rent_df長什麼樣子
# load package
library(httr)
library(jsonlite)
library(rvest)
# 用GET功能把591租屋網搜尋台北市租屋的結果拿下來
doc <- GET("https://rent.591.com.tw/index.php?module=search&action=rslist&is_new_list=1&type=1&searchtype=1®ion=1&orderType=desc&listview=txt&firstRow=120&totalRows=12674")
# 用content功能觀察剛剛拿下來的網頁內容
content(doc, "text")
# 把剛剛拿下來的網頁內容,從json格式轉成R容易處理的格式
df <- fromJSON(content(doc, "text"))
# 我們還要從df這個list中,找到需要的那一部分
rent_data <- df[["main"]]
# 觀察一下rent_data
rent_data
# 用rvest的read_html功能,整理rent_data
rent_html <- read_html(rent_data)
# 用html_nodes、html_text和html_attr功能,把需要的資料拿出來
rent_df <- data.frame(
county = html_text(html_nodes(rent_html, ".shTxInfo .txt-sh-region")),
town = html_text(html_nodes(rent_html, ".shTxInfo .txt-sh-section")),
name = html_attr(html_nodes(rent_html, ".shTxInfo .address a"), "title"),
area = html_text(html_nodes(rent_html, ".shTxInfo .area")),
price = html_text(html_nodes(rent_html, ".shTxInfo .price .fc-org"))
)
# 只要改動firstRow的數字,就能爬取其他頁面的資料。簡單的說,只要寫一個迴圈,就可以把所有台北市租屋資訊抓下來。
doc2 <- GET("https://rent.591.com.tw/index.php?module=search&action=rslist&is_new_list=1&type=1&searchtype=1®ion=1&orderType=desc&listview=txt&firstRow=160&totalRows=12674")
接下來是清理資料
爬取下來的資料幾乎不可能是乾淨的資料
- 時間格式通常不是電腦看得懂的格式,尤其在台灣更嚴重(105年 vs 2016)
- 想要的資料可能和其他資料混在一起(兩欄混成一欄)
- 金額欄裡有中文、逗點等電腦無法辨識為數字的雜訊
R用來清理資料的package:stringr
- 補充R原本用來處理文字的功能不足之處
- 能做到文字搜尋、取代、連接、切割等功能。
- 常用的功能:str_c、str_split、str_replace。
rent_df原本的樣子
在分析資料前,需要處理的工作
- 把價格欄位清到只剩下數字,再轉成數字格式
- 把area欄位中不屬於面積的資訊,獨立成type欄位
- 新成立一個欄位叫做unit_price(每坪租金)
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
...
# 清理價格資訊,把逗點跟「元」清掉,再轉成數字格式
rent_df$price <- as.numeric(str_replace_all(rent_df$price, ",|元", ""))
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
...
# 清理價格資訊,把逗點跟「元」清掉,再轉成數字格式
rent_df$price <- as.numeric(str_replace_all(rent_df$price, ",|元", ""))
# 新成立type一欄,這一欄的資訊來自area欄。我們用「/」切割type欄,取出後面的部分作為type欄的資訊。
rent_df$type <- sapply(str_split(rent_df$area, "/"), "[[", 2)
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
...
# 清理價格資訊,把逗點跟「元」清掉,再轉成數字格式
rent_df$price <- as.numeric(str_replace_all(rent_df$price, ",|元", ""))
# 新成立type一欄,這一欄的資訊來自area欄。我們用「/」切割type欄,取出後面的部分作為type欄的資訊。
rent_df$type <- sapply(str_split(rent_df$area, "/"), "[[", 2)
# 清理area欄,這一欄的資訊來自原本的area欄。我們用「/」切割type欄,取出前面的部分作為type欄的資訊。接著把「坪」清掉,再轉成數字格式。
rent_df$area <- as.numeric(str_replace_all(sapply(str_split(rent_df$area, "/"), "[[", 1), "坪", ""))
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
...
# 清理價格資訊,把逗點跟「元」清掉,再轉成數字格式
rent_df$price <- as.numeric(str_replace_all(rent_df$price, ",|元", ""))
# 新成立type一欄,這一欄的資訊來自area欄。我們用「/」切割type欄,取出後面的部分作為type欄的資訊。
rent_df$type <- sapply(str_split(rent_df$area, "/"), "[[", 2)
# 清理area欄,這一欄的資訊來自原本的area欄。我們用「/」切割type欄,取出前面的部分作為type欄的資訊。接著把「坪」清掉,再轉成數字格式。
rent_df$area <- as.numeric(str_replace_all(sapply(str_split(rent_df$area, "/"), "[[", 1), "坪", ""))
# 新成立unit_price一欄,這一欄的資料是price欄除以area欄,四捨五入到整數位的結果。
rent_df$unit_price <- round((rent_df$price / rent_df$area))
最終成果
完成資料清理的部分後,就可以進入分析部分了。
這裡用我之前抓的591租屋網全台灣租屋資料為例子
rent591
分析資料會用到的package:dplyr
- 相當於Excel和Google spreadsheet的樞紐分析功能
- 透過資料分組把資料進行各種彙整。
- 常用的功能:filter、group_by、summarise、arrange。
dplyr::filter
用價格、地區等資訊篩選資料
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
library(dplyr)
...
# 把rent591裡面台北市的資料拿出來
rent591_tp <- filter(rent591, county == "台北市")
# 把rent591裡面租金大於1萬元的資料拿出來
rent591_expensive <- filter(rent591, price > 10000)
# 把rent591裡面的套房資料拿出來
rent591_tao <- filter(rent591, type == "套房")
# 把rent591裡面的套房及雅房資料拿出來
rent591_tao_ya <- filter(rent591, type == "套房" | type == "雅房")
dplyr::arrange
用價格、地區等資訊排序資料
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
library(dplyr)
...
# 把rent591_tp的資料以租金由高到低排列
rent591_tp <- arrange(rent591_tp, desc(price))
# 把rent591_expensive的資料以每坪租金由高到低排列
rent591_expensive <- arrange(rent591_expensive, desc(unit_price))
# 把rent591_tao的資料以每坪租金由低到高排列
rent591_tao <- arrange(rent591_tao, unit_price)
# 把rent591_tao_ya根據縣市名稱做排序
rent591_tao_ya <- arrange(rent591_tao_ya, county)
dplyr::group_by
用價格、地區等資訊分類資料
dplyr::summarise
用分類結果彙整資料
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
library(dplyr)
...
# 計算台北市各行政區的平均租金
# 先把rent591_tp根據行政區分類
rent591_tp_group <- group_by(rent591_tp, town)
# 再根據分類結果計算各行政區的平均租金
rent591_tp_price <- summarise(rent591_tp_group, mean_price = mean(price))
# 最後把計算結果排序
rent591_tp_price <- arrange(rent591_tp_price, desc(mean_price))
rent591_tp_price的結果
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
library(dplyr)
...
# 計算台北市各行政區的套房平均面積
# 先篩選出rent591_tp的套房資料
rent591_tp <- filter(rent591_tp, type == "套房")
# 先把rent591_tp根據行政區分類
rent591_tp_group <- group_by(rent591_tp, town)
# 再根據分類結果計算各行政區的平均租金
rent591_tp_area <- summarise(rent591_tp_group, mean_area = mean(area))
# 最後把計算結果排序
rent591_tp_area <- arrange(rent591_tp_area, desc(mean_area))
rent591_tp_area的結果
有了這些分析結果之後,就可以來畫圖了
R的資料視覺化package:ggplot2
- 支援各種圖表類型
- 設計邏輯基於「grammar of graphics」
- 支援svg、pdf等向量輸出格式
基本的繪圖:長條圖
geom_bar
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
library(dplyr)
library(ggplot2)
...
# 把台北市各行政區的平均租金繪製成長條圖
# 設定畫布跟基本資訊:使用的資料(rent591_tp_price)、x軸(town)、y軸(mean_price)
ggplot(rent591_tp_price, aes(town, mean_price)) +
# 選擇要畫的圖表類型,這裡選擇geom_bar(長條圖)。stat = "identity"指的是用原本的數字作為長條高度
geom_bar(stat = "identity") +
# 由於R預設字型不支援中文顯示,因此要在最後加上字體設定,這裡設定為STHeiti(黑體)
theme(text = element_text(family = "STHeiti"))
產生的圖表
由於長條圖沒有自己排序,我們需要自己把行政區排序
# load package
library(httr)
library(jsonlite)
library(rvest)
library(stringr)
library(dplyr)
library(ggplot2)
...
# 把台北市各行政區轉成可排序的factor格式,再根據自己定義的順序排序factor。
rent591_tp_price$town <- factor(rent591_tp_price$town,
c("大安區","松山區","中正區",
"內湖區","信義區","中山區",
"士林區","萬華區","南港區",
"大同區","北投區","文山區"))
# 再畫一次圖
ggplot(rent591_tp_price, aes(town, mean_price)) +
geom_bar(stat = "identity") +
theme(text = element_text(family = "STHeiti"))
排序後的結果
除了長條圖以外,ggplot2還支援許多圖表類型
- 折線圖
- 熱度圖
- 地圖
- 直方圖
- 點圖
輸出成向量格式後,再用繪圖軟體進行後製,就是精美的資料作品了
R幫助我們做了些什麼事
- 從591租屋網爬取全台北乃至全台灣的租屋資料
- 將爬取下來的資料清理到方便分析的狀態
- 用篩選、排序、分組、彙整等功能分析資料
- 將分析結果畫成圖表
R優於Excel、Google Spreadsheet的地方
- 能將資料處理過程存成script,供以後重複使用或供他人檢視
- R的資料處理沒有1048576筆資料的上限
- R操作起來要快上許多
想了解更多資料分析?
- Data Man 的資料視覺化筆記
- 電子郵件:imandylin2@gmail.com
- 端傳媒
Q & A
我如何用R實現資料視覺化
By Andy Lin
我如何用R實現資料視覺化
- 5,177