10.9 데이터 프레임의 재구성

https://www.tutorialspoint.com/r/r_data_reshaping.htm

R에서 데이터 프레임의 재구성(reshaping)은 데이터가 행과 열로 조직된 방식을 변경하는 것입니다.

R에서 대부분의 데이터 처리는 입력 데이터를 데이터 프레임으로 사용하여 수행됩니다. 데이터 프레임의 행과 열에서 데이터를 추출하는 것은 쉽지만 데이터 프레임을 원래의 형식과 다른 형식의 데이터 프레임으로 변형할 필요가 있는 경우가 있습니다.

R에는 데이터 프레임에서 열의 분할(split)과 병합(merge) 그리고 행을 열로 변경하는 많은 함수들이 있습니다.

10.9.1 데이터 프레임의 열과 행의 결합

cbind() 함수를 사용하여 여러 벡터를 결합하여 데이터 프레임을 만들 수 있고, 또한 rbind() 함수를 사용하여 두 개의 데이터 프레임을 병합할 수 있음을 우리는 이미 살펴 보았습니다. 이러한 행과 열의 결합을 통해 데이터 프레임을 재구성할 수 있습니다.

10.9.2 데이터 프레임의 병합

merge() 함수를 사용하여 서로 다른 구조를 갖는 두 개의 데이터 프레임을 공통을 컬럼을 이용하여 병합할 수 있습니다. 데이터 프레임은 병합이 발생하는 두 데이터 프레임에는 공통의 열이 있어야 합니다.

merge() 함수의 형식은 다음과 같습니다.

merge(x, y, by = intersect(names(x), names(y)), by.x = by, by.y = by, all = FALSE, all.x = all, all.y = all, sort = TRUE, suffixes = c(".x",".y"), no.dups = TRUE, incomparables = NULL, ...)

  • x, y : 병합할 데이터 프레임
  • by, by.x, by.y : 병합에 사용할 컬럼을 지정합니다.
  • all : 기본값은 FALSE. by에 지정된 공통 컬럼에 부합하는 행만 선택한다.
  • all.x, all.y : TRUE 값으로 지정하면, 병합할 조건과 일치하지 않더라도 모든 x행(또는 y 행)을 추가한다. FALSE이면 병합 조건과 일치하는 행만 추가한다.

예를 들어 학생들의 중간고사 성적과 기말고사 성적을 별도의 데이터 프레임으로 생성하였다고 생각해 보겠습니다. 학기말에 성적을 처리하려면 이 두 개의 데이터 프레임을 병합하여 하나의 데이터 프레임으로 만들어야 합니다.

# 두 개의 데이터 프레임이 있습니다.
mid <- data.frame(name = c("Kim", "Lee", "Park", "Choi", "Hwang"),
                  mid.score = c(95, 80, 88, 92, 76))
str(mid)
## 'data.frame':    5 obs. of  2 variables:
##  $ name     : chr  "Kim" "Lee" "Park" "Choi" ...
##  $ mid.score: num  95 80 88 92 76
final <- data.frame(name = c("Kim", "Lee", "Park", "Hong", "Choi"),
                  final.score = c(90, 88, 90, 87, 90))
str(final)
## 'data.frame':    5 obs. of  2 variables:
##  $ name       : chr  "Kim" "Lee" "Park" "Hong" ...
##  $ final.score: num  90 88 90 87 90
# 이제 두 개의 데이터 프레임으로 병합하겠습니다.
# 공통의 컬럼 : name 컬럼

