-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathR26-NA-Processing.Rmd
More file actions
230 lines (173 loc) · 12.5 KB
/
R26-NA-Processing.Rmd
File metadata and controls
230 lines (173 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
```{r include=FALSE}
knitr::opts_chunk$set(cache = F, warning = F, message = FALSE, class.output = "output")
library(tidyverse)
options(scipen = 999)
```
# NA Processing {#na}
許多統計資料都會有不同程度的`NA`(缺失值、遺漏值)。缺失值產生的原因不一,可能有以下原因:
1. 資料運算的時候產生的填缺失值。例如`spread()`和`pivot_wider()`經常會產生`NA`,也經常會指定值(例如0)來取代可能產生的`NA`。
2. 資料紀錄的時候遺漏某些時間點的資料。
3. 開放資料在開放時已經被整理成階層化、易於展示、一般人易懂的表格型態。此時,若將其讀入也會產生非常大量的`NA`。例如本章節所要提到的政府各部會預算比例。
4. 紀錄資料筆數非常龐大、來源眾多、紀錄時間不一時,雖然有很多紀錄,但這些紀錄必須要被對齊、刪減,才能夠獲得有意義的可計算資料。例如本章節會提到的世界各國疫苗注射資料。
## Cleaning Gov Annual Budget
這個範例將清理中央政府111年度的歲出預算表。由於政府預算有款、科、目、節與機構,會呈現一個大部會到小布會的樹狀階層,因此非常適合用Treemap來做視覺化,預期視覺化的結果如下(視覺化的部分可參考章節\@ref(treemap-nested-proportion)):

在讀入資料後,由於資料具有階層性、從最大的科款、項、目,由於是給一般讀者所閱讀的資料,在原本的EXCEL表格中,比較大的階層可能會合併數個資料格來表示,這會使得較大的階層會有相當多的缺失值。此時,我們會需要依據其他列的值,來回填這些NA值,所用的函式為`zoo::na.locf()`。
```{r}
raw <- readxl::read_excel("data/111B歲出政事別預算表.xls", skip=3, col_names = F)
raw %>% head(10)
```
### Basic Cleaning
1. 重新命名欄位名稱
2. 刪去被當成表格標題的多於列(通常是前兩三列)`slice(-(1:2))`。
3. 觀察資料,「款」可以說是支出大類的代號,例如總統府、行政支出、立法支出、軍事支出、教育支出等。「科」為該單位底下的部門或者項目,例如「行政支出」下有行政院、主計總處支出等。更底下的細類「目」並非本例的分析對象,所以可以刪除。所以,如果款、科均為缺失值的話,代表其為更細的「目」。因此篩去款科為缺失值的所有項目。`filter(!is.na(款) | !is.na(科))`
4. 將機構id和機構名稱切分開來,視覺化的時候只會用到機構名稱。`separate(機構, c("oid", "org"), sep="\n")`
```{r}
names(raw) <- c("款", "科", "目", "節", "機構", "本年度預算", "上年度預算", "上年度決算", "預算差")
cleaned <- raw %>%
slice(-(1:2)) %>%
filter(!is.na(款) | !is.na(科)) %>%
select(-目, -節) %>%
separate(機構, c("oid", "org"), sep="\n")
cleaned %>% head(10)
```
### Processing NA
觀察一下現在的資料,發現,行政院、主計總處等均屬於行政支出,但行政支出卻自有一列。依照長表格的格式來說,應嘗試把「款」作為機構的變項。所以將款的數字取代為「行政支出」等支出類別的名稱。
```{r}
cleaned %>% mutate(款 = ifelse(!is.na(款), org, 款)) %>%
head(10)
```
接下來,希望能夠在「款==`NA`」的地方填入該欄的「前一個值」例如行政支出。查詢一下(關鍵字如「Fill in NA column values with the last value that was not NA」)還真的有這樣的函式可以操作:
::: notes
**`zoo::na.locf()`**:**`zoo::na.locf()`** 是 R 語言中 **`zoo`** 套件提供的函式,其作用是將缺失值(NA)用最後一個非缺失值(non-missing value)填充。具體而言,**`na.locf()`** 函式將會從第一個非缺失值開始向下填充,直到下一個非缺失值出現為止。這種方法稱為 "last observation carried forward"(LOCF),意思是最後觀測值向前填充。
:::
```{r}
library(zoo)
cleaned %>%
mutate(款 = ifelse(!is.na(款), org, 款)) %>%
mutate(款 = zoo::na.locf(款)) %>%
head(10)
```
太神奇了!看見沒!接下來只要把「科 is `NA`」的那些該大類支出總數的紀錄給刪除,資料就乾淨了。最後就只會剩下一些資料清理的功伕。完整程式碼可以看下一節。
### Complete Code
```{r}
library(zoo)
# raw <- readxl::read_excel("data/111B歲出政事別預算總表.xls")
raw <- readxl::read_excel("data/111B歲出政事別預算表.xls", skip=3, col_names = F)
names(raw) <- c("款", "科", "目", "節", "機構", "本年度預算", "上年度預算", "上年度決算", "預算差")
# raw$款 <- na.locf(raw$款)
cleaned <- raw %>%
filter(!is.na(款) | !is.na(科)) %>%
slice(-(1:2)) %>%
select(-目, -節) %>%
separate(機構, c("oid", "org"), sep="\n") %>%
mutate(款 = ifelse(!is.na(款), org, 款)) %>%
mutate(款 = zoo::na.locf(款)) %>%
filter(!is.na(科)) %>%
select(-科) %>% type_convert() %>%
mutate(上年度預算 = as.numeric(上年度預算),
上年度決算 = as.integer(上年度決算),
預算差 = as.numeric(預算差)) %>%
replace_na(list(上年度預算 = 0, 上年度決算 = 0)) %>%
mutate(預算差 = 本年度預算 - 上年度預算)
cleaned %>% head()
```
## Cleaning Covid Vaccinating data
這個案例是希望視覺化不同國家(Y)在不同時間點(X)的疫苗施打涵蓋率(將使用熱區圖,所以將用顏色來表示涵蓋率)。涵蓋率的表示法在該資料中為每百萬人施打疫苗數,但也可以轉為百分比,有多少比例的人已經施打過第一劑、第二劑或第三劑等。
資料來源為:
- <https://ourworldindata.org/covid-vaccinations>
- <https://github.com/owid/covid-19-data/tree/master/public/data/vaccinations>
預期希望看見的結果如下,如何將這份疫苗施打比例的資料做視覺化,請見視覺化的章節\@ref(heatmap-vaccination):

### 觀察並評估資料概況
**這是一份資料缺失相當多的資料**。評估資料概況後可發現這個資料集每一列就是某一個國家某一天所上傳的紀錄。所以,一個國家會有很多列。乍聽之下不難處理,但事實上每個國家不會每天上傳、也不會固定某一天上傳、哪一週、哪一個月開始上傳也不一定,也有可能會漏掉一些月份或週次。所以,制定出一個時間單位(例如週、月)、然後延著時間軸將資料「對齊」,讓每個國家在每個時間單位都有資料。但每個國家疫情發展程度不一,所以也不可能有一個完美的對齊,所以通常會建議就所要觀察的國家進行對齊即可。至於想刪除的那些資料列,幾乎都可以當成是所謂的缺失值。
```{r}
raw <- read_csv("data/vaccinations.csv")
dim(raw)
raw %>% head(20)
```
### 按月對齊資料
首先要挑選要拿來做視覺化的資料欄位。這邊所選擇的是`people_fully_vaccinated_per_hundred`,也就是每百人接種二劑疫苗的人數,相當於接種二劑疫苗的百分比。
接下來便是缺失值處理,如果這個欄位沒有數值的就直接用`drop_na()`篩除即可。
這個範例希望把該資料視覺化為Y軸為年、X軸為時間的熱區圖。但整個疫情資料橫亙二年多,如果以週為彙整單位的話,那勢必X軸會有近百個資料點。所以打算以「月」為單位來彙整這些資料,因為且資料中也有不少國家缺數週的資料,所以以月為彙整單位是一個權衡後的選擇(仍可以嘗試用週作為彙整單位試試看)。所以,運用了`lubridate::floor_date()`來將日期資料轉換為月,例如2022-03-12和2022-03-14都會被轉換為2022-03-01。
依照國家與時間群組彙整資料。接下來就依照各國的月份來做彙整(注意,此時會有不少資料同屬於某個月的資料)。彙整的方法是,經過對「日期」(不是對月)做排序後,僅留下第一筆資料,也就是僅留下最接近月份開頭的資料。經由這樣的操作,會使得各國在每個月剛好留下一筆資料,如下面程式的範例輸出。
```{r}
library(lubridate)
fullvaccinated <- raw %>% select(country = location, date,
people_fully_vaccinated_per_hundred) %>%
drop_na(people_fully_vaccinated_per_hundred) %>%
mutate(m = floor_date(date, unit = "month")) %>%
group_by(country, m) %>%
arrange(date) %>%
slice(1) %>%
ungroup() %>%
select(-date)
fullvaccinated %>% head(10)
```
### 處理遺漏資料的月份
接下來要處理的是資料紀錄的缺漏值。每個國家登錄資料的時間是很不一致的,某些國家會缺某些月份,或者某些國家是在某年某月以後才開始登記,或者最近沒在登記。但這個範例所要視覺化的資料是接種疫苗的比例,所以即使是現在沒在登記了,但接種比例應列計最後一次資料紀錄的接種比例。
首先我要讓每個國家都有所有月份,這裡應會有某個函式可以做到這件事,但我沒查到這個函式。不過我可以很技巧性地用`pivot_wider()`(`spread()`)和`pivot_longer()`(`gather()`)來完成這件事。`spread()`在展開時對於缺少的資料項可以自動補`NA`。所以我就只要把資料的月份展開後再`gather()`回來後,就可以自動讓每個國家所擁有的月份資料一致。以下為`spread()`後的結果,可以觀察到每一列是一個國家,每一欄是個月份,如果當月都沒資料紀錄,那該月的值就會是缺失值。可以看見缺失值是相當多的,也就是在那段時間都沒有資料紀錄。
```{r}
fullvaccinated %>%
spread(m, people_fully_vaccinated_per_hundred, fill=NA) %>%
head(10)
```
在以下的範例輸出可以看到`gather()`後的結果。注意,需要照國家和月份來排序後才便於觀察。
```{r}
fullvaccinated %>%
spread(m, people_fully_vaccinated_per_hundred, fill=NA) %>%
gather(month, perc, -country) %>%
arrange(country, month) %>% head(20)
```
接下來是最技巧性的部分。就接種比例而言是個遞增數列,所以如果這個月有紀錄,但下個月沒紀錄(`NA`),那下個月的資料應以這個月的資料來替代。此時可以用`zoo`套件的`na.locf()`來填`NA`值,其填`NA`值的規則是用最後一筆非`NA`值的資料來替代`NA`值。但要注意的是,因為資料紀錄可能到第六個月或第七個月才開始紀錄,但在前面的月份都沒資料紀錄,也就是說那些`NA`值沒有更早的資料紀錄權充填充值。原本`na.locf()`會把這些找不到參考對象的`NA`值直接刪除,但我們可以在裡面加一個參數使其不會被刪除(`na.locf(perc, na.rm = F)`)。
最後,就把這些沒被刪除也沒得參考的早期資料項,用`replace_na()`填上0即可。
```{r}
fullvaccinated %>%
spread(m, people_fully_vaccinated_per_hundred, fill=NA) %>%
gather(month, perc, -country) %>%
arrange(country, month) %>%
group_by(country) %>%
arrange(month) %>%
mutate(perc = zoo::na.locf(perc, na.rm = F)) %>%
ungroup() %>%
arrange(country, month) %>% head(10)
```
最後,就把這些沒被刪除也沒得參考的早期資料項,用`replace_na()`填上0即可。大功告成。
```{r}
fullvaccinated %>%
spread(m, people_fully_vaccinated_per_hundred, fill=NA) %>%
gather(month, perc, -country) %>%
arrange(country, month) %>%
group_by(country) %>%
arrange(month) %>%
mutate(perc = zoo::na.locf(perc, na.rm = F)) %>%
ungroup() %>%
arrange(country, month) %>%
replace_na(list(perc=0)) %>%
arrange(country, month) %>% head(10)
```
### 完整程式碼
```{r}
library(lubridate)
raw <- read_csv("data/vaccinations.csv")
fullvaccinated <- raw %>% select(country = location, date,
people_fully_vaccinated_per_hundred) %>%
drop_na(people_fully_vaccinated_per_hundred) %>%
mutate(m = floor_date(date, unit = "month")) %>%
group_by(country, m) %>%
arrange(date) %>%
slice(1) %>%
ungroup() %>%
select(-date)
vperc_by_month <- fullvaccinated %>%
spread(m, people_fully_vaccinated_per_hundred, fill=NA) %>%
gather(month, perc, -country) %>%
arrange(country, month) %>%
group_by(country) %>%
arrange(month) %>%
mutate(perc = zoo::na.locf(perc, na.rm = F)) %>%
ungroup() %>%
arrange(country, month) %>%
replace_na(list(perc=0))
```
如何將這份疫苗施打比例的資料做視覺化,請見視覺化的章節\@ref(heatmap-vaccination)