이번 포스팅은 페이스북의 R User Group 에서 아이디어 토론 주제로 올라왔길레 한번 짜봤던 코드입니다.  


'0'과 '1'로 가지는 긴 벡터를 처음부터 끝까지 쭉 훓어가면서 


(1) '0'이 연속으로 3번 나오면 그 구간을 기준으로 나누어서 

=> (2) 나누어진 구간을 새로운 벡터로 생성하기


입니다.  


'0'이 연속으로 나오는 회수는 분석가가 필요로 하는 회수로 지정할 수 있도록 매개변수(argument)로 지정해서 프로그래밍해보겠습니다. 


간단한 예제 벡터로서, '0'과 '1'을 원소로 해서 30개의 무작위수(이항분포 랜덤 샘플링, 0이 80%, 1이 20%)로 구성된 벡터를 생성해보겠습니다. 


 

> rm(list=ls()) # clean all


# Sample vector


> set.seed(123) # for reproducibility

> vec_raw <- sample(c(0, 1), size=30, replace=TRUE, prob=(c(0.8, 0.2)))

> vec_raw

 [1] 0 0 0 1 1 0 0 1 0 0 1 0 0 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0

> vec <- vec_raw # copy for manipulation




R 프로그래밍을 하고자 하는 일의 아웃풋 이미지는 아래와 같습니다. 

'0'이 연속 세번 나오는 구간을 노란색으로 표시를 했습니다. 이 구간을 기준으로 원래의 벡터를 구분해서 나눈 후에(split), 각각을 새로운 벡터로 생성하여 분리해주는 프로그램을 짜보는 것이 이번 포스팅의 주제입니다. 




아래에 wihle() 반복문, if(), else if() 조건문, assign() 함수를 사용해서 위의 요건을 수행하는 코드를 짜보았습니다. 원래 벡터에서 제일 왼쪽에 있는 숫자가 '0'이 아니면 하나씩 빼서 빈 벡터에 차곡차곡 쌓아 놓구요, '0'이 3개 나란히 있는 '000'이 나타나면 그동안 쌓아두었던 벡터를 잘라내서 다른 이름의 벡터로 저장하고, '000'도 그 다음 이름의 벡터로 저장하면서 원래 벡터에서 '000'을 빼놓도록 하는 반복문입니다. assign() 함수는 저장하는 객체의 이름에 paste()를 사용해서 변화하는 숫자를 붙여주고자 할 때 사용하는 함수인데요, 자세한 사용법 예제는 여기(=> http://rfriend.tistory.com/108)를 참고하세요. 


아래 프로그램에서 'bin_range'에 할당하는 숫자를 변경하면 원하는 회수만큼 '0'이 반복되었을 때 구간을 분할하도록 조정할 수 있습니다.  가령, '0'이 연속 10번 나오면 분할하고 싶으면 bin_range <- 10  이라고 할당해주면 됩니다. 



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

# Vector Bin Split by successive '0's criteria

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


# Setting

vec_tmp <- c() # null vector

bin_range <- 3 # successive '0's criterion

vec_idx <- 1

vec_num <- 1


# Zero_Bin_Splitter

while (length(vec) > 0){

  if (sum(vec[1:bin_range], na.rm=T) != 0){

    vec_tmp[vec_idx] <- vec[1]

    vec <- vec[-1]

    vec_idx <- vec_idx + 1

  } else if (is.null(vec_tmp)){

    assign(paste0("vec_split_", vec_num), vec[1:bin_range])

    vec <- vec[-(1:bin_range)]

    vec_idx <- 1 # initialization

    vec_num <- vec_num + 1

  } else {

    assign(paste0("vec_split_", vec_num), vec_tmp)

    vec_tmp <- c() # initialization

    vec_num <- vec_num + 1

    assign(paste0("vec_split_", vec_num), vec[1:bin_range])

    vec <- vec[-(1:bin_range)]

    vec_idx <- 1 # initialization

    vec_num <- vec_num + 1

  }

}


# delete temp vector and arguments

rm(vec, vec_tmp, bin_range, vec_idx, vec_num)




아래는 위의 프로그램을 실행시켰을 때의 결과입니다.  원래 의도했던대로 잘 수행되었네요. 




=====================================================

아래의 코드는 페이스북 R User Group의 회원이신 June Young Lee 님께서 data.table 라이브러리를 사용해서 짜신 것입니다.  코드도 간결할 뿐만 아니라, 위에 제가 짠 코드보다 월등히 빠른 실행속도를 자랑합니다. 대용량 데이터를 빠른 속도로 조작, 처리하려면 data.table 만한게 없지요. 아래 코드는 저도 공부하려고 옮겨 놓았습니다. 


똑같은 일을 하는데 있어서도 누가 어떤 로직으로 무슨 패키지, 함수를 사용해서 짜느냐에 따라서 복잡도나 연산속도가 이렇게 크게 차이가 날 수 있구나 하는 좋은 예제가 될 것 같습니다. 이런게 프로그래밍의 묘미이겠지요? ^^  June Young Lee 님께 엄지척! ^^b



# by June Young Lee


library(data.table)


# 0이 세번 연속으로 들어 있는 아무 벡터 생성

vec <- c(0,0,0,3,10,2,3,0,4,0,0,0,1,2,50,4,0,0,32,1,0,0,0,1,1)


# data.table함수인 rleid이용하여 연속인 행들을 구분하는 idx1 컬럼 생성 

dt <- data.table(v1=vec, idx1=rleid(vec))


# idx1 기준으로 갯수를 세어 N 컬럼 생성 

dt[,N:=.N, by=idx1]


# 0이 세번 연속으로 나온 그룹(=N컬럼변수가 3인)을 idx2로 체크하기 

dt[v1==0&N==3,idx2:=1L]


# split 하기 위한 기준 벡터를 마찬가지로 rleid함수를 이용하여 idx3로 생성


dt[,idx3:=rleid(idx2)]


# data.table의 split 함수 적용하고, lapply 적용하여 v1 칼럼만 추출 

res <- lapply(split(dt, by="idx3"), function(x) x[,v1])

res

# $`1`

# [1] 0 0 0

# $`2`

# [1] 3 10 2 3 0 4

# $`3`

# [1] 0 0 0

# $`4`

# [1] 1 2 50 4 0 0 32 1

# $`5`

# [1] 0 0 0

# $`6`

# [1] 1 1


# 0이 3번 연속으로 나온 놈들을 남겨두도록 작성한 이유는, 

# 위치확인 & 확인용입니다. 

# 필요없으면, 아래 정도의 코드를 추가하면 되겠네요.


res2 <- res[!unlist(lapply(res, function(x) length(x)==3&sum(x)==0))]

res2

# $`2`

# [1] 3 10 2 3 0 4

# $`4`

# [1] 1 2 50 4 0 0 32 1

# $`6`

# [1] 1 1

 


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


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



Posted by R Friend R_Friend