[R data.table] 효율적인 데이터 재구조화 (Efficient reshaping using data.tables)
R 분석과 프로그래밍/R 데이터 전처리 2020. 10. 25. 23:59지난 포스팅에서는 R data.table에서 := 를 사용한 참조에 의한 얕은 복사(shallow copy)의 부작용과 copy() 함수를 사용한 깊은 복사(deep copy)에 대하여 알아보았습니다.
이번 포스팅에서는 R data.table 에서 melt() 와 dcast() 함수를 사용해서 효율적으로 재구조화 (efficient reshaping of R data.table) 하는 방법을 소개하겠습니다.
R reshape 패키지의 melt(), cast() 함수와 유사하므로 활용법에 있어서 어렵거나 특별한 것은 없습니다. 다만 R data.table은 재구조화의 과정이 내부적으로 전부 C 언어로 수행되므로 매우 빠르고 또 메모리 효율적입니다.
(1) data.table 을 녹여서 넓은 자료구조를 길게 (wide to long) 재구조화 해주는 melt() 함수
(2) data.table 을 주조하여 긴 자료구조를 넓게 (long to wide) 재구조화 해주는 dcast() 함수
(1) melt() : data.table 을 녹여서 넓은 자료구조를 길게 (wide to long) 재구조화 |
data.table 패키지를 불러오고, MASS 패키지에 내장되어 있는 Cars93 데이터프레임에서 행 1~5번 까지, 그리고 6개 칼럼만 선별해와서 예제로 사용할 간단한 data.table을 만들어보겠습니다.
library(data.table) library(MASS) DT <- data.table(Cars93[1:5, c("Model", "Type", "DriveTrain", "Length", "Width", "Weight")]) print(DT) # Model Type DriveTrain Length Width Weight # 1: Integra Small Front 177 68 2705 # 2: Legend Midsize Front 195 71 3560 # 3: 90 Compact Front 180 67 3375 # 4: 100 Midsize Front 193 70 3405 # 5: 535i Midsize Rear 186 69 3640 |
이제 위에서 만든 data.table DT를 melt() 함수를 사용하여 ID(id.vars) 변수는 모델("Model"), 차종("Type"), 동력전달장치("DriveTrain") 의 3개 변수로 하고, 측정값 변수(measure.vars)로는 길이("Length"), 폭("Width"), 무게("Weight")의 3개 변수를 variable, value 의 2개 변수로 녹여서(melting) 재구조화함으로써, 옆으로 넓은 형태를 세로로 긴 형태 (wide to long)의 data.table로 재구조화 해보겠습니다.
## -- 1. Melting data.tables (wide to long) DT_melt_1 <- melt(DT, id.vars = c("Model", "Type", "DriveTrain"), measure.vars = c("Length", "Width", "Weight")) ## By default, the molten columns are automatically named 'variable' and 'value'. print(DT_melt_1) # Model Type DriveTrain variable value # 1: Integra Small Front Length 177 # 2: Legend Midsize Front Length 195 # 3: 90 Compact Front Length 180 # 4: 100 Midsize Front Length 193 # 5: 535i Midsize Rear Length 186 # 6: Integra Small Front Width 68 # 7: Legend Midsize Front Width 71 # 8: 90 Compact Front Width 67 # 9: 100 Midsize Front Width 70 # 10: 535i Midsize Rear Width 69 # 11: Integra Small Front Weight 2705 # 12: Legend Midsize Front Weight 3560 # 13: 90 Compact Front Weight 3375 # 14: 100 Midsize Front Weight 3405 # 15: 535i Midsize Rear Weight 3640 str(DT_melt_1) # Classes 'data.table' and 'data.frame': 15 obs. of 5 variables: # $ Model : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 49 56 9 1 6 ... # $ Type : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 4 3 1 3 3 ... # $ DriveTrain: Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 2 2 3 ... # $ variable : Factor w/ 3 levels "Length","Width",..: 1 1 1 1 1 2 2 2 2 2 ... # $ value : int 177 195 180 193 186 68 71 67 70 69 ... # - attr(*, ".internal.selfref")=<externalptr> |
위의 str()함수로 각 변수의 데이터 형태를 보면 "variable" 변수는 요인형(Factor) 입니다. melt() 함수로 재구조화 시 "variable" 칼럼의 기본 설정값은 요인형(Factor) 인데요, 만약 요인형 말고 문자형(charactor) 으로 하고 싶다면 variable.factor = FALSE 로 매개변수를 설정해주면 됩니다.
## By default, 'variable' column is of type factor. ## Set variable.factor argument to FALSE if you like to return a character vector. DT_melt_2 <- melt(DT, id.vars = c("Model", "Type", "DriveTrain"), measure.vars = c("Length", "Width", "Weight"), variable.factor = FALSE) str(DT_melt_2) # Classes 'data.table' and 'data.frame': 15 obs. of 5 variables: # $ Model : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 49 56 9 1 6 ... # $ Type : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 4 3 1 3 3 ... # $ DriveTrain: Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 2 2 3 ... # $ variable : chr "Length" "Length" "Length" "Length" ... # <--- charactr vector # $ value : int 177 195 180 193 186 68 71 67 70 69 ... # - attr(*, ".internal.selfref")=<externalptr> |
만약 녹여서(melting) 길게(wide to long) 재구조화한 후의 "variable", "value" 변수 이름을 사용자가 지정해서 다른 이름으로 부여를 하고 싶다면 variable.name = "new_variable_name", value.name = "new_value_name" 처럼 매개변수에 새로운 칼럼 이름을 부여해주면 됩니다.
## Name the 'variable' and 'value' columns to 'measure' and 'val' respectively. DT_melt_3 <- melt(DT, id.vars = c("Model", "Type", "DriveTrain"), measures.vars = c("Length", "Width", "Weight"), variable.name = "measure", value.name = "val") head(DT_melt_3) # Model Type DriveTrain measure val # 1: Integra Small Front Length 177 # 2: Legend Midsize Front Length 195 # 3: 90 Compact Front Length 180 # 4: 100 Midsize Front Length 193 # 5: 535i Midsize Rear Length 186 # 6: Integra Small Front Width 68 |
(2) dcast() : data.table 을 주조하여 긴 자료구조를 넓게 (long to wide) 재구조화 |
위의 (1)번에서 세로로 길게 재구조화한 data.table을 원래의 옆으로 넓은 형태로 역으로 재구조화를 하고 싶으면 dcast() 함수를 사용하면 됩니다.
dcast(DT, ID1 + ID2 + ID3 ~ variable) 처럼 함수 형태의 구문을 사용합니다.
## -- 2. dcasting data.tables (long to wide) ## reverse operation of melting ## dcast uses formula interface. dcast(DT_melt_1, Model + Type + DriveTrain ~ variable) # Model Type DriveTrain Length Width Weight # 1: 100 Midsize Front 193 70 3405 # 2: 535i Midsize Rear 186 69 3640 # 3: 90 Compact Front 180 67 3375 # 4: Integra Small Front 177 68 2705 # 5: Legend Midsize Front 195 71 3560 |
만약 위의 (1)번에서 "variable"과 "value" 칼럼이름을 사용자가 지정해서 melt()를 수행했다면 dcast() 를 하여 역으로 넓게 재구조화하려고 할 때 사용자가 지정한 변수(variable)와 값(value)의 칼럼 이름을 사용해주면 됩니다.
위의 (1-3) 예에서 만든 DT_melt_3 이름의 data.table을 dcast()로 재구조화하려면
dcast(DT_melt_3, Model + Type + DriveTrain ~ measure, value.var = "val")
처럼 위 (1-3)에서 지정했던 변수(variable) 이름인 'measure', 값(value) 이름인 value.var = "val" 을 써주면 됩니다.
## 'value.var' denotes the column to be filled in with while casting to wide format. dcast(DT_melt_3, Model + Type + DriveTrain ~ measure, value.var = "val") # Model Type DriveTrain Length Width Weight # 1: 100 Midsize Front 193 70 3405 # 2: 535i Midsize Rear 186 69 3640 # 3: 90 Compact Front 180 67 3375 # 4: Integra Small Front 177 68 2705 # 5: Legend Midsize Front 195 71 3560
|
dcast() 함수에 집계함수(aggregation function)를 사용하여 ID 그룹별로 요약통계량을 계산한 결과를 재구조화하여 반환할 수도 있습니다.
위의 (1)에서 data.table을 길게 녹여서 재구조화했던 DT_melt_1에 대해서 차종(Type)을 기준으로 녹여서 만든 변수(variable)에 대해 평균(fun.aggregate = mean)을 집계하여 역으로 옆으로 넓게 재구조화한 data.table을 반환해보겠스니다.
## You can pass a function to aggregate by in dcast with the argument 'fun.aggregate. dcast(DT_melt_1, Type ~ variable, fun.aggregate = mean) # Type Length Width Weight # 1: Compact 180.0000 67 3375 # 2: Midsize 191.3333 70 3535 # 3: Small 177.0000 68 2705 |
fun.aggregate 는 fun.agg 로 줄여서 쓸 수 있으며, fun.agg 뒤에는 function(x) 로 해서 어떠한 R 함수도 써줄 수 있습니다. 아래 예에서는 x가 결측값인지(1) 아닌지(0) 여부에 대해서 합(sum)을 구하는 집계함수로서 fun.agg = function(x) sum(!is.na(x)) 를 써주었습니다. (즉, 결측값이 아닌 행의 개수)
그리고 subset 매개변수를 사용하면 dcast()의 대상이 되는 data.table에서 특정 조건을 만족하는 부분집합만 필터링해와서 dcast() 재구조화를 할 수도 있습니다.
## 'fun.agg' is the same with 'fun.aggregate' ## subset: Specified if casting should be done on a subset of the data. dcast(DT_melt_1, Type ~ variable, fun.agg = function(x) sum(!is.na(x)), subset = .(variable != "Length")) # Type Width Weight # 1: Compact 1 1 # 2: Midsize 3 3 # 3: Small 1 1 |
[Reference]
* R data.table vignette
: https://cran.r-project.org/web/packages/data.table/vignettes/datatable-reshape.html
다음번 포스팅에서는 특정 패턴이 있는 data.table의 여러개 칼럼을 동시에 녹이고 주조하여 재구조화하는 방법을 소개하겠습니다. (https://rfriend.tistory.com/576)
이번 포스팅이 많은 도움이 되었기를 바랍니다.
행복한 데이터 과학자 되세요!