'R 문자열 부분집합 찾기'에 해당되는 글 1건

  1. 2015.07.25 R 문자함수 nchar(), substr(), paste(), strsplit(), sub(), gsub(), grep(), regexpr(), gregexpr() (18)

그동안 R 에서 숫자형 벡터의 처리에 대한 여러가지 함수를 알아 보았습니다.  벡터는 R의 똘망똘망한 일꾼이자 무기라고 말씀드렸는데요, 이번 포스팅에서는 숫자형 벡터 외에 문자형 벡터를 가지고 떡 주무르듯이 가지고 놀 수 있는 문자형 함수들을 알아보도록 하겠습니다.

 

R 문자형 벡터를 다루는 함수로는 nchar(), substr(), paste(), strsplit(), sub(), gsub(), grep(), regexpr(), gregexpr() 등이 있습니다.  아래에 예제를 들어가면서 하나씩 설명 드리도록 하겠습니다.

 

R을 활용한 텍스트 마이닝은 별도로 나중에 분석 주제로 들어가면 그때 설명드리도록 하겠습니다. 

 

 

 R 문자함수 nchar(), substr(), paste(), strsplit(), sub(), gsub()

 

(1) nchar(x) : 문자형 벡터 x의 구성요소 개수 구하기

 

> # nchar()
> x <- c("Seoul", "New York", "London", "1234")
> nchar(x)
[1] 5 8 6 4

 

"New York"의 경우 중간에 스페이스바 공간 하나가 있는데요, 이것도 '1'개로 count해서 '8'로 계산했다는 점은 유의하기 바랍니다.

 

 

(2) substr(x, start, stop) : 문자형 벡터 x의 start에서 부터 stop 까지만 잘라오기 (부분 선택)

 

> # substr()로 문자형 벡터 부분 선택하기 > time_stamp <- c("201507251040", "201507251041", "201507251042", "201507251043", "201507251044") > t_yyyymm <- substr(time_stamp, 1, 6) > t_yyyymm [1] "201507" "201507" "201507" "201507" "201507" >

> # 데이터 프레임에서 transfrom()함수와 substr()함수로 부분 선택한 내용으로 새로운 변수 만들기
>
gas_temp <- c(145.0, 145.1, 145.5, 150.1, 150.6) > ts_gas_temp <- data.frame(time_stamp, gas_temp) > ts_gas_temp time_stamp gas_temp 1 201507251040 145.0 2 201507251041 145.1 3 201507251042 145.5 4 201507251043 150.1 5 201507251044 150.6 > > ts_gas_temp <- transform(ts_gas_temp, mmdd = substr(time_stamp, 5, 8)) > ts_gas_temp <- transform(ts_gas_temp, hhmm = substr(time_stamp, 9, 12)) > > ts_gas_temp time_stamp gas_temp mmdd hhmm 1 201507251040 145.0 0725 1040 2 201507251041 145.1 0725 1041 3 201507251042 145.5 0725 1042 4 201507251043 150.1 0725 1043 5 201507251044 150.6 0725 1044

 

첫번째 예제는 time_stamp 라는 벡터를 가지고 부분 선택하는 것이고, 두번째 예제는 ts_gas_temp라는 데이터 프레임에서 특정 변수를 선택해서 transfrom()이라는 함수와 substr()함수를 사용해서 부분 선택한 내용으로 새로운 변수를 만들어 보는 예제가 되겠습니다.

 

 

(3) paste(x, y, sep = " ") : 문자형 벡터 x와 y를 붙이기

 

> # 문자형 벡터의 객체들을 paste()로 하나로 붙이기 > paste("I", "Love", "New York", sep = "") [1] "ILoveNew York" > paste("I", "Love", "New York", sep = " ") [1] "I Love New York" > paste("I", "Love", "New York", sep = "_") [1] "I_Love_New York" >

> # 데이터 프레임에서 transform()함수와 paste()함수로 두개의 변수를 하나로 합쳐서 새로운 변수 만들기 > ts_gas_temp <- transform(ts_gas_temp, mmddhhmm = paste(mmdd, "일_", hhmm, "분", sep="")) > ts_gas_temp time_stamp gas_temp mmdd hhmm mmddhhmm 1 201507251040 145.0 0725 1040 0725일_1040분 2 201507251041 145.1 0725 1041 0725일_1041분 3 201507251042 145.5 0725 1042 0725일_1042분 4 201507251043 150.1 0725 1043 0725일_1043분 5 201507251044 150.6 0725 1044 0725일_1044분

 

