-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy path03-tidy-data.Rmd
312 lines (221 loc) · 14.9 KB
/
03-tidy-data.Rmd
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# 데이터 주무르기
> 씹고 뜯고 맛보고 즐기고 -- 이가탄
## datatoys 패키지 소개
> Play gives children a chance to practice what they are learning
> -- Fred Rogers
데이터를 분석하는데 있어서 가장 큰 장벽은 데이터를 읽어오고, 분석에 알맞는 형태로 만드는 작업이다. **datatoys 패키지는 공공데이터포털에서 제공하는 각종 데이터를 R에서 곧바로 분석할 수 있는 상태로 제공**한다(python [버전](https://github.com/statgarten/datatoys-python)도 있지만 R이 조금 먼저 개발된다). 패키지의 이름처럼 마치 어린 아이가 데이터를 재밌는 장난감처럼 갖고 놀 수 있도록 함이 목적이다. 본 챕터에서는 datatoys 패키지를 활용하여 데이터를 주무르는 방법을 배워보도록 하자.
```{r}
library(datatoys)
```
datatoys패키지에서 제공하는 데이터셋에 대한 자세한 정보는 [문서](https://www.statgarten.com/datatoys/reference/index.html)를 통해 확인할 수 있다. 또는 `?`를 활용해 다음 `?accident`과 같이 확인할 수 있다.
## 데이터프레임 이해
이미지, 영상, 문서 같이 여러가지 형태의 데이터가 있지만, **우리는 직사각형처럼 생긴 데이터(데이터프레임)에 초점**을 맞출 것이다. 데이터프레임은 행과 열로 이루어진 표 형태의 데이터이다. 행은 관측치를, 열은 변수를 의미한다.
```{r message=FALSE}
library(datatoys)
library(dplyr)
str(restaurant)
```
`restaurant` 데이터는 137개의 관측치와 9개의 변수로 이루어져 있다. 각 행은 경기도 한 음식점을 의미하고, 열은 음식점의 정보를 의미한다. 시군명, 음식점명, 맛집 전화번호, 대표 음식명, 소재지 우편번호 등의 정보를 제공한다.
## 음주운전 데이터 주무르기
`datatoys` 패키지에는 `drunkdrive`라는 데이터셋이 있다. 이 데이터는 경찰청에서 음주운전 적발기록을 통한 음주예방을 위해 경찰처에서 공개한 자료로 성별, 적발횟수, 나이, 알콜농도, 측정일시, 관할경찰서 등의 정보를 제공한다. `head()` 함수를 통해 어떤 모양으로 생긴 데이터인지부터 확인해보자.
```{r}
head(drunkdrive)
```
데이터를 주무르기 위한 많은 패키지가 있지만, 가장 많은 사람들이 사용하는 `dplyr`라는 최적의 패키지가 있다. 이번 챕터에서는 `dplyr`에서 가장 많이 사용되는 몇가지 함수를 배워보도록 하자.
```{r}
library(dplyr)
```
### select()
`drunkdrive`는 총 8가지 변수를 갖고 있다. **너무 많은 데이터들에 압도당하지 않으려면 필요한 변수만 선택(select) 할 필요**가 있다. select 함수는 데이터셋에서 필요한 변수(열)만을 선택할 때 사용한다. 성별, 적발횟수, 나이, 알콜농도 열만 선택해보자.
```{r}
drunkdrive %>% select(성별, 적발횟수, 나이, 알콜농도)
```
데이터가 훨씬 간결해졌다. `:`를 사용하면 범위를 지정할 수도 있다. 동일한 결과를 보여준다.
```{r}
drunkdrive %>% select(성별:알콜농도)
```
때로는 필요한 변수를 제외하고 싶을 때도 있다. 이때는 `-`를 사용하면 된다. 성별, 적발횟수, 나이, 알콜농도 열을 제외하고 보고 싶다면 다음과 같이 사용하면 된다.
```{r}
drunkdrive %>% select(-성별, -적발횟수, -나이, -알콜농도)
```
때론 데이터 열의 순서를 바꾸고 싶을 때가 있다. `everything()` 함수를 함께 활용하면 데이터셋의 남은 모든 열을 선택할 수 있다.
```{r}
drunkdrive %>% select(알콜농도, 적발횟수, 나이, everything())
```
datatoys 패키지에는 장소별 원인별 화재통계를 제공하는 `fire`이라는 데이터셋이 있다. 데이터는 아래와 같은 열로 구성되어 있다.
```{r}
str(fire)
```
`starts_with()`와 `ends_with()` 함수를 함께 활용하면, 특정 문자열로 시작하는 열이나 특정 문자열로 끝나는 열을 선택할 수 있다. `장소`로 시작하는 열을 선택해보자.
```{r}
fire %>% select(starts_with("장소"))
```
`분류`로 끝나는 열을 선택해보자.
```{r}
fire %>% select(ends_with("분류"))
```
`contains()` 함수를 사용하면 특정 문자열을 포함하는 열을 선택할 수 있다. `요인`이라는 문자열을 포함하는 열을 선택해보자.
```{r}
fire %>% select(contains("요인"))
```
`select()`함수는 '열'을 선택할 때 사용한다. 만약 '행'을 선택하고 싶다면 `slice()` 함수를 사용할 수 있다. `slice()` 함수는 데이터셋에서 특정 행을 선택할 때 사용한다. 1, 3, 5, 7, 9번 행을 선택해보자.
```{r}
drunkdrive %>% slice(1, 3, 5, 7, 9)
```
연결된 행을 선택하기 위해서는 `:`를 활용할 수 있다.
```{r}
drunkdrive %>% slice(1:5)
```
### arrange()
뭐든 비교하고 줄세우는건 재밌다. 우리가 그 대상이 아니라면 말이다. `arrange()` 함수를 사용하면 데이터를 쉽게 정렬할 수 있다. `arrange()` 함수는 데이터셋에서 특정 열을 기준으로 정렬할 때 사용한다. `drunkdrive`데이터셋은 음주운전 적발자의 정보를 담고 있다. 나이를 기준으로 정렬하면 어떤 결과가 나올까?
```{r}
drunkdrive %>% arrange(나이)
```
Outlier다! Outlier를 발견했다. -77살은 존재할 수 없는 나이다. -77살을 제외하고 보면 2022년 7월 원주에서 적발된 14살이 가장 어린 나이다. `arrange()`함수는 기본적으로 작은수부터 정렬한다. `desc()`를 함께 사용하면 큰 수부터 정렬할 수 있다. 알콜농도가 높은 순서대로 정렬해보자.
```{r}
drunkdrive %>% arrange(desc(알콜농도))
```
2022년 7월 서울시 서초구에서 적발된 67세 남성의 알콜농도는 0.93으로 가장 높았다. 나이가 어린 순으로 정렬하면서 만약 나이가 같은 경우 알콜농도가 높은 순서대로 정렬하고 싶으면 다음과 같이 활용할 수 있다.
```{r}
drunkdrive %>% arrange(나이, desc(알콜농도))
```
### filter()
그렇다면 음주측정을 거부한 사람들이 있을까? 조건에 맞는 행만 뽑아내고 싶을 때는 `filter()` 함수를 사용해보자. `filter()` 함수는 조건이 TRUE인 데이터만을 선택할 때 사용한다. `측정`이라는 열에서 `거부`인 데이터만 뽑아보자.
```{r}
drunkdrive %>% filter(측정 == "거부")
```
남자(또는 여자)만 뽑아낼 수도,
```{r}
drunkdrive %>% filter(성별 == "남자")
```
미성년자만 뽑아낼 수도 있다. 어른들이 보다 관심을 가지고 돌봐줘야 할 아이들이 무려 148명이나 존재한다.
```{r}
drunkdrive %>% filter(나이 < 20)
```
`is.na()` 함수를 사용해 결측치(NA)를 뽑아낼 수 있다. NA는 Not Available의 약자로 결측치를 의미한다.
```{r}
drunkdrive %>% filter(is.na(측정일시))
```
하지만 대부분의 상황에서는 결측치를 찾아내기 보다. 결측치를 제거하는데 더 자주 사용된다. 결측치를 제거하고 싶다면 `!is.na()`를 사용하면 된다.
```{r}
drunkdrive %>% filter(!is.na(측정일시))
```
기본적으로 `filter()`의 인자들은 'and' 조건으로 연결된다. 즉, 남자이면서 미성년자인 데이터를 뽑아내고 싶다면 다음과 같이 활용하면 된다.
```{r}
drunkdrive %>% filter(성별 == "남자", 나이 < 20)
```
만약 'or' 조건으로 연결하고 싶다면 `|`를 사용하면 된다. `|`는 or을 의미한다. `|`를 사용하면 남자**이거나** 미성년자인 데이터를 뽑아낼 수 있다.
```{r}
drunkdrive %>% filter(성별 == "남자" | 나이 < 20)
```
### mutate()
`mutate()` 함수는 데이터셋에 새로운 열을 추가할 때 사용한다. `mutate()` 함수는 기존의 열을 사용해 새로운 열을 만들 때 사용한다. `mutate()` 함수는 항상 데이터프레임의 마지막 열에 새로운 열을 추가한다. `datatoys` 패키지의 `tuition` 데이터셋은 한국장학재단에서 제공하는 매년 4월 대학정보공시 기준의 대학별 입학정원, 평균입학금, 평균등록금 정보 등을 제공한다. 먼저 `select()` 함수를 통해 필요한 데이터를 뽑고, `mutate()` 함수를 사용해 한 학기당 등록금을 계산해보자. 한 학기당 등록금은 평균 등록금을 2로 나눈 값이다.
```{r}
tuition %>%
select(대학명, 평균등록금.원.) %>%
mutate(한학기당등록금 = 평균등록금.원. / 2)
```
또는 '원' 단위의 등록금을 '만원' 단위로 바꿔 볼 수도 있다.
```{r}
tuition %>%
select(대학명, 평균등록금.원.) %>%
mutate(평균등록금_만원 = 평균등록금.원. / 10000) %>%
arrange(desc(평균등록금_만원))
```
한국공학대학교의 평균 등록금이 연간 903만원으로 가장 높다. 순위를 표시하고 싶으면 `row_number()` 함수를 사용하면 된다. `row_number()` 함수는 데이터프레임의 행 번호를 표시한다. 높은 순위부터 순서대로 표시하고 싶다면 `desc()` 함수를 사용하면 된다.
```{r}
tuition %>%
select(대학명, 평균등록금.원.) %>%
mutate(평균등록금_만원 = 평균등록금.원. / 10000) %>%
arrange(desc(평균등록금_만원)) %>%
mutate(순위 = row_number(desc(평균등록금_만원)))
```
#### ifelse()와 함께
`ifelse()` 함수는 조건에 따라 다른 값을 반환한다. `ifelse()` 함수는 `mutate()` 함수와 함께 사용하면 특정 조건에 따라 다른 값을 가지는 새로운 열을 만들 수 있다. `ifelse()` 함수는 다음과 같이 사용한다.
```{r}
tuition %>%
select(대학명, 평균등록금.원.) %>%
mutate(백만원 = ifelse(평균등록금.원. >= 4000000, "400만원 이상", "400만원 미만"))
```
`ifelse()` 함수의 첫 번째 인자는 조건이다. 조건은 `평균등록금.원. >= 4000000`으로, 평균 등록금이 400만원 이상인지 아닌지를 판단한다. 두 번째 인자는 조건이 참일 때 반환할 값이다. 세 번째 인자는 조건이 거짓일 때 반환할 값이다. `ifelse()` 함수는 조건이 참일 때와 거짓일 때 반환할 값의 자료형이 같아야 한다.
### rename()
`rename()` 함수는 열 이름을 바꿀 때 사용한다. `평균등록금.원.`이란 뭔가 마음 한구석 불편한 이름을 `평균등록금_원`으로 바꿔보자.
```{r}
tuition %>%
select(대학명, 평균등록금.원.) %>%
rename(
대학이름 = 대학명,
평균등록금_원 = 평균등록금.원.
)
```
### group_by()
`group_by()` 함수는 데이터를 그룹으로 나눌 때 사용한다. `group_by()` 함수는 많은 경우 `summarise()` 함수와 함께 사용한다. `summarise()` 함수는 그룹별로 요약 통계량을 계산한다. 각 지역별로 평균 등록금을 계산해보자.
```{r}
tuition %>%
group_by(지역별) %>%
summarise(평균등록금_원 = mean(평균등록금.원.)) %>%
arrange(desc(평균등록금_원))
```
`n()` 함수를 활용하면 그룹별로 데이터의 개수를 계산할 수 있다. 각 지역별로 몇 개의 대학이 있는지 알아보자.
```{r}
tuition %>%
group_by(지역별) %>%
summarise(대학수 = n()) %>%
arrange(desc(대학수))
```
## 데이터라는 것이 폭발한다. 합쳤을 때.
실제 데이터 분석을 할 때 한가지 데이터만으로 분석이 진행되는 경우는 거의 없다. 일반적으로 우리가 데이터에서 답을 찾아내기 위해서는 여러 데이터들을 합쳐야 하는 경우가 많다. 이렇게 관계가 있는 데이터들을 관계형 데이터라고 하는데 각 개별 데이터셋 끼리의 관계가 중요하기 때문이다. 이 관계는 주로 키(key) 값으로 연결된다. 키는 각 데이터프레임을 연결하는데 사용되는 변수(열)을 뜻한다. 키는 주민등록번호, 학번, 이름 등이 될 수 있다. 키를 사용하여 데이터프레임을 연결하는 것을 결합(join)이라고 한다. 결합은 `dplyr` 패키지의 `inner_join()`, `left_join()`, `right_join()`, `full_join()` 함수를 사용한다.
### left_join()
`left_join()` 함수는 두 데이터프레임을 합칠 때 사용한다. `left_join()` 함수는 왼쪽 데이터프레임의 모든 행을 포함하는 데이터프레임을 만든다. 위에서 설명한 다른 join방법들이 있지만 별다른 이유가 없다면 `left_join()` 함수를 사용하자. 이는 왼쪽의 모든 관측값을 보존한다.
```{r}
성적 <- tibble(
이름 = c("철수", "영희", "영수"),
토익 = c(900, 800, 700)
)
개인정보 <- tibble(
이름 = c("영수", "영희"),
고향 = c("부산", "정읍"),
나이 = c(20, 21)
)
```
먼저 `tibble()` 함수를 사용해 새로운 데이터프레임을 2가지 만들었다. `성적` 데이터프레임은 이름과 토익 점수를 담고 있다. `개인정보` 데이터프레임은 이름, 고향, 나이를 담고 있다. `left_join()` 함수를 사용해 두 데이터프레임을 합쳐보자.
```{r}
성적 %>%
left_join(개인정보, by = "이름")
```
그 결과 `성적` 데이터프레임의 모든 행이 포함된 데이터프레임이 만들어졌다. `left_join()` 함수는 왼쪽 데이터셋의 값을 보존하기 때문에 왼쪽 데이터셋에 없는 키 값은 결측값으로 처리된다. `성적` 데이터프레임에 없는 이름인 "철수"의 고향과 나이는 결측값으로 처리된다. 만약 개인정보를 먼저 입력했다면 모든 관측값이 보존되었을 것이다.
```{r}
개인정보 %>%
left_join(성적, by = "이름")
```
`by = ` 인자는 결합할 때 사용할 키를 지정한다. 만약 지정되지 않았다면 두 데이터프레임에 공통된 열 이름을 키로 사용한다. `by = ` 인자에 키가 여러 개인 경우에는 `c()` 함수를 사용해 여러 개의 키를 지정한다. 만약 철수라는 동명이인이 여러명 존재한다고 해보자.
```{r}
성적 <- tibble(
이름 = c("철수", "영희", "영수", "철수", "철수"),
학번 = c(1, 2, 3, 4, 5),
토익 = c(900, 800, 700, 450, 100)
)
개인정보 <- tibble(
이름 = c("영수", "영희", "철수", "철수", "철수"),
학번 = c(3, 2, 1, 4, 5),
고향 = c("부산", "정읍", "서울", "평양", "뉴욕"),
나이 = c(20, 21, 22, 23, 24)
)
성적 %>%
left_join(개인정보, by = c("이름", "학번"))
```
결과적으로 이름과 학번이 모두 일치한 데이터를 join한다.
만약 키가 되는 열 이름이 서로 다른 경우에는 어떻게 할까? 그러 때는 `by = ` 인자에 왼쪽 데이터프레임의 키와 오른쪽 데이터프레임의 키를 지정할 수 있다.
```{r}
성적 <- tibble(
이름 = c("철수", "영희", "영수"),
토익 = c(900, 800, 700)
)
개인정보 <- tibble(
name = c("영수", "영희", "철수"),
고향 = c("부산", "정읍", "서울"),
나이 = c(20, 21, 22)
)
성적 %>%
left_join(개인정보, by = c("이름" = "name"))
```