-
Wide Form Vs Long Form
데이터 공부하고 전처리를 하다 보면 wide form 데이터, long form 데이터에 대해 들어본 적이 있을 것이다. 필자는 늘 궁금점이 있었다. 도대체 보기 편한 와이드형 데이터를 왜 보기에 불편한 롱형 데이터로 바꾸어 처리하는 것인지, 언제, 어떤 경우에 데이터를 변환해야 하는지 등에 의문이 있었다. 많은 책에서 wide form 데이터를 long form 형식의 데이터로 변환하는 방법에 대해서는 자세하게 설명하고 있지만, 언제, 어떤 경우에 데이터 구조를 변환해야 하는지에 대한 이유를 피부에 와 닿게 설명해 주는 글은 보지 못했다. 이번 글은 왜 데이터들의 구조와 형식을 wide에서 long form 형태로 변환해야 하는지 그 이유에 대해 이론이 아닌 사례 위주로 제시하여 데이터 구조에 대한 본능적인 감각을 키우기 위해 작성한 글이다.
다음은 성적 데이터로 우리가 익숙하게 보아온 데이터이다. 이러한 형태의 데이터를 wide form 형(이하 가로형)의 데이터라고 한다. 사람이 보기 좋도록 학생의 성적(관측치)을 한 행으로 표현하고, 각 열은 특정 과목(변수)에 대한 점수가 들어가는 형식의 데이터이다.
이름 국어 영어 수학 홍길동 80 85 90 임꺽정 90 95 100 심청이 70 75 80 이도령 60 65 70 일단 가로형 데이터를 세로형 데이터로 변환해 보자.
세로형 데이터에서는 “국어”, “영어”, “수학”이던 변수 이름이 없어지고 그 대신 “과목”이라는 변수가 생기고 원래의 변수 이름은 “과목” 변수의 측정값으로 입력이 되었음을 알 수 있다. 이런 형식의 데이터를 세로형 데이터라고 한다. 우리에게 이런 데이터는 불편하다. “임꺽정”의 점수를 알려면 세 개의 행에 걸쳐 있는 데이터를 보아야 하고, 임꺽정이와 홍길동의 “수학” 점수를 비교해 보려면 한참을 찾아 보아야 한다. 하지만 컴퓨터는 사람과 다르게 세로형 데이터를 더 선호한다고 한다. 왜 그럴까?이름 과목 점수 홍길동 국어 80 홍길동 영어 85 홍길동 수학 90 임꺽정 국어 90 임꺽정 영어 95 임꺽정 수학 100 심청이 국어 70 세로형 데이터는 왜 필요한가?
그렇다면, 어떤 처리를 하는데 가로형 데이터보다 세로형 데이터가 유리한 것인지 생각해 보자. 위의 데이터는 가로형, 세로형 데이터의 특성을 이해하기 쉽게 예를 든 데이터이고, 여기서는 몇가지 예와 실습을 위해 일반적으로 많이 알려진 iris 데이터로 설명하고자 한다. iris 데이터는 붓꽃의 품종별 꽃받침의 길이와 너비, 꽃잎의 너비와 길이를 조사한 데이터이다. iris 데이터의 구조는 가로형 데이터로 head를 보면 다음과 같다.
Sepal.Length Sepal.Width Petal.Length Petal.Width Species 5.1 3.5 1.4 0.2 setosa 4.9 3.0 1.4 0.2 setosa 4.7 3.2 1.3 0.2 setosa 4.6 3.1 1.5 0.2 setosa 5.0 3.6 1.4 0.2 setosa 먼저 iris 품종별 꽃받침 길이(Sepal.Length)를 박스플롯으로 그려서 비교하는 단순 작업을 해보자. 이런 그래프를 그리는 것은 가로형이나 세로형 형식과 관계 없이 어렵지 않다. 아래 소스 코드를 보자. ggplot의 aes 매핑 함수를 보면 품종별로(x = Species), 꽃받침 길이(y = Sepal.Length)의 분포를 그리되 품종별로 색깔을 다르게 표현하라고(col =Species)하였다. 또 그래프는 BoxPlot으로 그리고, 데이터의 위치를 점(geom_jitter)으로 표현해달라고 했다. 우리가 원하는 대로 그래프를 그리면 아래와 같다.
ggplot(iris, aes(x = Species, y =Sepal.Length, col =Species)) + geom_boxplot() + geom_jitter() + ggtitle("Species 별 Sepal.Length의 분포(BoxPlot)")
다양한 그래프의 작성
그렇다면, 만일 품종별로 붓꽃의 모든 제원(Sepal.Length, Sepal.Width Petal.Length, Petal.Width)의 분포를 하나의 Boxplot으로 그려서 품종별로 꽃받침과 꽃잎의 길이와 너비가 어떻게 차이가 나는지를 한눈에 비교해 보고 싶은 경우에는 어떻게 그려야 할까? 아래 그림 같은 그래프를 그리고 싶을 때 말이다.
이런 형태의 그래프를 작성하기 위해서는 각 변수가 변수명으로서가 아닌 다른 변수의 값으로 표현돼야 한다. 위 성적 데이터 예에서 “국어”,“영어”,수학”이란 변수가 통합된 “과목”이란 이름의 새로운 변수가 되고, 원래의 변수명이 이었던 “국어”,영어”,“수학”이 “과목”이라는 새로 생긴 변수의 값으로 변환되듯이 말이다. 그래야 통합된 변수를 ggplot의 aes의 인자인 col이나 fill로 지정해서 그 변수별로 다양한 그래프를 그릴 수 있게 된다. 즉 가로형 데이터의 유사한 변수들을 통합한 새로운 변수명을 만들고 기존의 변수명은 새로 만든 변수의 값으로 입력한다. 이렇게 여러 변수들을 통합하여 새로운 이름의 변수로 만들고, 당초 가지고 있던 변수 이름을 통합된 변수의 값으로 정리한 형태가 Long form 형태의 데이터가 되는 것이다. wide form 형태의 원 iris 데이터를 long form 형태로 변환한 모습은 다음과 같은 모습이 된다.
Species new.feature value setosa Sepal.Length 5.1 setosa Sepal.Width 3.5 setosa Petal.Length 1.4 setosa Petal.Width 0.2 setosa Sepal.Length 4.9 setosa Sepal.Width 3.0 iris 데이터를 롬 폼으로 변환한 데이터를 보면 species를 뺀 기존의 변수 이름들이 사라지고 “new.feature”라는 새로운 변수와 “value”라는 변수가 새로 생겼음을 알 수 있다. 기존의 변수 이름은 new.feature라는 새로 생긴 변수의 값으로 들어가고 그 변수가 가지고 있던 관측값이 새로운 생긴 value라는 변수의 값들로 들어갔음을 알게 된다. 이렇게 변환된 롱폼의 데이터로는 다양한 그래프를 쉽게 그릴 수 있다. 다음은 붓꽃의 품종별 여러 제원의 분포를 한 그래프로 표현한 것이다.
pivot_longer(iris, cols = c(1:4), names_to = "new.feature", values_to ="value") %>% ggplot(aes(y = value, fill = new.feature)) + geom_density(aes(colours = new.feature, alpha = 0.8)) + coord_flip() + facet_grid(vars(Species), scales = "free_y") + ggtitle("붓꽃 품종별 꽃잎, 꽃받침 길이 및 넓이 분포(BoxPlot)") + theme(axis.text.x = element_blank())
## Warning in geom_density(aes(colours = new.feature, alpha = 0.8)): Ignoring ## unknown aesthetics: colours
이런 그래프가 가능한 이유는 new.feature라는 변수를 만들고 기존의 변수 이름을 new.feature의 값으로 변환한 다음 각 변수의 관측값들을 새로운 변수인 value로 통합한 세로 형식으로 구조를 변환했기 때문이다.
데이터 요약 및 변환
이번에는 iris 데이터를 요약하여 품종별 네 가지 제원(꽃받침과 꽃잎의 길이와 너비)에 대한 중앙값을 구하는 일을 한다고 가정 해보자. 이를 위해 group() 함수로 Species 별로 그룹화한 다음 summarise() 함수를 써서 네 가지 변수의 중앙값을 구했다.
iris %>% group_by(Species) %>% summarise(Petal.Length = median(Petal.Length), Petal.Width = median(Petal.Width), Sepal.Length = median(Sepal.Length), Sepal.Width = median(Sepal.Width)) %>% kable()
Species Petal.Length Petal.Width Sepal.Length Sepal.Width setosa 1.50 0.2 5.0 3.4 versicolor 4.35 1.3 5.9 2.8 virginica 5.55 2.0 6.5 3.0 이렇게 변수별로 median() 함수 등을 이용하면 변수별로 요약할 수 있다. 하지만 만일 변수의 숫자가 50개, 100개가 된다면 어떻게 요약할 것인가? 변수별로 median() 함수를 100번씩 써서 코딩하는 것은 현실적으로 곤란한 문제이다. 이런 경우 데이터를 세로형으로 구조를 바꾸어 원 변수 이름을 새로 생성한 변수(new.feature 같은)의 값으로 입력하면 group_by() 함수를 써서 한꺼번에 그룹화할 수 있고 summaise() 함수를 이용하여 요약할 수 있다. 마치 각각의 변수 이름이 new.feature의 범주 레벨이 된 것 같이 활용할 수 있는 것이다.
iris %>% pivot_longer(col = c(1:4), names_to ="new.feature", values_to = "value") %>% # 세로형으로 변환 group_by(Species, new.feature) %>% # 품종별 변수 종류별로 그룹화 summarise(mean = median(value)) %>% # 품종별 변수 종류별 평균을 구함 pivot_wider(id_cols = Species, names_from = new.feature, values_from = mean) %>% #가로형으로 kable()
Species Petal.Length Petal.Width Sepal.Length Sepal.Width setosa 1.50 0.2 5.0 3.4 versicolor 4.35 1.3 5.9 2.8 virginica 5.55 2.0 6.5 3.0 이번엔 방금 요약한 데이터를 이용하여 품종별 붓꽃의 네 가지 변수의 중앙값을 막대그래프로 그려보자.
iris %>% pivot_longer(col = c(1:4), names_to ="new.feature", values_to = "value") %>% group_by(Species, new.feature) %>% summarise(median = median(value)) %>% ggplot(aes(x=Species, y = median, fill = new.feature)) + geom_col(position = position_dodge()) + ggtitle("품종별 각 변수의 중앙값")
## `summarise()` has grouped output by 'Species'. You can override using the ## `.groups` argument.
가로형 세로형 데이터의 변환 방법
가로형 데이터를 세로형으로 또 세로형 데이터를 가로형으로 변환하는 것은 매우 자주 발생하는 작업이다. 이런 필요에 따라 R에서는 이를 위한 다양한 함수들이 존재한다. 대표적으로 가로형 데이터를 세로형 데이터로 변환하는 데 사용할 수 있는 함수는 shape2 패키지의 melt()함수와 tidyr 패키지지의 gather() 함수와 pivot_longer() 함수가 있다. 모든 함수에 다 익숙해질 필요는 없다고 생각한다. 코딩하는 사람에 따라 자기가 사용하기 익숙한 함수를 사용하기 때문에 다른 사람의 코드를 읽을 수는 있어야 하지만, 자기에게 익숙하고 사용하기 쉬운 변환하는 함수를 알고 숙달 시키면 될 것이다. 여기서는 최근에 가장 많이 사용되고 pivot_longer()함수를 이용하여 위에서 예를 든 성적 데이터를 세로형 데이터로 변환하는 예를 제시한다.
sungjuk_long <- sungjuk %>% pivot_longer(cols = c("국어", "영어", "수학"), names_to = "과목", values_to = "점수") sungjuk_long %>% head() %>% kable()
이름 과목 점수 홍길동 국어 80 홍길동 영어 85 홍길동 수학 90 임꺽정 국어 90 임꺽정 영어 95 임꺽정 수학 100 pivot_longer() 함수는 다음과 같이 간편하게 지정해서 사용해도 된다. 다만 새로 컬럼들이 조정되어 생기는 변수 이름은 디폴트로 설정된 “name”과 “value”로 만들어진다.
sungjuk %>% pivot_longer(cols = 2:4) # 또는 sungjuk %>% pivot_longer(국어:수학)
세로형 데이터를 가로형 데이터로 변환 할때는 pivot_wider() 함수를 사용한다. 이번에는 세로형 데이터인 sunjuk_long 데이터를 가로형 데이터로 바꿔보자.
sungjuk_wide <- sungjuk_long %>% pivot_wider(names_from = 과목, values_from = 점수) sungjuk_wide %>% head() %>% kable()
이름 국어 영어 수학 홍길동 80 85 90 임꺽정 90 95 100 심청이 70 75 80 이도령 60 65 70 데이터를 자유자재로 다루는 능력은 제빵사가 밀가루 반죽을 만든 후 원하는 빵을 굽기 위해 반죽을 여러 가지 모양으로 성형하는 것과 같은 것이다. 원재료는 같은데, 반죽 모양만 바꾸어 여러 가지 빵을 만들듯이 분석의 필요에 따라 원하는 폼으로 데이터를 주무르는 것은 데이터 분석을 위해 꼭 필요한 분석가의 능력이다.
'데이터' 카테고리의 다른 글
시계열 분석 시리즈 II (Plots, Decomposition, Autocorrelation) (0) 2024.03.03 시계열 분석 시리즈 I (Tsibble 객체 다루기) (0) 2024.03.03 Association Rule 분석(장바구니 분석) (2) 2024.02.04 k-mean Clustering (2) 2024.01.21 chatGPT-4로 하는 데이터분석 (1) 2024.01.07