# 두 개의 데이터 프레임에서 name 컬럼의 값이 일치하는 경우에만 병합합니다
grade.inner <- merge(mid, final, by = "name")      # 이러한 경우를 inner join 이라고 합니다.
grade.inner                                        # name 컬럼의 같이 일치하는 경우만 병합합니다.
##   name mid.score final.score
## 1 Choi        92          90
## 2  Kim        95          90
## 3  Lee        80          88
## 4 Park        88          90
# 중간고사를 본 모든 학생은 병합할 때 모두 포함시킵니다. 기말고사를 보지 않은 학생의 성적은 NA로 출력됩니다.
grade.left <- merge(mid, final, by = "name", all.x = TRUE)
grade.left                                         # 이러한 경우를 left join 이라고 합니다.
##    name mid.score final.score
## 1  Choi        92          90
## 2 Hwang        76          NA
## 3   Kim        95          90
## 4   Lee        80          88
## 5  Park        88          90
# 기말고사를 본 모든 학생은 병합할 때 모두 포함시킵니다. 중간고사를 보지 않은 학생의 성적은 NA로 출력됩니다.
grade.right <- merge(mid, final, by = "name", all.y = TRUE)
grade.right                                        # 이러한 경우를 right join 이라고 합니다.
##   name mid.score final.score
## 1 Choi        92          90
## 2 Hong        NA          87
## 3  Kim        95          90
## 4  Lee        80          88
## 5 Park        88          90
# mid와 final에 있는 모든 학생을 병합합니다. 중간고사나 기말고사를 보지 않은 학생의 성적은 NA로 출력됩니다.
grade.full <- merge(mid, final, by = "name", all = TRUE)     
grade.full                                         # 이러한 경우를 full join 이라고 합니다.
##    name mid.score final.score
## 1  Choi        92          90
## 2  Hong        NA          87
## 3 Hwang        76          NA
## 4   Kim        95          90
## 5   Lee        80          88
## 6  Park        88          90

만일 두 데이터 프레임의 공통 컬럼 이름이 다른 경우 by 대신에 by.xby.y로 각각 지정해 주면 됩니다. 예를 들어 mid 데이터 프레임의 공통 컬럼이 name이고 final 데이터 프레임의 공통 컬럼이 std_name인 경우 by 대신에 by.x = “name”by.y = “std_name”이라고 설정하면 됩니다.

또한, 공통 컬럼이 하나가 아닌 두 개인 경우by = c(col1, col2)와 같이 지정해 주면 됩니다.

10.9.3 Melting과 Casting

앞에서 살펴본 grade.full 데이터 프레임의 경우, name 컬럼, mid.score 컬럼 그리고 final.score 컬럼으로 구성이 되어 있습니다.

그런데 사실상mid.score컬럼도 score(점수) 컬럼이고, final.score 컬럼도 score(점수) 컬럼으로 볼 수 있습니다. 다만 mid.score는 시험의 종류(type)가 중간고사이고, final.score는 시험의 종류가 final이 되는 겁니다.

만일 중간고사, 기말고사 이외에도 수시시험이 있거나 출석 점수 등이 있다면 grade.full 의 데이터 프레임에는 그만큼의 열들이 추가되어야 할 것입니다. 그러나 수시시험 점수나 출석 점수도 결국은 점수(score)이며, 다만 시험의 종류(type) 만이 다를 뿐입니다.

이러한 관점에서 볼 때 grade.full과 같이 시험의 종류가 증가하면 데이터 프레임의 열이 증가하는 데이터 테이블을 넓은(wide-format) 데이터 프레임이라고 합니다. 반면에 시험의 종류를 type 컬럼으로 설정하고 시험 점수를 score 컬럼으로 설정한다면 데이터 프레임의 구조에서 보면 열이 늘어나는 것이 아니라 행이 추가될 뿐입니다. 이러한 데이터 프레임을 긴(long-format) 데이터 프레임이라고 합니다.

10.9.3.1 넓은 형식을 긴 형식으로 변환하기

지금의 예제에서와 같이 grade.full과 같은 넓은 데이터 프레임을 긴 데이터 프레임으로 변형하는데 이용하는 함수가 reshape 패키지에 있는 melt() 함수입니다.

