'R 데이터프레임 가로형태를 세로형태로 재구조화 변환하기'에 해당되는 글 1건

  1. 2016.09.29 [R] 데이터프레임 다수 item을 가진 문자형 변수를 분리 후에 세로로 재구조화하기 (10)

아래의 예제와 같이 콤마로 구분되어 있는 문자형 변수(caracter variable with comma seperator delimiter)를 가진 데이터 프레임이 있다고 합시다.

 

두번째의 item 변수에 콤마로 구분되어 들어있는 다수의 항목들을 구분자 콤마(',')를 기준으로 분리(split)한 후에 => 이를 동일한 name을 key로 해서 세로로 주욱~ 늘여서 재구조화하여야 할 필요가 생겼다고 합시다.

 

데이터 구조변환의 전/후 모습을 말로 설명하려니 좀 어려운데요, 아래의 'Before (original dataset) => After (transformation)' 의 그림을 참고하시기 바랍니다.

 

 

 

이걸 R로 하는 방법을 소개합니다.

 


 

먼저 위의 Before 모양으로 간단한 데이터 프레임을 만들어보았습니다.

 


 ##--------------------------------------------------------
## splitting character of dataframe and reshaping dataset
##--------------------------------------------------------

 

# (1) creating sample dataframe
name_1 <- c("John")
item_1 <- c("Apple,Banana,Mango")

name_2 <- c("Jane")
item_2 <- c("Banana,Kiwi,Tomato,Mango")

name_3 <- c("Tom")
item_3 <- c("Apple,Tomato,Cherry,Milk,IceCream")

tr_1 <- cbind(name_1, item_1)
tr_2 <- cbind(name_2, item_2)
tr_3 <- cbind(name_3, item_3)
mart <- data.frame(rbind(tr_1, tr_2, tr_3))

 

# rename
install.packages("dplyr")
library(dplyr)
mart <- rename(mart,
               name = name_1,
               item = item_1)

 

#-------------

> mart
  name                              item
1 John                Apple,Banana,Mango
2 Jane          Banana,Kiwi,Tomato,Mango
3  Tom Apple,Tomato,Cherry,Milk,IceCream
 

 

 

 


 

위의 mart 라는 이름의 'Before' 데이터프레임을 'After' 모양의 데이터 프레임으로 구조 변환시키는 절차 및 R script는 아래와 같습니다.

 

 

[ 문자형 분리(split) 및 데이터 재구조화(reshape) 절차 ]

 

(1) mart_new <- data.table() : mart_new 라는 비어있는 데이터 테이블, 데이터 프레임을 생성  (<= 여기에 차곡차곡 loop돌린 결과를 rbind()로 쌓아가게 됨)

 

(2) n <- nrow(mart) : original dataset인 mart 의 총 행의 개수(nrow(mart))를 n 이라는 벡터로 할당 (<= 이 개수만큼 loop를 돌릴겁니다. 위 예제는 총 3개군요. John, Jane, Tom 이렇게요)

 

(3) for (i in 1:n)  : 1부터 n (즉, 3)까지 반복 수행 <= 매 행별로 아래의 문자열을 구분자를 기준으로 분리 후 세로로 재구조화하는 작업을 반복할 겁니다

 

(4) print(i) : 반복수행 loop 문이 어디쯤 돌고 있나 콘솔 창에서 확인하려고 집어넣었습니다. 굳이 없어도 됩니다.

 

(5) name_index <- as.character(mart[i, 1])  : i번째 행(row)의 1번째 열("name")을 indexing 해옵니다. 즉, i의 순서 1, 2, 3 별로 "John", "Jane", "Tom", 이렇게 순서대로 indexing 해옵니다.  

 

(6) item_index <- as.character(mart[i, 2])  : i번째 행(row)의 2번째 열("item")을 indexing 해옵니다. 

 

(7) item_index_split_temp <- data.frame(strsplit(item_index, split = ',')) : (6)번에서 i번째 행별로 indexing해 온 item을 구분자 'comma (',')'를 기준으로 문자열을 분리(split)한 후에, 이것을 데이터 프레임으로 생성합니다.

 

(8) mart_temp <- data.frame(cbind(name_index, item_index_split_temp))  : (5)번과 (7)번 결과물을 cbind()로 묶은 후에, 이것을 데이터 프레임으로 생성합니다.  cbind()로 결합할 때 name은 1개인데 item이 2개 이상일 경우에는 1개밖에 없는 name이 자동으로 반복으로 item의 개수만큼 재할당이 됩니다.

 

(9) names(mart_temp) <- c("name", "item")  : mart_temp의 변수 이름을 첫번째 변수는 "name", 두번째 변수는 "item"으로 지정해줍니다.

 

(10) mart_new <- rbind(mart_new, mart_temp) : (1)번에서 생성한 (처음에는 비어있는) 데이터프레임에 loop를 돌 때마다 생성이되는 (8, 9)번 데이터프레임을 차곡 차곡 rbind()로 반복적으로 쌓아줍니다.

(11) rm(name_index, item_index, item_index_split_temp, mart_temp)  :  중간 임시 데이터셋 삭제

 

끝.

 

 

[ R script ]

 

 

# (2) splitting charater by delimeter, reshaping data structure by loop programming

install.packages("data.table")
library(data.table)

mart_new <- data.table() # blank data.table

 

n <- nrow(mart) # total number of rows

 

for (i in 1:n){
 

  print(i) # to check progress
 
  name_index <- as.character(mart[i, 1])
  item_index <- as.character(mart[i, 2])
 
  item_index_split_temp <- data.frame(strsplit(item_index, split = ','))
  mart_temp <- data.frame(cbind(name_index, item_index_split_temp))
 
  names(mart_temp) <- c("name", "item")
 
  mart_new <- rbind(mart_new, mart_temp)
}
 