첫번째 예제는 문자형 벡터의 객체들을 paste() 함수를 사용해 하나로 붙인 것인데요, sep="", sep=" ", sep="_" 등 sep에 무엇을 넣느냐에 따라 결과가 달라지는 것을 알 수 있습니다.

 

두번째 예제는 데이터 프레임에서 transfrom()과 paste()함수를 사용해 두개 이상의 문자형 벡터 변수를 합쳐서 새로운 문자형 변수를 만드는 예제입니다.  실전에서는 데이터 프레임 구조의 데이터 셋을 가지고 작업을 많이 하므로 알아두면 유용하겠지요.

 

 

(4-1) strsplit(x, split= ",") : 문자형 벡터 x를 split 기준으로 해서 나누기

 

> # strsplit() 으로 분리하기
> name <- c("Chulsu, Kim", "Younghei, Lee", "Dongho, Choi")
> name_split <- strsplit(name, split=",")
> name_split
[[1]]
[1] "Chulsu" " Kim"  

[[2]]
[1] "Younghei" " Lee"    

[[3]]
[1] "Dongho" " Choi" 

> 

> # indexing 해오기 > last_name <- c(name_split[[1]][2], name_split[[2]][2], name_split[[3]][2]) > last_name [1] " Kim" " Lee" " Choi" > > first_name <- c(name_split[[1]][1], name_split[[2]][1], name_split[[3]][1]) > first_name [1] "Chulsu" "Younghei" "Dongho" > > # last_name과 first_name, name을 데이터 프레임으로 묶기

> name_d.f <- data.frame(last_name, first_name, name)
> name_d.f
  last_name first_name          name
1       Kim     Chulsu   Chulsu, Kim
2       Lee   Younghei Younghei, Lee
3      Choi     Dongho  Dongho, Choi

 

strsplit()함수는 split="any" 의 큰따옴표 안에 들어가는 구분자 기준에 따라서 문자열을 분리해주는 함수입니다.

첫번째 예제 strsplit()함수로 name 문자형 벡터를 나누어보니 결과가 리스트(list) 구조로 나왔습니다.

 

두번째 예제는 리스트(list) 결과에서 Indexing해오는 방법을 소개하여보았습니다.  Indexing은 데이터 처리, 프로그래밍할 때 정말 많이 쓰고 반드시 알아두어야 하는 핵심 개념 중의 하나입니다. Indexing에 관한 자세한 내용은 이전 포스팅을 참고하세요 (☞ 바로가기)

 

세번째 예제는 strsplit()함수로 분리한 개별 벡터들을 하나의 데이터 프레임으로 묶는 방법이 되겠습니다. 세트로 알아두면 좋겠지요?

 

 

 

(4-2) 데이터프레임에서 문자열을 구분자 기준으로 나누는 방법은 아래 예제를 참조하세요.

        (spliting character in dataframe by delimeter)

         :  data.frame(do.call('rbind', strsplit(as.character(df$var), split='delimeter', fixed=T))) 

 

> ##############################################
> ## split character in dataframe by delimeter
> ##############################################
> 
> # example data frame
> name_df <- data.frame(ID = c(1:3), name = c("Chulsu/Kim", "Younghei/Lee", "Dongho/Choi"))
> name_df
  ID         name
1  1   Chulsu/Kim
2  2 Younghei/Lee
3  3  Dongho/Choi
> 
> 
> # strsplit character in dataframe by delimeter
> name_strsplit <- data.frame(do.call('rbind', 
+                                     strsplit(as.character(name_df$name), 
+                                              split = '/', 
+                                              fixed = TRUE)))
> name_strsplit
        X1   X2
1   Chulsu  Kim
2 Younghei  Lee
3   Dongho Choi
> 
> 
> # changing name
> # install.packages("reshape")
> library(reshape)
> name_strsplit <- rename(name_strsplit, 
+                         c(X1 = "First_Name", 
+                         X2 = "Last_Name"))
> 
> name_strsplit
  First_Name Last_Name
1     Chulsu       Kim
2   Younghei       Lee
3     Dongho      Choi

 

 

 

 

(5) sub(old, new, x): 문자형 벡터 x에서 처음 나오는 old문자를 new문자로 한번만 바꾸기

(6) gsub(old, new, x): 문자형 벡터 x 내에 모든 old 문자를 new 문자로 모두 바꾸기

 

