1. tidyverse
tidy data를 다루기 앞서 tidyverse 패키지를 열어보자. dplyr, ggplot2와 모든 tidyverse 내의 패키지들은 tidy data로 잘 작동하도록 설계됐다.
그렇다면 무엇을 tidy data라 하는가?
Tidy data는 세 가지 기본 원칙을 따른다.
변수는 열로 정리되어야 합니다: 각 변수(예: 나이, 성별, 소득)는 데이터 집합의 열에 대응.
열은 변수의 유형에 따라 명확하게 정의되어야 한다.
관측치는 행으로 정리되어야 한다. 각 관측치(예: 개별 사람, 제품, 도시)는 데이터 집합의 행에 대응한다.
각 행은 고유한 식별자를 가지며, 다른 관측치와 구분될 수 있어야 한다.
측정값은 셀에 저장되어야 한다. 데이터 집합의 각 셀은 하나의 측정값을 포함해야 하고, 따라서 하나의 셀에는 단일 숫자, 문자열, 또는 논리값이 있어야 한다.
뭐 일단 그러하다고 한다.
너무 어려운 비교 말고 가장 단순한 비교로 이해를 돕자면,
library(tidyverse)
table1
table2
table1의 경우는 좋은 데이터로 tidy data라고 할 수 있다. 관측값이 행으로 정리되어 있고, 변수는 열로 정리되어 있다.
반면 table2는 별로 좋은 데이터라고 하기 힘들다. 변수로 구분되는 cases나 population이 열로 구분 되어있지 않고 행으로 구분되어 있는 것을 확인할 수 있다. 이러한 유형의 모든 데이터는 tidy data라고 할 수 없다.
table3
table4
tidyverse에는 다양한 table* 데이터를 학습용으로 제공하는데 table1과 각각은 table데이터를 비교해보면 무엇을 tidy data라고 하는지 이해하는 데에 도움이 될 것이다.
table1 %>%
mutate( rate = cases / population * 10000)
table1에 만명당 cases의 비율을 보이는 rate열을 추가하였다.
연도별로 cases의 합을 구해보자.
table1 %>%
group_by(year) %>%
summarise(totalclass = sum(cases))
country별로 시간이 지남에 따라 Cases의 수가 어떻게 변화했는지를 알아보자.
library(ggplot2)
ggplot(table1, aes(year, cases)) +
geom_line(aes(group= country), colour = 'grey50')+
geom_point(aes(colour = country))
위에서 다룬 tidy data의
tidy data의 원칙은 너무나 당연한 것 같아서 non tidy data를 다룰 일이 있을까 궁금할 수 있다.
하지만 안타깝게도 대부분의 데이터는 정리가 되어 있지 않은 '똥데이터'일 확률이 높다.
때문에 우리는 데이터를 처음 다룰 때 전처리과정을 거쳐야 한다.
첫 번째 단계는 항상 변수와 관측값이 무엇인지 파악하는 것이다.
때로는 이 작업이 쉬울 수도 있지만, 도무지 이해가 안 될 때엔 원래 데이터를 생성한 사람과 상의해야 할 수도 있다.
두 번째 단계는 두 가지 일반적인 문제 중 하나를 해결하는 것인데,
하나의 변수가 여러 열에 분산되어 있을 수 있고, ex) table2
하나의 관측값이 여러 행에 흩어져 있을 수 있다.
그럴 때 pivot_longer()와 pivot_wider()를 사용해 보자.
2. pivot_longer()
table4a
table4a를 보면 열의 이름이 변수명이 아니다. 이 데이터의 의미는 1999년도의 cases 수를 나타내는데 이런 경우 tidy data라고 할 수 없다.
table4a %>%
pivot_longer(c(`1999`,`2000`),
names_to = "year",
values_to = 'cases')
`1999`, `2000`열에 대해서 'year'이라는 변수를 새로만들어 넣어주고 각 변수들의 값들을 cases변수를 새로 만들어 넣어주는 과정이다.
변수명이 변수의 이름을 나타내는 tidy data가 됐다.
table4b
table4b는 각각 `1999`,`2000`에 해당하는 population 값을 보여준다. 이것 또한 table4a와 마찬가지로 tidy data라고 할 수 없을 것이다. 조금씩 이해가 되지?
아까처럼 pivot_longer()를 사용해보자.
table4b %>%
pivot_longer(c(`1999`,`2000`),
names_to = 'year',
values_to = 'population')
3. pivot_wider()
table2
table2를 보면 변수명 'year'은 실제 변수명을 의미하지만 type과 count는 실제 변수가 가져야 할 이름을 담고 있지 않다.
table2 %>%
pivot_wider(
names_from = type,
values_from = count)
- Exercise
- why does this code fail?
table4a %>%
pivot_longer(c(1999, 2000),
names_to = "year",
values_to = "cases")
table4a에서 featrue names는 `1999`, `2000`은 character type인데, longer할 변수명 설정을 위해 c로 묶은 1999, 2000은 numeric으로 실제 변수명 dtype과 다르기 때문이다.
오류에서도 확인할 수 있다.
-What would happen if you widen this table? Why? How could you add a new column to uniquely identify each value?
people <- tribble(
~name, ~key, ~value,
#-----------------|--------|------
"Phillip Woods", "age", 45,
"Phillip Woods", "height", 186,
"Phillip Woods", "age", 50,
"Jessica Cordero", "age", 37,
"Jessica Cordero", "height", 156
)
people %>% pivot_wider(names_from = name, values_from = value)
단순히 데이터를 보고 Phillip Woods가 age를 두 개 가지니까 name을 피쳐로 올리고 value는 value피쳐에 해당하는 값을 이용해야겠다고 생각하고 pivot_wider를 짜봤다.
원하는 대로 wider가 되지 않았고 그 이유는 value에 대해서 고유하지 않다는 것이었다. 다시 데이터를 살펴보니 같은 name의 같은 key에 대해서 다른 value를 가진 observation이 있었다. 때문에 name, key별 value에 대한 관측값이 구분되게 하기 위해 row_number() 함수로 반환된 값인 obs 변수를 데이터에 mutate한 후 다시 데이터를 살펴봤다.
people2 <- people %>%
group_by(name, key) %>%
mutate(obs = row_number())
이제 obs가 추가된 people2를 pivot_wider해보면
pivot_wider(people2,
names_from="name",
values_from = "value")
또 다른 방법으로는
people %>%
distinct(name, key,.keep_all = TRUE) %>%
pivot_wider(names_from = name, values_from = value)
people 데이터에서 name, key에 대해서 중복된 row를 제거하는 함수인 distinct()를 사용하는데 .keep_all로 모든 변수를 살리고 그 데이터에 대해서 pivot_wider를 사용하는 것이다.
결측값이 보이지 않는다. 하지만 모든 상황에서 이렇게 막 결측치를 무시하는 행위는 위험할 수 있다.
- Tidy the simple tibble below. Do you need to make it wider or longer? What are the variables?
preg <- tribble(
~pregnant, ~male, ~female,
"yes", NA, 10,
"no", 20, 12
)
tidy하지 못한 데이터다. male, female의 value는 pregnant한 사람의 count이므로, 변수명은 변수의 real name이 아니다.
pivot_long()을 사용해서 male과 female을 sex feature를 만들어서 tidy하게 만들고싶다. 물론 기존의 value들은 count라는 이름을 가진 feature로 들어가는게 data를 tidy하게 만드는 방향으로써 올바를 것이다.
preg_tidy <- preg %>%
pivot_longer(c('male','female'),
names_to = 'sex',
values_to ='count')
preg_tidy
꽤 tidy해 졌다. 하지만 불필요한 행이라고 생각되는 count가 NA인 행을 삭제해야 마음이 편해질 것 같다.
preg_tidy <- preg %>%
pivot_longer(c('male','female'),
names_to = 'sex',
values_to ='count',
values_drop_na = TRUE)
preg_tidy
마음이 편해졌다고 할 수 있겠다.
4. Separate
table3
rate 안에 있는 value는 두 가지 변수로 존재할 수 있다. 한 변수에는 한 가지 정보만 있을 때 tidy하다고 할 수 있을 것이다.
table3 %>% separate(rate,
into = c('cases','population')
)
마음이 편해졌다.
- sep =
기본적으로 separate() 함수는 숫자나 문자가 아닌 값을 볼 때마다 값을 분리해 준다. 사용자가 구분자를 특정하여 함수를 사용할 수도 있다.
table3 %>% separate(rate,
into = c('cases','population'),
sep = '/'
)
- convert =
separate()을 사용하여 반환된 결과에서 우리는 분리된 변수가 가진 value들의 데이터타입에 주목할 필요가 있다.
int가 아닌 character이다. 우리는 이러한 상황에서 convert parameter를 사용할 수 있다.
table3 %>% separate(rate,
into = c('cases','population'),
sep = '/',
convert = TRUE
)
convert = TRUE를 사용하면 더 나은 데이터 분리를 하는데에 도움이 될 수 있다.
- sep = n
table3 %>%
separate(year, into = c('century','year'), sep = 2)
sep 구분자에 숫자를 명시하면 n번째까지 한 묶음으로 해서 value들을 잘라준다.
5. unite()
table5
century와 year을 합치고 싶을 수 있다. 이런 경우도 있을 수 있잖아~
table5 %>% unite(new,
century, year, sep = ''
)
짜잔. 구분자에 특수한 기호를 넣는다면 님이 예상하는 대로 결과가 나올 것이다. rate처럼 ㅇㅋㅇㅋ?!
- Missing values
dataset에 missing value를 보이는 방법은 두 가지 정도로 볼 수 있다.
하나는 명시적으로 NA값을 노출시키는 방법, 또 다른 방법으로는 간단하게 그냥 해당하는 데이터를 명시하지 않는 방법이다.
stocks <- tibble(
year = c(2015, 2015, 2015, 2015, 2016, 2016, 2016),
qtr = c( 1, 2, 3, 4, 2, 3, 4),
return = c(1.88, 0.59, 0.35, NA, 0.92, 0.17, 2.66)
)
stocks
위 데이터셋에는 NA값이 2개다. 님이 그랬던 것처럼 나 또한 1개 아니야라고 했지만, 조금만 합리적으로 생각을 하면서 데이터를 관찰하면 16년에 1분기 데이터가 없는 것을 확인할 수 있다. 이러한 예가 데이터에서 NA값을 보이는 두 번째 방법이다.
implicit values를 explicit하는 방법을 알아보자.
stocks %>%
pivot_wider(
names_from = year,
values_from = return
)
명시되지 않았던 16년도 1분기 NA값이 보인다.
stocks %>% pivot_wider(names_from = year, values_from = return) %>%
pivot_longer(col = c('2015','2016'),
names_to = 'year',
values_to = 'return',
values_drop_na = TRUE)
명시된 NA값이 존재하지 않는 데이터셋을 만들었다.
- complete()
tidy data에서 NA값을 명시적으로 만드는 중요한 tool인 complete() 함수를 공부해 보자.
stocks %>%
complete(year, qtr)
complete함수는 명시한 변수들에 대해서 모든 조합에 대한 데이터를 반환해 준다. 때문에 complete함수는 관측치 자체가 존재하지 않는 NA값을 찾아서 명시하고 싶을 때 유용하다.
- fill()
때때로 데이터값이 누락됐을 때, 그 값이 앞전의 데이터와 같은 경우가 있다.
treatment <- tribble(
~ person, ~ treatment, ~response,
"Derrick Whitmore", 1, 7,
NA, 2, 10,
NA, 3, 9,
"Katherine Burke", 1, 4
)
treatment는 person의 치료 회차를 의미하고 response는 반응 횟수를 의미할 것이다. 그렇다면 index2,3에는 index 1의 person value인 'Derrick Whitmore'가 들어가는 것이 옳은 판단일 것이다. 이런 경우 사용하는 함수이다.
treatment %>%
fill(person)
- who dataset
who
who dataset을 살펴보자. 우선 country, iso2, iso3는 국가를 의미하는 column 같다. year은 변수이고, new_sp..는 뭔지 모르겠지만 변수명은 아니고 어떤 변수의 value일 것 같다. 왜냐하면 NA값이 너무 많음.
who1 <- who %>%
pivot_longer(cols = new_sp_m014:newrel_f65,
names_to = 'key',
values_to = 'cases',
values_drop_na = TRUE
)
우선 pivot_longer()를 사용해서 new머시기 값들을 key라는 변수를 만들어 넣어주고 기존의 new머시기들이 갖고 있던 values들을 cases 변수를 또 새로 만들어 넣어주었다. 지저분했던 NA값들은 values_drop_na 로 날려줬다.
이쯤 돼서 key변수에서의 value에 대해서 짚고 넘어가자.
new뒤에 처음 두 글자는 결핵의 유형을 의미한다고 한다.
rel은 재발
ep는 폐 외 결핵
sn은 진단될 수 없는 결핵
sp는 진단될 수 있는 결핵
여섯 번째 글자는 결핵환자의 성별을 의미한다.
나머지 숫자는 환자의 연령대를 제공한다. ex) 014 = 0-14세 , 65 = 65세 이상
눈썰미가 좋은 분들은 눈치를 채셨겠지만, 이제 key 값에서 이름을 조금 수정해야 할 부분이 있다. 왜냐하면 현재 column value의 name format은 일관성이 부족하다. 왜냐하면 대부분 new_sp, new_ep 형식의 이름을 갖고 있지만 재발의 경우인 rel은 new_rel이 아닌 newrel의 형식을 띠고 있기 때문이다.
who2 <- who1 %>%
mutate(key = stringr::str_replace(key, 'newrel','new_rel'))
나중에 배울 패키지지만 짧게 다뤄보자면 stringr 패키지의 str_replace함수는 특정 벡터에서 문자열을 원하는 문자열로 바꿔주는 기능을 가지고 있다. 즉 위 코드를 진행하면 newrel 부분이 new_rel로 변경될 것이다.
who3 <- who2 %>%
separate(key, c('new','type','sexage'), sep = '_')
who3
앞에서 공부를 제대로 했다면 이 코드에 대한 이해는 쉬울 것이라 생각한다.
이제 우리가 궁금할만한 feature들만 select() 함수를 사용해서 추출해 보자.
who4 <- who3 %>%
select(-new, -iso2, -iso3)
who5 <- who4 %>%
separate(sexage,
c('sex','age'),
sep = 1
)
who5
tidy한 데이터 완성.
'R > Exploratory data analysis' 카테고리의 다른 글
R for Data Science:: string (0) | 2023.06.16 |
---|---|
R for Data Science:: Relational data (0) | 2023.06.10 |
R for Data Science::tibbles and data import (0) | 2023.06.08 |
R for Data Science::transformation 3 ; Exercise 5.7 (1) | 2023.06.08 |
R for Data Science::transformation2 ; grouped (0) | 2023.06.07 |