rm(name_index, item_index, item_index_split_temp, mart_temp) # delete temp dataset

 

# ----------------

> mart_new
    name     item
 1: John    Apple
 2: John   Banana
 3: John    Mango
 4: Jane   Banana
 5: Jane     Kiwi
 6: Jane   Tomato
 7: Jane    Mango
 8:  Tom    Apple
 9:  Tom   Tomato
10:  Tom   Cherry
11:  Tom     Milk
12:  Tom IceCream
 

 

 

 


 

Ashtray 님께서 댓글로 남겨주신 R script를 여러 사람이 좀더 쉽게 널리 공유할 수 있도록 아래에 공유합니다 (Ashtray님, R script 공유해주셔서 감사합니다. ^^).

 

구분자가 "\\^"를 기준으로 문자열을 분리하고, for loop 문을 사용해서 세로로 세워서 재구조화하는 절차는 같습니다.  대신 변수 개수가 다르다보니 for loop문이 조금 다르구요, data table이 아니라 list()를 사용했다는점도 다릅니다. 

 

R script 짜다보면, 그리고 다른 분께서 R script 짜놓은것을 보면 "동일한 input과 동일한 output이지만 가운데 process는 방법이 단 한가지만 있는게 아니구나" 하고 저도 배우게 됩니다.

 

 

names(data) <- c("number", "PK", "Call_ID", "Keyword_Count")
data$Keyword_Count <- strsplit(data$Keyword_Count, split="\\^")

datalist <- list()
k <- 1
for(i in 1:nrow(data)){
    for(j in 1:length(unlist(data$Keyword_Count[i]))){
        datalist[[k]] <- list(data$number[i], data$PK[i], data$Call_ID[i],
        data$Keyword_Count[[i]][j])
    k <- k+1
    }
}

data2 <- rbindlist(datalist)
data2[[4]] <- unlist(data2[[4]])

 

 

 


 

혹시 Spark을 사용하는 분이라면 (key, value) pair RDD로 구성된 데이터셋에 대해 flatMapValues() 라는 pair operation 을 사용하면 쉽게 key를 기준으로 세로로 재구성한 RDD를 만들 수 있습니다.  참고하세요.

 

 

 

많은 도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~ ♡'를 꾸욱 눌러주세요.

 

 

[Reference]

1. R 문자함수 : http://rfriend.tistory.com/37

2. R 반복 연산 loop 프로그래밍 : http://rfriend.tistory.com/90

 

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. AshtrayKim 2016.09.29 15:42  댓글주소  수정/삭제  댓글쓰기

    감사합니다! 제가 한 코드도 공유하겠습니다.
    다만 제 원본 테이블은 name에 해당하는 컬럼이 3개(number, PK, Call_ID)로 나눠져있고,
    item에 해당하는 항목을 ^로 나누었습니다

  2. AshtrayKim 2016.09.29 15:48  댓글주소  수정/삭제  댓글쓰기

    스팸차단이라고 글을 못작성하게 해서.. 핵심적인 부분만 적을게요
    names(data) <- c("number", "PK", "Call_ID", "Keyword_Count")
    data$Keyword_Count <- strsplit(data$Keyword_Count, split="\\^")

    datalist <- list()
    k <- 1
    for(i in 1:nrow(data)){
    for(j in 1:length(unlist(data$Keyword_Count[i]))){
    datalist[[k]] <- list(data$number[i], data$PK[i], data$Call_ID[i],
    data$Keyword_Count[[i]][j])
    k <- k+1
    }
    }

    data2 <- rbindlist(datalist)
    data2[[4]] <- unlist(data2[[4]])

  3. 이상규 2016.11.25 13:22  댓글주소  수정/삭제  댓글쓰기

    감사합니다 !! 정말 도움 많이 되는것 같아요

    혹시 After 에서 Before로 넘어가는 포스팅도 있나요?

    • R Friend R_Friend 2016.11.25 13:35 신고  댓글주소  수정/삭제

      이상규님,
      arules package의 read.transactions() 함수 사용해보세요.

      library(arules)

      tr <- read.transactions(file, format=c("single"), cols=c("transactionID", "itemID"))

    • 이상규 2016.11.27 16:52  댓글주소  수정/삭제

      안녕하세요 알프렌드님

      Before로 넘어가려고 추천해주신 코드로 아래와 같이 코딩했는데

      > tr <- read.transactions("su.csv", format = c("single"), cols = c("JID", "Group"))


      Error in read.transactions("su.csv", format = c("single"), cols = c("JID", :
      'cols' does not match 2 entries in header of file.

      에러가 발생했습니다. 어느 부분이 문제일까요 ㅜㅜ?

      JID는 character
      Group은 Factor형태입니다.

    • R Friend R_Friend 2016.11.27 18:00 신고  댓글주소  수정/삭제

      저도 read.transactions 해보니 에러 나네요. ^^;

      대신에 as(split([dadaframe[,"itemID"], dataframe[,"transactionID"]), "transactions") 함수를 사용해보세요. 아래 R script 참고하세요.

      ##-- making data.frame
      transactionID <- c(rep("tr1", 3), rep("tr2", 4))
      itemID <- c("item1", "item2", "item3", "item1", "item2", "item4", "item5")
      tr_df <- data.frame(transactionID, itemID)
      str(tr_df)

      ## converting data.frame to transactions format
      tr <- as(split(tr_df[,"itemID"], tr_df[,"transactionID"]), "transactions")
      inspect(tr)

      > inspect(tr)
      items transactionID
      [1] {item1,item2,item3} tr1
      [2] {item1,item2,item4,item5} tr2

    • 이상규 2016.11.27 18:03  댓글주소  수정/삭제

      감사합니다! 도전해보고 올게요!!