> # sub()는 처음 나오는 old 문자만 new 문자로 한번만 바꿈 > z <- c("My name is Chulsu. What's your name?") > sub("name", "first name", z) [1] "My first name is Chulsu. What's your name?" >

> # gsub()는 모든 old 문자를 new 문자로 바꿈
> gsub("name", "first name", z)
[1] "My first name is Chulsu. What's your first name?"
> 

> # new 자리에 ""를 넣으면 없애는 효과

> sub("My name is Chulsu. ", "", z)
[1] "What's your name?"

 

첫번째 예제 sub()함수에서는 "name"을 "first name"으로 바꾸라는 명령문입니다.  필자가 이해를 돕기 위해 name에다가 밑줄을 그어놓았는데요, z 벡터에는 name이 두번 나오는데 sub()함수로 바꾸기를 했더니 처음 나오는 "name"은 "first name"으로 바뀌었지만 두번째 나오는 "name"은 그대로인 것을 알 수 있습니다.

 

반면에 두번째 예시에서 gsub()는 첫번째 나오는 "name"을 "first name"으로 바꾸었을 뿐만 아니라, 두번째 나오는 "name" 또한 "first name"으로 바꾸었습니다.  따라서 분석 목적에 따라서 sub()와 gsub()를 선택적으로 사용하시면 되겠습니다.

 

 아래 예제는 데이터 프레임에서 transform()함수로 sub()함수를 사용해서 특정 변수 내 특정 문자열을 old -> new로 바꾸는 내용이 되겠습니다. 

 

> cust_id <- c("c1", "c2", "c3", "c4", "c5", "c6")
> size <- c("XS", "L", "M", "XS", "XL", "S")
> cust_db <- data.frame(cust_id, size)
> cust_db
  cust_id size
1      c1   XS
2      c2    L
3      c3    M
4      c4   XS
5      c5   XL
6      c6    S
>

> # size 변수 ㄴ "XS" 사이즈를 "S" 사이즈로 바꿔서 size_1 이라는 새로운 변수에 저장(생성)해라 > cust_db <- transform(cust_db, size_1 = sub("XS", "S", size)) > cust_db cust_id size size_1 1 c1 XS S 2 c2 L L 3 c3 M M 4 c4 XS S 5 c5 XL XL 6 c6 S S 

 

예제의 경우 만약 'old'에서 'new'로 바꿔야 하는 조건이 2개 이상이 되면 ifelse() 라든지 within() 함수 등을 사용해야 하는데요, 이것은 나중에 새로운 범주형 변수 만들기에서 별도로 소개해드리도록 하겠습니다.

 


문자열에 포함되어 있는 모든 점(".", point)을 없애려면 gsub(".", "", col, fixed=TRUE) 라고 하거나, 혹은 정규 표현식을 이용해서 gsub("[.]", "", col) 이라고 해주면 됩니다. 


> # how to remove a point "." in a string

> id <- c("a", "b", "c", "c")

> col <- c("11.23", "64.12", "931.01", "3.3.0.4.1.2")

> df <- data.frame(id, col)

> df

  id         col

1  a       11.23

2  b       64.12

3  c      931.01

4  c 3.3.0.4.1.2

> df <- transform(df, 

+                 col_2 = gsub(".", "", col, fixed=TRUE))

> df

  id         col  col_2

1  a       11.23   1123

2  b       64.12   6412

3  c      931.01  93101

4  c 3.3.0.4.1.2 330412

> df <- transform(df, 

+                 col_3 = gsub("[.]", "", col))

> df

  id         col  col_2  col_3

1  a       11.23   1123   1123

2  b       64.12   6412   6412

3  c      931.01  93101  93101

4  c 3.3.0.4.1.2 330412 330412


 

(7) grep(pattern, x) : 문자열 벡터에서 특정 부분 문자열 패턴 찾기

 

> grep("1010", c("1001", "1010", "1110", "101000"))
[1] 2 4
> 
> grep("1010", c("1001", "1009", "1110", "100000"))
integer(0) 

 

위의 첫번째 예는 문자열 "1010"이라는 패턴이 2번째와 4번째 원소에 들어있다는 뜻입니다.

두번째 예에서는 문자열 "1010"이라는 패턴이 하나도 안들어 있다는 뜻이 되겠습니다.

 

 

(8) regexpr() : text 내에서 패턴이 가장 먼저 나오는 위치 찾기

 

> regexpr("NY", "I love NY and I'm from NY")
[1] 8
attr(,"match.length")
[1] 2
attr(,"useBytes")
[1] TRUE