# 패키지 불러오기
# install.packages("reshape")
library(reshape)
## 
## Attaching package: 'reshape'
## The following object is masked from 'package:dplyr':
## 
##     rename
## The following objects are masked from 'package:tidyr':
## 
##     expand, smiths
grade.molten <- melt(grade.full, id = c("name"))   # name 컬럼을 중심으로 long format으로 변환합니다.
grade.molten                                       # variable과 value 컬럼이 생성되었습니다.
##     name    variable value
## 1   Choi   mid.score    92
## 2   Hong   mid.score    NA
## 3  Hwang   mid.score    76
## 4    Kim   mid.score    95
## 5    Lee   mid.score    80
## 6   Park   mid.score    88
## 7   Choi final.score    90
## 8   Hong final.score    87
## 9  Hwang final.score    NA
## 10   Kim final.score    90
## 11   Lee final.score    88
## 12  Park final.score    90
str(grade.molten)                                  # variable 컬럼은 factor형 입니다.
## 'data.frame':    12 obs. of  3 variables:
##  $ name    : chr  "Choi" "Hong" "Hwang" "Kim" ...
##  $ variable: Factor w/ 2 levels "mid.score","final.score": 1 1 1 1 1 1 2 2 2 2 ...
##  $ value   : num  92 NA 76 95 80 88 90 87 NA 90 ...

grade.full 데이터 프레임을 name 컬럼을 중심으로 melt() 함수를 적용하면, 시험의 종류가 variable 컬럼으로 그리고 시험의 점수가 value 컬럼으로 정리가 됨을 알 수 있습니다.

이때 variable 컬럼에는 mid.scorefinal.score 등의 컬럼 이름이 문자열 값으로 입력되어 있습니다. 그리고 value 컬럼에는 점수가 데이터로 입력이 되어 있습니다. 특히 variable 컬럼은 요인(factor)형임을 주목하기 바랍니다.

이제 grade.molten 데이터 프레임은 넓은 형식에서 긴 형식의 데이터 프레임으로 재구성이 된 것입니다.

tidyr 패키지의 pivot_longer() 함수를 참고하기 바랍니다.

10.9.3.2 긴 형식을 넓은 형식으로 변환하기

한편 긴 형식의 grade.molten 데이터 프레임을 넓은 형식으로 변환할 수 있는데, 이때 reshape 패키지의 cast() 함수를 사용합니다.

다음은 grade.molten 데이터 프레임의 name 컬럼을 기준으로 variable 컬럼에 있는 데이터를 새로운 데이터 프레임의 컬럼으로 설정하고 그 값의 합(sum)을 새로운 컬럼의 데이터로 생성하도록 해 줍니다.

# 패키지 불러오기
# install.packages("reshape")
library(reshape)

grade.cast <- cast(grade.molten, name ~ variable, sum)   # name 컬럼을 중심으로 long format으로 변환합니다.
grade.cast
##    name mid.score final.score
## 1  Choi        92          90
## 2  Hong        NA          87
## 3 Hwang        76          NA
## 4   Kim        95          90
## 5   Lee        80          88
## 6  Park        88          90
str(grade.cast)
## List of 3
##  $ name       : chr [1:6] "Choi" "Hong" "Hwang" "Kim" ...
##  $ mid.score  : num [1:6] 92 NA 76 95 80 88
##  $ final.score: num [1:6] 90 87 NA 90 88 90
##  - attr(*, "row.names")= int [1:6] 1 2 3 4 5 6
##  - attr(*, "idvars")= chr "name"
##  - attr(*, "rdimnames")=List of 2
##   ..$ :'data.frame': 6 obs. of  1 variable:
##   .. ..$ name: chr [1:6] "Choi" "Hong" "Hwang" "Kim" ...
##   ..$ :'data.frame': 2 obs. of  1 variable:
##   .. ..$ variable: Factor w/ 2 levels "mid.score","final.score": 1 2
# grade.cast와 grade.full이 동일한 데이터 프레임인지 setequal() 함수로 확인합니다.
setequal(grade.cast, grade.full)           # TRUE를 반환하므로 동일한 데이터 프레임입니다.
## [1] TRUE

이제 grade.cast 데이터 프레임은 긴 형식에서 넓은 형식으로 재구성이 된 것입니다. 이 grade.cast 데이터 프레임을 앞에서 생성한 grade.full과 비교해 보면 동일한 데이터 프레임인 것을 알 수 있습니다.

tidyr 패키지의 pivot_wider() 함수를 참고하기 바랍니다.