關卡 1
近年來,能源議題是個和大家息息相關的問題。 而透過政府的公開資料,我們是有能力從這些資料更深入的了解能源議題。 又由於能源議題往往和經濟議題息息相關,所以我們希望能夠好好的整理 台灣各行業的耗電量,以及各行業所貢獻的GDP 。 藉由整理出來的結果,來協助我們更深入的思考台灣的能源相關議題。
關卡 2
這次練習中,用電的資料來源是取自:<http://data.gov.tw/node/6064>。 我目前將2015-09-26時下載後的檔案解壓縮,並且把要用到的「歷年行業別.txt」 搬到課程目錄之下。由於中文檔名在跨平台上不方便,所以更改名稱為power.txt。
關卡 3
由於網路在傳輸課程資料時,可能會出錯,所以我們可以先用R 的內建 功能檢查power.txt的內容是不是在安裝過程中毀損。請同學輸入: tools::md5sum(power_path)
。完成後課程會在背景自動比對結果。
tools::md5sum(power_path)
關卡 4
這裡的::
代表說,md5sum
這個函數是來自tools
這個套件。 這是近代R 語言新增的一種類似namespace的功能,是個很大的進步。
關卡 5
各行業的GDP 資料則下載自<http://statdb.dgbas.gov.tw/pxweb/dialog/statfile9L.asp>。 檔案路徑已經放入gdp_path
之中。請同學再次利用tools::md5sum(gdp_path)
來檢查檔案內容。
tools::md5sum(gdp_path)
關卡 6
首先,就讓我們把資料載入到R 之中吧! 首先我們來一起處理電力資料。 一般來說,在讀中文資料之前,我們要先了解資料的編碼。 由於官方文件上並沒有編碼的資訊,所以在實務上只能一個個嘗試。 通常政府的官方資料如果沒有特別註明,建議就是從BIG-5開始嘗試。 請同學試試看:readLines(file(power_path, encoding = "BIG-5"), n = 10)
這裡的file
是R 建立一個檔案連結的方式。file的第一個參數是檔案的路徑, encoding
則代表這個檔案內容的編碼。 readLines
則是輸出一個字串向量,而這個向量是從檔案中一行一行的把內容讀出來的。 也就是說,檔案裡面的每一行會變成一個向量中的字串。 參數n = 10
則代表我們只讀最前面的10行。 這是實務上的一個好習慣:先看一看檔案的一開始。
readLines(file(power_path, encoding = "BIG-5"), n = 10)
關卡 7
如果成功看到中文字,就是猜對Encoding了。
關卡 8
接下來,從螢幕上我們可以看到,這個檔案的分隔符號會是應該是";\t"
, (還滿少見的)。另外在中間有部分資料的分隔符號只有"\t"
。 可惜R 的能接受的分隔符號,通常只能是一個字元。 資料不大,所以我們可以自己切資料吧! 首先,請同學用readLines把所有資料讀出來,並且將資料都存到power
這個變數。
power <- readLines(file(power_path, encoding = "BIG-5"))
關卡 9
其實這樣混亂、不好整理的資料,是我們在處理實際資料時 常常遇到的狀況。而且我們也常常犯錯,導致要重頭開始重新整理。
關卡 10
在R 中,我們可以使用strsplit
來切割字串。 strsplit
的參數x 是要被切割的字串,split是定位分割點的字串。 請同學呼叫strsplit
來用";?\t"
切割power
,並且將結果儲存到 power.split
中。 ";?\t"
在這邊代表,這個分號;
是可有可無的。這是為了讓R 能夠 同時處理分隔符號為";\t"
或"\t"
的狀況。
power.split <- strsplit(power, ";?\t")
關卡 11
使用do.call
可以讓我們把power.split
組裝成一個character matrix。 請同學輸入:power.mat <- do.call(rbind, power.split)
。 power.split
是一個R 物件向量,而rbind是吃一個任意參數的函數, 並且把每個參數當成一個row,組裝出一個matrix。 do.call
會把在這裡的功能,就等價於:rbind(power.split[[1]], power.split[[2]], ...)
把power.split
拆解後,全部當成參數丟到rbind去做組裝。
power.mat <- do.call(rbind, power.split)
關卡 12
我們差不多把電力資料整理的差不多了,只剩下最後一步:把power.mat
轉換成一個data.frame,並且讓各個column擁有正確的形態。 但是這最後一步卻有許多小步驟。 首先,我們把power.mat
用data.frame
轉換成power.df
這裡要注意的是,data.frame
在轉換的時候,這裡有下參數:stringsAsFactors = FALSE
, 否則欄位會被轉換成factor,在做後續字串處理時,有時候會出錯。
power.df <- data.frame(power.mat, stringsAsFactors = FALSE)
關卡 13
然後,我們設定power.df
的欄位名稱(colnames)依序為: c("id", "name", "year", "power")
請同學使用:colnames(power.df) <-
的語法來指定power.df的 colnames。
colnames(power.df) <- c("id", "name", "year", "power")
關卡 14
接下來,我們設定讓power.df的name欄位是factor
。 請使用factor
這個函數。
power.df$name <- factor(power.df$name)
關卡 15
接下來,我們設定讓power.df的year欄位是integer
。 請使用as.integer
這個函數。
power.df$year <- as.integer(power.df$year)
關卡 16
接下來,我們設定讓power.df的power欄位是numeric
。 請使用as.numeric
這個函數。
power.df$power <- as.numeric(power.df$power)
關卡 17
大功造成了,請輸入head(power.df)
來檢視一下我們整理好的 data.frame物件。同時我們也會再檢驗一次power.df看看是否正確。
head(power.df)
關卡 18
由於我們在抓資料的分割點時,很有可能會抓錯。所以保險起見, 在這個時間點我們要檢查資料是否有清理乾淨。 在R 中,如果中間資料有任何錯誤的話,常常會有NA產生。 所以一個方法就是用is.na
檢查power.df
的四個欄位中間有沒有NA。 all
這個函數,則可以檢查一個邏輯向量的所有的值是否為TRUE。 請同學組合is.na
和all
來檢查power.df
的欄位是否包含NA。
all(!is.na(power.df))
關卡 19
接下來,我們來運用dplyr和ggplot2這兩個套件將power.df的資料繪製成圖表。 請先安裝dplyr套件。
check_then_install("dplyr", "0.4.3")
關卡 20
請再安裝ggplot2套件
check_then_install("ggplot2", "1.0.1")
關卡 21
使用套件的起手式:載入dplyr
library(dplyr)
關卡 22
這裡的id
欄位,是中華民國行業標準分類的代碼。 這個代碼的最大類, 都是用英文字母標示的。所以我們就用regular expression來找出 所有英文開頭的id
。 grepl("^[A-Z]", input)
會判斷input這個字串向量的值,是不是符合 字母開頭。如果是的話,就傳TRUE,否則就是FALSE。 和dplyr套件的filter
搭配使用,我們就可以快速取得所有大類的資料。 請同學將這樣的資料存到power.target
這個變數之中。
power.target <- filter(power.df, grepl("^[A-Z]", id))
關卡 23
filter 不只可以幫我們挑出主要的行業類別,還可以挑選年度。 所以我們可以繪製每年度,主要行業別的用電比較。 首先,我們先整理出91年度的主要類別,並且存放到power.target
之中。
power.target <- filter(power.df, grepl("^[A-Z]", id), year == 91)
關卡 24
我們可以利用power.target來繪製一個barchart來呈現 各種行業別的用電量。首先,還是要先載入ggplot2。
library(ggplot2)
關卡 25
我們先建立ggplot2的底圖物件。 這裡我們會用到aes
這個函數,來辨識在power.target
的欄位,哪一欄要放到x 軸, 哪一欄,要放到y 軸。x 軸的通常放的是分類,而y 放的是我們想要觀測的數據的值。 請同學修改以下的指令,將ggplot2的底圖物件寫入變數g
。 指令是:g <- ggplot(power.target, aes(x = <欄位名稱1>, y = <欄位名稱2>))
g <- ggplot(power.target, aes(x = name, y = power))
關卡 26
建立底圖之後,我們就要選用適當的圖形工具來適當的呈現我們要看的工具。 這裡我們來用geom_bar
來呈現我們要資料。 大家可以試試看:g + geom_bar(stat = "identity")
g + geom_bar(stat = "identity")
關卡 27
有些使用者可能會無法在圖上顯示中文。這可能是字形的問題。 如果是蘋果牌的使用者,可以更改預設的字形來顯示中文: theme_set(theme_gray(base_family = "STKaiti"))
關卡 28
另外我們也會發現整個x 軸容納不下我們的說明文字。 這時候可以透過設定xlab
的字體方向,來解決這個問題: theme(axis.text.x = element_text(angle = 90))
請注意,ggplot2在設定類型時,是直接透過在程式碼的最後用+
來加上 theme(...)
來作處理的。 我建議各位同學先使用上下鍵盤,找出剛剛繪圖的指令,然後在最後加上: + theme(axis.text.x = element_text(angle = 90))
g + geom_bar(stat = "identity") + theme(axis.text.x = element_text(angle = 90))
關卡 29
從圖上顯示,我們可以注意到在民國91年的時候, 在各行業中,製造業還是消耗了最多的電力。
關卡 30
在ggplot2,我們可以把bar chart更改成pie chart。 請同學試試看:ggplot(power.target, aes(x = "", y = power, fill = name)) + geom_bar(stat = "identity") + coord_polar(theta = "y")
如果同學覺得這個指令很冗長的話,可以輸入skip()
來跳過。
ggplot(power.target, aes(x = "", y = power, fill = name)) + geom_bar(stat = "identity") + coord_polar(theta = "y")
關卡 31
事實上ggplot2對pie chart的支援並不好,而這是有理由的。 有興趣的同學可以讀一讀<http://www.r-chart.com/2010/07/pie-charts-in-ggplot2.html> 裡面提到一些統計學家對pie chart的批評。
關卡 32
而根據這個pie chart,我們至少可以了解 在民國91年製造業消耗了我們超過一半的電力。
關卡 33
一個我們也許會感興趣的問題是: 我們的GDP是不是大部份也來自於製造業呢?
關卡 34
接著,我們來整理GDP的資料…
關卡 35
透過一樣的要領,我們應該先用readLines
先看看gdp_path
的資料內容。請同學檢視gdp_path
這個檔案的前20行。
readLines(file(gdp_path, encoding = "BIG-5"), n = 20)
關卡 36
一些不熟悉R 的同學可能會被\"
這個符號給搞混了。 由於"
在R 中是代表字串的開始和結束,所以R 會在顯示字串時, 在"
之前加註\
,所以一個\"
實際上就是一個"
關卡 37
另一個避免\"
干擾的方式,是使用cat
這函數。 請同學試試看:cat(readLines(file(gdp_path, encoding = "BIG-5"), n = 20), sep = "\n")
cat(readLines(file(gdp_path, encoding = "BIG-5"), n = 20), sep = "\n")
關卡 38
上一個問題的答案,其實牽涉到了三層函數:
關卡 39
我們先呼叫file(gdp_path, encoding = "BIG-5")
建立一個connection。 connection是R 處理檔案的專業術語,如果比較不熟悉程式設計的同學,就先把 connection當成檔案的代理就好。
關卡 40
接著,我們使用readLines
處理file
回傳的物件。這是第二層。 這裡我們指定參數n=20
,代表我們要從connection的最前面讀取前20行。
關卡 41
最後,我們把readLines
回傳的20個字串交給cat
作處理。 cat
會直接把字串內容印到R 的console上,而且我們指定每一個 字串之間要插入一個斷行。
關卡 42
這樣一層一層疊上去的寫法,很容易讓程式變的很複雜。 Stefan Milton Bache推出了一個R 套件很好地解決了這個問題: magrittr
。我們在安裝dplyr
之後,應該已經安裝了這個套件了。 因此,請同學直接輸入:library(magrittr)
library(magrittr)
關卡 43
回顧一下我們剛剛做的事情: 把file(gdp_path, encodoing = "BIG-5")
得到的物件給 readLines(..., n = 20)
再把得到的物件給 cat(..., sep = "\n")
。 這裡的...
就代表從上一個函數得到的物件。使用magrittr
後, 我們的寫法就變成:file(gdp_path, encoding = "BIG-5") %>% readLines(n = 20) %>% cat(sep = "\n")
請同學試試看
file(gdp_path, encoding = "BIG-5") %>% readLines(n = 20) %>% cat(sep = "\n")
關卡 44
在實務上,我們會在%>%
後斷行,增加程式的可讀性。
關卡 45
回歸正傳,我們第一個要看的,是檔案中的分隔符號是什麼。 大家在看過檔案內容之後,應該會同意:分隔符號是,
另外大家也會注意到這個檔案中,字串的內容的前後會加上"
。 另外每一列的欄位數目也不一致,所以不能簡單用read.csv
解決。 因為資料量不大,我們還是全部用readLines
讀進來後再用strsplit
自己切吧!
關卡 46
和剛剛的電力資料一樣,請讓我們先用readLines
把資料全部 載入並且存到gdp
這個變數之中。
gdp <- file(gdp_path, encoding = "BIG-5") %>% readLines()
關卡 47
接著,我們利用strsplit
,用","
當分隔符號, 將讀入的gdp資料切成list。請將結果存到gdp.split
gdp.split <- strsplit(gdp, ",")
關卡 48
仔細一點的話,我們能注意到,年份的資料在切割後,長度都是1 。 所以讓我們選取gdp.split
這個list中,length
為1 的值。這部分,可以 利用sapply
這個函數來查詢。請同學試試看: (sapply(gdp.split, length) == 1) %>% which
這裡的sapply(gdp.split, lenght)
相當於建立一個向量,並且讓第一個值 為length(gdp.split[[1]])
,第二個值為length(gdp.split[[2]])
,以此類推。
(sapply(gdp.split, length) == 1) %>% which
關卡 49
我們可以看一下,這些長度為一的行的內容: 請同學輸入:gdp.split[(sapply(gdp.split, length) == 1) %>% which]
gdp.split[(sapply(gdp.split, length) == 1) %>% which]
關卡 50
我們可以看到,只有前7 個index 是真的代表年份。 最後幾個index 其實只有指到這個檔案的一些說明。 所以我們利用head
這個函數,拿出前7 個index,並存到:year.index
之中。 請同學輸入:year.index <- (sapply(gdp.split, length) == 1) %>% which %>% head(7)
year.index <- (sapply(gdp.split, length) == 1) %>% which %>% head(7)
關卡 51
接著我們可以利用for 迴圈來切出這份資料。這部分請同學 參考路徑在gdp_df_path
的這個檔案,修改它,讓我們能得到gdp_df
這個整理乾淨過後的data.frame。 你可以利用browseURL(gdp_df_path)
來打開參考檔案。把檔案 的內容複製到gdp_df.R之中,並且再完成之後,輸入:submit()
# 開始的index
year.index.start <- year.index
# 結束的index
## 每一個開始的index接續到下一個開始的index
## 也就是結尾的index就是下一個開始的index
year.index.end <- c(tail(year.index, -1), length(gdp.split))
# 開始撰寫for迴圈
## 我們先建立每一年份整理出來的gdp 資料
## 這些資料會被放到gdp.df.components之中
gdp.df.components <- list()
# 請填寫正確的for 迴圈的範圍
for(i in 1:7) {
# 針對特定年份做處理的程式碼
## 開始的index
start <- year.index.start[i]
## 結束的index
end <- year.index.end[i]
## 年份的資料在第一筆
year <- gdp.split[[start]] %>%
## 將 "2007" ==> 2007,將"用空白替換掉
gsub(pattern = '"', replacement = '')
## 只抽出這次要處理的資料
target <- gdp.split[start:end]
## 挑出長度是3 的,做rbind
target.mat <- do.call(rbind,
target[sapply(target, length) == 3])
## 原本的第一行是空白,我們改成放年份
target.mat[,1] <- year
## 處理第二行中的"
target.mat[,2] <- gsub('"', '', target.mat[,2])
## 將這輪處理的資料,放到gdp.df.components
gdp.df.components[[i]] <- target.mat
}
gdp.df <- do.call(rbind, gdp.df.components) %>%
## 我們先不把資料轉成factor
data.frame(stringsAsFactors = FALSE)
colnames(gdp.df) <- c("year", "name", "gdp")
## 把year 轉成integer
gdp.df$year <- as.integer(gdp.df$year)
## 把name 轉成character
gdp.df$name <- as.character(gdp.df$name)
## 把gdp 轉成numeric
gdp.df$gdp <- as.numeric(gdp.df$gdp)
gdp.df <- filter(gdp.df, !is.na(gdp))
關卡 52
接下來我們比對兩者的資料,好計算使用能源的效率。 但是,這不容易… 比對資料最重要的,是兩者的一致性。
關卡 53
我們先看看gdp.df資料中的行業別。請同學輸入:unique(gdp.df$name)
unique(gdp.df$name)
關卡 54
再來看看power.df資料中的行業別。請同學輸入:unique(power.df$id)
unique(power.df$id)
關卡 55
這裡的資料,一邊是英文+中文,一邊是數字或英文。所以我們先把目標 放在英文上就好了。
關卡 56
請同學輸入:gdp.df %<>% mutate(id = strsplit(name, ".", fixed = TRUE) %>% sapply("[", 1))
這裡的 %<>% 表示R 會把gdp.df 放到 mutate的第一個參數,然後把輸出結果回存到 變數 gdp.df 。這裡的動作是用 “.” 來切割 gdp.df$name ,然後將“.”之前的結果 放到欄位id
gdp.df %<>% mutate(id = strsplit(name, ".", fixed = TRUE) %>% sapply("[", 1))
關卡 57
我們輸入:unique(gdp.df$id)
看看整理後的結果。
unique(gdp.df$id)
關卡 58
中間混雜了一些中英文,並且有些是子類別。例如:JA、JB喊JC就是J的子類別。
關卡 59
請輸入:gdp.df2 <- filter(gdp.df, nchar(id) == 1)
來只挑出那些 id 為單一文字的資料
gdp.df2 <- filter(gdp.df, nchar(id) == 1)
關卡 60
我們輸入:unique(gdp.df2$id)
看看整理後的結果,是不是全英文
unique(gdp.df2$id)
關卡 61
接著我們把目標轉到power.df。請同學利用上述所學,挑選出 id欄位為英文的data.frame,將year轉為西元,並將結果存到 power.df2
中。
#' 提示:
#' 我會用到filter + grepl, 來挑出英文的id
#' ps. "^[A-Z]" 代表要尋找英文大寫字母開頭的字串
#' mutate 來將年度從民國變成西元
#' 最後用 mutate + gsub 把 id 中的"."給去除
power.df2 <-
filter(power.df, grepl("^[A-Z]", id)) %>%
mutate(year = year + 1911, id = gsub(".", "", id, fixed = TRUE))
stopifnot(nrow(power.df2) == 204)
stopifnot(sum(power.df2$year) == 409224)
關卡 62
接下來,就可以用id與year來比對資料了…嗎?
關卡 63
請同學輸入:distinct(power.df2, id, name)
再檢查一下 用電量資料裡的id所代表的意義
distinct(power.df2, id, name)
關卡 64
再請同學輸入:distinct(gdp.df2, id, name)
再檢查一下 國民生產毛額的資料裡的id所代表的意義
distinct(gdp.df2, id, name)
關卡 65
同學有沒有注意到,兩者的K,代表的意義不一致?
關卡 66
這裡我們幫同學簡略地整理一個對應表,放在變數:translation
, 請同學看一看。這是一個多對多的對應關係。有些行業別我們不知道 如何對應,等等就先略過。
translation
關卡 67
我們要先把translation展開。利用translation做一個對應表。 請同學手動建立一個文字向量:名字就剛好是 c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K")
代表power.df2中的id;值是則是c(1, 2, 3, …),代表 該值出現在translation中的位置。最後將結果存到變數: translation.power
translation.power <- local({
retval <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 10)
names(retval) <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K")
retval
})
關卡 68
請用同樣的要領建立變數:translation.gdp
translation.gdp <- local({
translation.gdp <- c(1, 2, 3, 4, 4, 5, 6, 7, 6, 7, 8, 8, 9, 9, 10, 9, 9, 9, 9)
names(translation.gdp) <- head(LETTERS, length(translation.gdp))
translation.gdp
})
關卡 69
接著,透過translation,我們就可以將兩邊的資料比對成一個data.frame。 請同學挑戰看看。
power.df3 <-
power.df2 %>%
mutate(id2 = translation.power[id]) %>%
filter(!is.na(id2)) %>%
group_by(year, id2) %>%
summarise(power = sum(power), name = paste(name, collapse = ","))
gdp.df3 <-
gdp.df2 %>%
mutate(id2 = translation.gdp[id]) %>%
group_by(year, id2) %>%
summarise(gdp = sum(gdp))
power.gdp <- inner_join(power.df3, gdp.df3, c("year", "id2")) %>%
mutate(eff = gdp / power) %>%
as.data.frame()
關卡 70
恭喜同學完成比對各行業的用電資料與貢獻的國民生產毛額。 我們接下來嘗試利用power.gdp
來建立一些圖表看看資料說說 故事。