"NY"이라는 패턴이 8번째 (스페이스 포함) 자리에서 처음으로 나왔다는 뜻입니다. 

 

 

(9) gregexpr() : text 내에서 패턴이 나오는 모든 위치를 찾기

 

> gregexpr("NY", "I love NY and I'm from NY")
[[1]]
[1]  8 24
attr(,"match.length")
[1] 2 2
attr(,"useBytes")
[1] TRUE 

 

"NY"이라는 패턴이 8번째, 그리고 24번째 자리에서 나왔다는 뜻입니다. 

 

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

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. AshtrayK 2016.08.30 16:38 신고  댓글주소  수정/삭제  댓글쓰기

    sub는 한개의 문자형 스칼라?원소? 내에서 처음나오는 old를 바꿔주는 거죠?
    그래서 XS를 S로 바꾸는 예제에서 sub를 쓴 결과가 저렇게 나온거구 gsub를 여기에 써도 마찬가지 결과가 나오는 거 맞나요?
    (현재 실습이 불가하여 ㅠㅠ)

  2. AshtrayK 2016.08.30 16:47 신고  댓글주소  수정/삭제  댓글쓰기

    (4-2)부분이 생소하네요 ㅠㅠ
    do.call과 fixed=TRUE 부분 설명좀 부탁드립니다~

  3. AshtrayK 2016.09.26 10:57 신고  댓글주소  수정/삭제  댓글쓰기

    strsplit으로 분리해볼게있는데요. split="^"으로 하면 작동이 안되는것 같은데 어떻게해야 할까요ㅠㅠ
    예제 만들어서 해봐도 이상하게 나오네요

  4. 궁금해요 2016.11.01 14:10  댓글주소  수정/삭제  댓글쓰기

    안녕하세요ㅜ 오랜만에 다시 글을 남깁니다..

    데이터 프레임내에서 데이터 내의 문자로 filter 하고 싶은데...

    예를 들어 코코아분말, 코코아음료 등의 분류가 있는데, 코코아라는 공통된 문자로 데이터를 filter하고 싶은데요. 어떻게 하면 될까요?

    • R Friend R_Friend 2016.11.01 14:19 신고  댓글주소  수정/삭제

      substr(x, 1, 3) 으로 앞 3자리 문자만 가져다가 새로운 변수를 만든 다음에 => "코코아" 조건으로 filter() 또는 subset() 하면 될거 같습니다.

    • R Friend R_Friend 2016.11.02 10:41 신고  댓글주소  수정/삭제

      좀더 쉬운 방법, 함수가 있을거 같긴 한데요,,,, 일단 제가 아는 선에서 (복잡한... -_-;) 프로그램 짜봤습니다. 급하시면 일단 이거라도 참고하시지요.

      ##------------------------------------------
      # making dataframe
      prd <- data.frame(ID = c(1:5),
      prd_name = c("코코아분말", "브라질산 코코아",
      "맛있는 코코아 우유", "커피 사탕",
      "바닐라라떼"))

      str(prd)

      # prd_name : changing from factor -> character
      prd <- transform(prd, prd_name = as.character(prd_name))
      class(prd$prd_name)

      # number of character
      prd <- transform(prd, num_char = nchar(prd$prd_name))
      prd

      # splitting character in dataframe
      prd_strsplit <- data.frame(do.call('rbind',
      strsplit(as.character(prd$prd_name),
      split = "코코아",
      fixed = TRUE)))

      prd_strsplit

      str(prd_strsplit)

      # prd_name : changing from factor -> character
      prd_strsplit <- transform(prd_strsplit,
      X2 = as.character(X2))
      class(prd_strsplit$X2)

      # number of character of prd_strsplit dataframe
      prd_strsplit <- transform(prd_strsplit,
      num_char_strsplit_X2 = nchar(X2))

      prd_strsplit

      # cbind
      prd$num_char_strsplit_X2 <- prd_strsplit$num_char_strsplit_X2
      prd

      # if num_char and num_char_strsplit_X2 is not equal, then filter it
      prd <- transform(prd, nchar_gap = num_char - num_char_strsplit_X2)

      prd_subset <- subset(prd,
      subset = nchar_gap != 0)

      prd_subset

    • Ara 2018.07.31 10:45  댓글주소  수정/삭제

      덕분에 많이 배우고 갑니다. 코코아가 들어간 문자에만 nchar에 변화를 주는 방법으로 필터링 하셨네요. 엑셀로는 몇번의 클릭으로 되는 일이 R에서는 이정도의 작업을 거쳐야 한다는게 번거롭긴 하지만.. 덕분에 그 기저에 깔려있는 논리나 프로세스를 많이 배웁니다. 항상 좋은 콘텐츠 감사합니다.

    • R Friend R_Friend 2018.07.31 10:52 신고  댓글주소  수정/삭제

      안녕하세요 Ara님,
      아마도 stringr 같은 문자열 처리 전문 패키지에 찾아보면 훨씬 간단하게 할 수 있는 험수가 있을거 같습니다. ^^;

      블로그 좋게 봐주셔서 감사합니다.

  5. AshtrayK 2016.11.09 17:10  댓글주소  수정/삭제  댓글쓰기

    regexpr 질문있습니다.
    결과물이
    [1] -1 4 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
    attr(,"match.length")
    [1] -1 2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
    attr(,"useBytes")
    [1] TRUE

    이렇게 3가지가 나오는데요.
    첫번째 줄의 뜻은 알겠는데요(처음으로 패턴이 등장하는 index시작위치)
    나머지는 잘 모르겠습니다.

    match.length는, regexpr("en", txt)로 실행할 때, 찾고싶은 패턴(여기서는 en)의 길이와 항상 똑같은 값이 나오게 되는 것 같은데..
    en을 찾으라고 했으면 당연히 찾으라고 한 값의 길이인 2가 나온다는 것을 굳이 출력해줄 이유가 있을까요?
    예외가 없다면 이건 어디에 써먹을 수 있는 정보인지 모르겠어서요..

    그리고 마지막 useBytes는 무슨뜻인지 전혀 감이 안잡힙니다ㅠㅠ

    • R Friend R_Friend 2016.11.16 23:52 신고  댓글주소  수정/삭제

      저도 AshtrayK님과 비슷한 생각입니다. 말씀해주신 것처럼 match.length 가 추가로 제공해주는 정보라는게 미미하다고 생각합니다.

      "useBytes"는 TRUE면 index 숫자로 표기되고 FALSE면 문자형(character)로 표기된다고 매뉴얼에는 나오는데요, FALSE인 경우를 한번도 못봤습니다. 이것도 추가 제공 정보라는게 거의 없다고 생각합니다.

      근데, 이 함수 만든 사람이 뭔가 쓸모가 있으니깐 이렇게 3개 값을 반환하라고 해놨을 텐데요, 이걸 어디에 써먹을 수 있을까 좀 억지로 생각을 해보자면요,

      우리가 예로 든것 처럼 몇 개의 소소한 데이터셋을 가지고 공부하려고 하는 분석이 아니라, 대용량의 수백, 수천개의 변수를 가진 데이터셋에 대해서 '자동화'하는 애플리케이션을 운영하는 상황이라고 했을 때요,

      (1) "usrBytes" 가 TRUE 인 것을 확인하고 (=> 음, 찾으려는 문자열의 시작위치와 길이를 나타내는 index값을 사용해도 되겠군. OK, go!)

      => (2) 시작 위치와 => (3) 길이(match.length)의 index 값을

      regexpr 분석결과의 반환 객체에서 찾아서 자동으로 찾아가서 뭔가 추가 조치를 취할 수 있도록 하는데 쓰일 수 있지 않을까...하고 긍정적인 마인드로 생각해보았습니다. ^^;

  6. 산낙지 2017.05.31 22:19  댓글주소  수정/삭제  댓글쓰기

    각각 변수의 값들을 paste하려고 하는데, 변수에 na가 있는 경우 na까지 그대로 paste가 되더라고요... "산낙지 na" 이런 식으로요. na.rm=TRUE와 같은 옵션을 줘도 안되고요. paste에서 na를 무력화할 수 있는 방법이 있을까요?

    • R Friend R_Friend 2017.05.31 22:39 신고  댓글주소  수정/삭제

      집에 인터넷이 고장나서 핸드폰으로 간단히 답변 달아요.

      # input data
      c1 <- c("A", "B", "C")
      c2 <- c(1, 2, NA)

      # combining c1, c2 as a dataframe
      mydata <- data.frame(c1, c2)

      # c1: factor => character
      mydata$c1 <- as.character(c1)

      # c3 = paste0(c1, c2)
      mydata <- transform(mydata, c3 = ifelse(is.na(c2) == TRUE, c1, paste0(c1, c2)))