본 정규표현식 교재는 Software Carpentry Regular Expression을 번역한 것입니다. 기본적인 프로그래밍 개념을 이미 이해하고 있고, 파이썬 기본구성요소에 친숙하거나 신속히 숙달할 수 있음을 가정한다. 또한, 데이터 사이언스 R 언어도 동일한 사항을 다루고 있어 파이썬 코드를 R코드로 변역을 하였고 텍스트에 정규표현식 교육을 위해서 개발된 R 학습교재와 팩키도 함께 반영한 것입니다.

정규표현식(Regular Expression)은 연관된 문자열 집합을 매칭하는 패턴이다. 정규표현식이 매칭에 실패하는 패턴도 있지만, 레거시 텍스트 파일에서 정보를 추출할 때, 프로그래머 대부분이 사용하는 강력한 도구다.

주요점(key points)

  • 정규표현식은 문자열로 작성한다(따라서, 표기법이 다소 세련되지 못하다).
  • 알파벳과 숫자는 그 자체로 매칭되고, 한글도 매칭된다.
  • 반복되는 문자에 대해 *, +, ? 특수기호를 사용한다.
  • |을 사용해서 또는 혹은 문자 집합을 매칭한다.
  • 괄호를 사용해서 문자열을 집단으로 묶고, 매칭되는 정보를 추출한다.
  • 정규표현식 라이브러리를 사용해서 매칭되는 모든 것을 찾고, 문자열을 바꾸고, 기타 연산작업을 수행한다.

1 소프트웨어 카펜트리 V4 동영상

소개 및 간단한 패턴 연산자 동작원리
추가 패턴 참고문헌 사례

2 자주 사용되는 정규식 1 2 3

자주 사용되는 한국어 정규표현식을 차근차근 정리해보자.

* 전자우편 주소: /^[a-z0-9_+.-]+@([a-z0-9-]+\.)+[a-z0-9]{2,4}$/  
* URL: /^(file|gopher|news|nntp|telnet|https?|ftps?|sftp):\/\/([a-z0-9-]+\.)+[a-z0-9]{2,4}.*$/  
* HTML 태그 - HTML tags: /\<(/?[^\>]+)\>/  
* 전화 번호 - 예, 123-123-2344 혹은 123-1234-1234: /(\d{3}).*(\d{3}).*(\d{4})/  
* 날짜 - 예, 3/28/2007 혹은 3/28/07: /^\d{1,2}\/\d{1,2}\/\d{2,4}$/  
* jpg, gif 또는 png 확장자를 가진 그림 파일명: /([^\s]+(?=\.(jpg|gif|png))\.\2)/  
* 1부터 50 사이의 번호 - 1과 50 포함: /^[1-9]{1}$|^[1-4]{1}[0-9]{1}$|^50$/  
* 16 진수로 된 색깔 번호: /#?([A-Fa-f0-9]){3}(([A-Fa-f0-9]){3})?/  
* 적어도 소문자 하나, 대문자 하나, 숫자 하나가 포함되어 있는 문자열(8글자 이상 15글자 이하) - 올바른 암호 형식을 확인할 때 사용될 수 있음: /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,15}/  

3 R 정규표현식

3.1 텍스트 헬로월드

가장 먼저 R 정규표현식으로 작업할 텍스트 데이터를 R 작업 환경으로 가져온다.

Lucy Park (박은정), 2015-08-29, “한국어와 NLTK, Gensim의 만남 (부제: 영화 리뷰를 컴퓨터가 이해할 수 있는 형식으로 표현해서 센티멘트 분석하기)”, PyCon Korea 2015 네이버 영화 리뷰 데이터를 자져와서 작업을 시작해 본다.

데이터프레임에서 텍스트, 즉 영화리뷰 20개만 뽑아 텍스트 벡터로 변환시킨 후 stringr 팩키지의 정규표현식 pattern=을 적용시켜서 “영화”만 선택한다.

## 팩키지와 재현가능하도록... 난수 seed 고정
library(tidyverse)
library(glue)
set.seed(777)

## 데이터 가져와서 20개 리뷰만 추출
review <- read_delim('https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt', delim="\t") %>% 
  sample_n(20)

## 데이터프레임으로 ... 텍스트 벡터로 변환
review %>% 
  pull(document) %>% 
  str_view(pattern="영화")

3.2 특정 단어 추출

검색어(“영화”)만 포함된 영화리뷰를 뽑아보자. 이를 위해서 str_detect() 함수 내부에 정규표현식을 전달시켜 TRUE/FALSE 마스크(mask)를 생성시킨다. 그리고 나서, 텍스트 벡터에 마스크 벡터를 넣어 TRUE만 즉, “영화” 단어가 포함된 애들만 추출한다.

(word_mask <- str_detect(review$document, "영화"))
 [1]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE
[13] FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE
review$document[word_mask]
[1] "정말. 좋은 영화였습니다 감동!"                                                                                                                                                                                                                                
[2] "왜이리 평점이 낮지..베드신만을 생각하지말고전체적인 영화스토리에선 너무 재미있었다.영상도 너무 이뻣고.마지막 영상하나하나 기억에 남는다"                                                                                                                      
[3] "뭐하자는 플레이냐. 포르노 배우로 애로영화를 찍는 사치는 뭐냐?"                                                                                                                                                                                                
[4] "케이블로 막 봤는데 재미지다~ 요새 할리우드 액션 영화에선 없는 맛이 있음!"                                                                                                                                                                                     
[5] "정말 이영화가 6점대라니ㅠ분이 옛날시절 사랑얘기 잠깐나오는데 왜이렇게 슬플까요 그리고 중간중간 웃기부분도 있고..정말 천국이 있다면 이런곳일까요?천국이있다면 저런모습이였으면좋겠어요 지금 하늘에 있을 아이들이나 많은 사람들이 하늘에서라도 편햇으면좋겟네요"
[6] "OOO기영화"                                                                                                                                                                                                                                                    

이제 이를 확장시켜 전체 영화 리뷰 중 “영화” 단어가 포함된 리뷰는 몇개나 될까? 전체 데이터를 가져와서 앞서 적용시킨 알고리즘으로 마스크를 생성하는데, 재미있는 사실은 R에서 TRUE는 1, FALSE는 0으로 내부적으로 자연수로 저장되는 성질을 이용하여 간단히 mean() 함수를 적용시키면 평균을 구할 수 있다. filter() 함수로 결측값이 있는지 확인해보자. 이런 연유로 mean(..., na.rm=TRUE)가 사용된 것이다.

## 전체 영화 리뷰 데이터
full_review <- read_delim('https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt', delim="\t")

word_mask <- str_detect(full_review$document, "영화")

round(mean(word_mask, na.rm = TRUE) * 100, 1)
[1] 31.3
## NA 확인
full_review %>% 
  filter(is.na(document))
# A tibble: 5 x 3
       id document label
    <dbl> <chr>    <dbl>
1 2172111 <NA>         1
2 6369843 <NA>         1
3 1034280 <NA>         0
4 5942978 <NA>         0
5 1034283 <NA>         0

3.3 정규표현식 기초

정규표현식(regular expression)을 사용하려면 키보드에 있는 특수문자를 이용한다는 사실 하나와 이를 사용해서 텍스트의 패턴을 만들어서 원하는 작업을 수행할 수 있다는 점이다. 가장 기본이 되는 정규표현식 기초에 대한 내용을 살펴보자.

특수 문자 의미
^ 라인 혹은 문자열의 시작을 의미함
$ 라인 혹은 문자열의 끝을 의미함
. 문자, 숫자, 공백문자를 가리지 않고 어떤 것이라도 매칭함을 의미함
\\ 특수문자(^, $, ., …) 을 매칭하고자 할 때 정규표현식 탈출(escape)하는 의미

예를 들어, 영화.스토리 텍스트가 있는 첫번째 단어를 뽑고자 할 경우 ^으로 위치를 지정하고 .으로 앞서 의미하는 문자, 숫자, 공백문자 아무것이나 하나를 뽑아낸다. 즉, ^. 을 조합하게 되면 첫번째 오는 아무 문자나 추출시킨다. .$는 아무 문자나 하나 마지막에 온다는 의미가 된다. 주의할 점은 정규표현식에 사용되는 특수문자는 \\.와 같이 역슬래쉬를 두번 사용해야 한다. 이점이 일반 정규표현식과 차이가 나는 점이다.

정규표현식 R 코드 실행 결과
str_match("영화.스토리", "^.")
str_match("영화.스토리", ".$")
str_match("영화.스토리", ".")
str_match("영화.스토리", "\\.") .
str_match("영화스토리", "\\.") NA

3.4 텍스트 유형별 작업

다음으로 텍스트는 문자 클래스를 갖는데 다음과 같이 구분하면 좋을 듯 싶다. 한글, 영문, 일본어, 숫자, 특수문자 등 다양한 문자 클래스가 존재하지만, 이를 추출하기 위한 정규표현식 패턴도 함께 존재한다.

  • 한글: [ㄱ-ㅎ가-힣]
    • 한글 자음: ㄱ-ㅎ
    • 한글 모음: ㅏ-ㅣ
    • 한글: 가-힗
  • 일본어: [あ-んァ-ソ]
    • 히라가나: あ-ん
    • 카타카나: ァ-ソ
  • 영문자: a-z, A-Z, [:word:], [:alpha:]
    • 영어모음: [aeiou]
    • 영어자음: [b-df-hj-np-tv-z]
  • 숫자: 0-9, [:digit:]
  • 특수 문자: !@#$%^&*(),.?":{}|<>, [:punct:]
  • 한자: \\p{script=Han} 4

가장 관심가는

str_match_all('원문: 其戰勝不復, 而應形于無窮 \n 
              읽는 법: 기전승불복 이응형어무궁 \n
              뜻: 그렇게 달성되는 전승에는 동일한 양상이 반복되지 않으며, 형세에 따라 융통성 있게 적용되는 방법이 무궁무진한 것이다.', pattern = "\\p{script=Han}+")
[[1]]
     [,1]          
[1,] "其戰勝不復"  
[2,] "而應形于無窮"
문자 클래스 실제 예제 R 코드 실행 결과
\\d, [:digit:] 0, 1, 2,... str_match_all("저는 37살 입니다.", "\\d") 3, 7
\\w, [:word:] V, i, c, t, o, r str_match_all("Victor", "\\w") V, i, c, t, o, r
[ㄱ-ㅎ가-힣] 저" "는" "살" "입" "니" "다" str_match_all("저는 37살 입니다.", "[ㄱ-ㅎ가-힣]") 저, 는, 살, 입, 니, 다
\\s " " " " str_match_all("저는 !@#$%^&* 입니다.", "\\s") ,
[:punct:] "!" "@" "#" "%" "&" "*" "(" ")" "," "." "?" "\"" ":" "{" "}" str_match_all('!@#$%^&*(),.?":{}|', "[:punct:]") !, @, #, %, &, *, (, ), ,, ., ?, ", :, {, }

3.5 반복

.은 임의 문자 하나를 의미했다. \\d 이것도 마찬가지로 숫자 하나를 의미한다. 전화번호는 일반적으로 000-0000-0000와 같은 형태를 띄게 된다. 즉 숫자를 3번, - 4번, - 4번과 같은 순서로 쭉 매칭이 되어야 한다. 정규표현식을 \\d{3}-\\d{4}-\\d{4}와 같이 패턴을 적게 되면 "010-7622-2319" 번호를 추출할 수 있게 된다.

phone_number <- c("0221120001", "02-2112-2327", "0107775342", "010-7622-2319", "010-4352-2319")

str_match_all(phone_number, pattern = "\\d{3}-\\d{4}-\\d{4}") %>% unlist
[1] "010-7622-2319" "010-4352-2319"

이번에 약간 틀어서 핸드폰 뿐만 아니라 유선 전화번호도 추출하고자 하면 어떨까? \\d{2,3}와 같이 정의하면 숫자 두자리부터 3자리만 패턴을 매칭시킨다.

str_match_all(phone_number, pattern = "\\d{2,3}-\\d{4}-\\d{4}") %>% unlist
[1] "02-2112-2327"  "010-7622-2319" "010-4352-2319"
구문 의미
\\w{3} 정확히 연속한 문자 3개 매칭
\\w{1,3} 최소 문자 1개부터 최대 문자 3개 매칭
\\w{3, } 최소 문자 3개 이상 최대값 없이 최대한 매칭
\\w+ +는 1개 이상 문자를 매칭
\\w* *는 0개 이상 문자를 매칭

4 고급 정규표현식

4.1 여집합(inversion)

\(\{1, 2, 3, 4, 5, 6\}\)을 전체집합이라 하면, \(\{2, 3, 4, 5\}\)의 여집합은 \(\{1, 6\}\)이 되는 것처럼 매칭된 패턴을 도치하여 여집합을 구하고자 하는 경우 사용한다.

예를 들어, \\D는 숫자에 대한 여집합을 구하고자 할 때 사용하는 패턴이다. 숫자를 뺀 나머지 모든 문자를 추출해보자.

mixed_string <- "한글과 English가 fancy하게 숫자도 777개나 뒤섞여 無窮 있어요"

str_match_all(mixed_string, "\\D") %>% unlist %>% str_c(collapse = "")
[1] "한글과 English가 fancy하게 숫자도 개나 뒤섞여 無窮 있어요"

숫자만 추출하고자 할 때는 [^]와 같이 [] 내부에 ^을 넣게 되면 a-zA-Z가-힣을 만족시키지 않는 패턴을 반환시킨다. 즉, 한자와 숫자만 추출시킨다.

str_match_all(mixed_string, " [^a-zA-Z가-힣]+") %>% unlist %>% str_c(collapse = "")
[1] " 777 無窮 "

[^a-zA-Z]+와 같이 패턴을 정의하게 되면 영문 대소문자를 제외한 나머지 문자를 모두 추출할 수 있다.

str_match_all(mixed_string, "[^a-zA-Z]+") %>% unlist %>% str_c(collapse = "")
[1] "한글과 가 하게 숫자도 777개나 뒤섞여 無窮 있어요"

4.2 사용자 정의 패턴

[] 꺽쇄괄호 내부에 정규표현식을 적게 되면 사용자 정의 정규표현식을 사용할 수 있다. 즉, \\d, \\w, \\D 등은 마치 내장함수처럼 기본으로 지원되는 정규표현식이고, []을 사용하게 되면 임의 사용자 정의 정규표현식을 작성할 수 있다. 숫자 혹은 공백문자(\\s)를 찾는 사용자 정의 정규표현식을 다음과 같이 작성할 수 있다.

str_match_all(mixed_string, "[\\s\\d]") %>% unlist
 [1] " " " " " " " " "7" "7" "7" " " " " " "

4.3 단어경계

특정 단어 예를 들어, “왕가위”를 매칭하고자 할 때, 양 옆에 뭔가 단어가 붙은 경우 이를 무시하고 특정 단어만 단어경계를 가진 문자열만 추출하고자 할 때 유용하다. 이를 위해서 \\b를 추출하고자 하는 문자열 앞뒤로 붙인다.

boundary_text <- c("왕가위, 장국영, 비포 선라이즈. 서울의 하얀 밤을 떠다니는 외로운 그들..그리고 우리... 왕가위짱, 왕가위, 정통왕가위짱, 왕가 왕가윙 왕가위ㄴ 마지막 왕가위 ")

단어 경계 없음

str_view_all(boundary_text, "왕가위")

단어 경계 \\b 적용

str_view_all(boundary_text, "\\b왕가위[:punct:]?\\b")

4.4 파이프와 물음표(?)

파이프(|)와 물음표(?)를 사용하게 되면 좀더 다양한 패턴을 매칭시킬 수가 있다. 파이프(|)는 혹은(or)에 해당되어 A 혹은 B 혹은 C 와 같은 부울식을 구현할 수 있다. 예를 들어, 영화배우 이름이 포함된 리뷰를 찾고자 할 때, 영화 배우면을 파이프(|)로 쭉 연결시키게 되면 영화배우가 포함된 리뷰를 걸러낼 수 있다.

movie_star <- str_detect(review$document, "덴젤|왕가위|장국영")

review$document[movie_star]
[1] "왕가위, 장국영, 비포 선라이즈. 서울의 하얀 밤을 떠다니는 외로운 그들..그리고 우리..."
[2] "역시 난 덴젤의 연기가 좋다. 점점 주름이 늘어나는 모습에 슬플뿐ㅠㅠ"                  

경기, 경기도는 같은 시도지만, 이를 파이프(|) 연산자를 사용해서 작업할 경우 코드가 길어진다. 선택(?)을 넣게 되면 간결한 코드를 남길 수가 있다.

provinces <- c("경기도", "경기", "강원", "강원도")

파이프(|)

str_match_all(provinces, "[가-힣]{2}도|[가-힣]{2}") %>% unlist
[1] "경기도" "경기"   "강원"   "강원도"

?로 선택옵션을 줌

str_match_all(provinces, "[가-힣]{2}도?") %>% unlist
[1] "경기도" "경기"   "강원"   "강원도"

4.5 욕심쟁이와 게으른 연산자

다음과 같이 “한국” 단어가 여러버 나올 경우 “."와 같은 정규표현식은 욕심쟁이라 최대한 많이 "한국"을 매칭하게 된다. 반면에 ".?” 와 같이 “?”을 추가하게 되면 게으른 연산이 실행되어 매칭되는 첫번째에서 멈추게 된다.

news <- c("한국은 미국과 중국의 무역갈등으로 인해 힘든 시간을 보냈지만, 한국과 일본의 경제문제도 또 다른 현안으로 떠오르고 있다.")

str_view(news, ".*한국")
str_view(news, ".*?한국")

5 glue 팩키지

glue를 정규표현식, 데이터프레임과 엮어서 사용하게 될 경우 복잡한 많은 작업을 수월하게 진행시킬 수 있다. 전자우편 주소 벡터가 있을 때, glue() 함수를 사용하게 되면 코드를 간결하게 하여 데이터베이스에 저장된 전자우편 주소로부터 예를 들어 메일을 보내는데 도움을 받을 수 있다.

library(glue)

email <- c("kwangchun@gmail.com", "lee@live.com", "kwang@naver.com")

glue("TO: {email}")
TO: kwangchun@gmail.com
TO: lee@live.com
TO: kwang@naver.com

5.1 몇가지 사례

앞선 사례는 글로벌 환경에서 선언된 변수를 glue() 함수에서 가져와서 문자열을 새로 생성했다. glue() 내부에서 변수를 선언하는 것도 가능하다.

glue("현재 온도: {temp}",
      temp=38.5)
현재 온도: 38.5

“{}” 내부에 표현식을 넣어서 연산작업을 수행하는 것도 가능하다.

for(i in 1:9) {
    cat(glue(" 2 * {i} = {2*i}"), "\n")
}
 2 * 1 = 2 
 2 * 2 = 4 
 2 * 3 = 6 
 2 * 4 = 8 
 2 * 5 = 10 
 2 * 6 = 12 
 2 * 7 = 14 
 2 * 8 = 16 
 2 * 9 = 18 

glue() 함수와 데이터프레임을 조합하게 되면 정형데이터로부터 텍스트 문장을 생성할 수 있다.

baseball <- tribble(~"team", ~"win", ~"lose",
                     "해태", 57, 56,
                     "롯데", 55, 53,
                     "넥슨", 71, 67)

baseball %>% 
  mutate(text = glue("올시즌 {team}는 {win}승 {lose}패를 기록했다"))
# A tibble: 3 x 4
  team    win  lose text                              
  <chr> <dbl> <dbl> <glue>                            
1 해태     57    56 올시즌 해태는 57승 56패를 기록했다
2 롯데     55    53 올시즌 롯데는 55승 53패를 기록했다
3 넥슨     71    67 올시즌 넥슨는 71승 67패를 기록했다

glue_collapse() 함수는 벡터를 스칼라로 변환시키는 역할을 하는 유용한 함수다. 예를 들어 영화배우가 텍스트 벡터로 된 경우 이를 glue_collapse() 함수로 사용할 경우 출연배우명을 쭉 붙여 텍스트 스칼라로 만들 수 있다.

movie <- "유열의 음악앨범"
actors <- c("송강호", "김고은", "정해인")

glue(
  "{movie} 출연배우: ", 
  glue_collapse(actors, 
              sep=", ",
              last=",  그리고 ")
)
유열의 음악앨범 출연배우: 송강호, 김고은,  그리고 정해인

baseball 데이터프레임의 변수를 하나 뽑아서 glue_collapse() 함수에 넣을 경우 텍스트 스카라로 만들 수 있다.

glue_collapse(baseball$team, sep=" ")
해태 롯데 넥슨

5.2 정규표현식 + glue_collapse()

국가명을 country 벡터에 저장한 후, 앞서 학습한 glue_collapse() 함수와 결합하여 country_regex 정규표현식 벡터로 만든 후 뉴스에 언급된 국가만 추추하는 것이 가능하다.

country <- c("한국", "중국", "일본")

news <- c("한국은 미국과 중국의 무역갈등으로 인해 힘든 시간을 보냈지만, 한국과 일본의 경제문제도 또 다른 현안으로 떠오르고 있다.")

(country_regex <- glue_collapse(country, sep="|"))
한국|중국|일본
str_view_all(news, country_regex)

6 텍스트 → 데이터프레임

텍스트가 깔끔하게 준비된 경우, 정규표현식을 glue_collapse() 함수로 작성할 경우 가독성 높은 코드를 얻을 수 있어, 나중에 코드를 다시 볼 때 보기 좋게 된다.

baseball_text <- c("올시즌 해태는 57승 56패를 기록했다
                    올시즌 롯데는 55승 53패를 기록했다
                    올시즌 넥슨는 71승 67패를 기록했다")

(baseball_regex <- glue_collapse(c(
                "team" = "[가-힣]+",
                "\\s+",
                "win" = "\\d+", "승",
                "\\s+",
                "lose" = "\\d+", "패")))
[가-힣]+\s+\d+승\s+\d+패
str_match_all(baseball_text, baseball_regex) %>% unlist
[1] "해태는 57승 56패" "롯데는 55승 53패" "넥슨는 71승 67패"

6.1 그룹(group)

앞서 프로야구 경기팀의 승패를 기록한 텍스트에 오류가 있어, 승과 패가 뒤바뀐 일이 발생했다. 이를 바로잡고자 그룹(group)을 동원해서 문제를 풀어보자. 정규표현식에 ()를 사용하면 그룹을 패턴에 부여할 수 있게 된다.

(regex_group <- glue_collapse(c(
                "team" = "([가-힣]+)",
                "\\s+",
                "win" = "(\\d+)", "승",
                "\\s+",
                "lose" = "(\\d+)", "패")))
([가-힣]+)\s+(\d+)승\s+(\d+)패
str_match_all(baseball_text, regex_group)
[[1]]
     [,1]               [,2]     [,3] [,4]
[1,] "해태는 57승 56패" "해태는" "57" "56"
[2,] "롯데는 55승 53패" "롯데는" "55" "53"
[3,] "넥슨는 71승 67패" "넥슨는" "71" "67"

\\1, \\2, \\3을 지칭해서 위치를 바꿀 수 있다.

(regex_group <- glue_collapse(c(
                "team" = "([가-힣]+)",
                "\\s+",
                "win" = "(\\d+)", "승",
                "\\s+",
                "lose" = "(\\d+)", "패")))
([가-힣]+)\s+(\d+)승\s+(\d+)패
baseball_text
[1] "올시즌 해태는 57승 56패를 기록했다\n                    올시즌 롯데는 55승 53패를 기록했다\n                    올시즌 넥슨는 71승 67패를 기록했다"
str_replace_all(baseball_text, regex_group, "\\1 \\3승 \\2패")
[1] "올시즌 해태는 56승 57패를 기록했다\n                    올시즌 롯데는 53승 55패를 기록했다\n                    올시즌 넥슨는 67승 71패를 기록했다"

6.2 DF + regex: extract

데이터프레임과 정규표현식이 만나는 교차점에 extract() 동사가 있다. 먼저 특정 단어를 하나 뽑아 mutate() 동사로 새로운 변수를 생성하는 사례를 살펴보자.

review %>% 
  mutate(is_movie = str_match(document, "영화")) 
# A tibble: 20 x 4
         id document                                          label is_movie[,1]
      <dbl> <chr>                                             <dbl> <chr>       
 1  9212028 정말. 좋은 영화였습니다 감동!                         1 영화        
 2  1659748 아놔 진짜 더럽게 재미없네                             0 <NA>        
 3  1077998 내 안에도 천사가 있을까.                              1 <NA>        
 4  8747167 조잡스런 장면과 억지스런 설정에 사라진 웃음, 쌓여가는 짜증…     0 <NA>        
 5  6110214 누구나 공감가는 이야기를 소소하게 잘 그려냈네요. 보면서 엄청 울었네요.…     1 <NA>        
 6 10276348 인간들아 욕 좀 그만해라 금발의 여 주인공 쌕시하고 예뻐서 시종 눈을 뗄 수 없는 명작…     1 <NA>        
 7  8241466 귀엽고 따뜻한 예능..☆ 재미있어요! 샘들 힘내세요~~~~…     1 <NA>        
 8  7631300 왜이리 평점이 낮지..베드신만을 생각하지말고전체적인 영화스토리에선 너무 재미있었다.영상…     1 영화        
 9  5453346 뭐하자는 플레이냐. 포르노 배우로 애로영화를 찍는 사치는 뭐냐?…     0 영화        
10  6682980 평점이 아직도 올라온다 ㄷㄷㄷ 째든 이건 내가봐도 명작임. 아주 어렸을적에 봤는데 그때…     1 <NA>        
11  3775688 아... 진짜.... 시간 날렸당                            0 <NA>        
12  9677551 케이블로 막 봤는데 재미지다~ 요새 할리우드 액션 영화에선 없는 맛이 있음!…     1 영화        
13  4649132 난 1편을 생일에 보고 2편도 생일에 봐서 꽤 재미있는듯?…     1 <NA>        
14  8693988 정말 이영화가 6점대라니ㅠ분이 옛날시절 사랑얘기 잠깐나오는데 왜이렇게 슬플까요 그리고 …     1 영화        
15  7616081 다소 지루하다. 하지만 담고 있는 내용이 워낙 훌룡하다. 평점 조절이 필요하다.…     1 <NA>        
16  2183087 OOO기영화                                             0 영화        
17   728878 마지막 장면은 정말 잊지못함..                         1 <NA>        
18  7285865 왕가위, 장국영, 비포 선라이즈. 서울의 하얀 밤을 떠다니는 외로운 그들..그리고 우리…     1 <NA>        
19  9394907 미이라, 잃어버린세계를 찾아서..등등 좋아하니까        1 <NA>        
20  8189082 역시 난 덴젤의 연기가 좋다. 점점 주름이 늘어나는 모습에 슬플뿐ㅠㅠ…     1 <NA>        

그런데, 이번에는 “평점”, “영화” 두 단어를 동시에 찾아 새로운 변수로 넣는 경우를 상정해보자. 이를 위해서 필요한 동사가 extract()가 되고 앞서 학습한 정규표현식 그룹의 개념이 포함된다. 다음과 같이 review 데이터프레임에서 document 컬럼을 뽑아 그룹으로 정규표현식을 구성하고 이를 into =의 새로운 칼럼으로 넣는다.

review %>% 
  extract(col = "document",
          into = c("is_score", "is_movie"),
          regex = "(평점).*?(영화)",
          remove=TRUE) 
# A tibble: 20 x 4
         id is_score is_movie label
      <dbl> <chr>    <chr>    <dbl>
 1  9212028 <NA>     <NA>         1
 2  1659748 <NA>     <NA>         0
 3  1077998 <NA>     <NA>         1
 4  8747167 <NA>     <NA>         0
 5  6110214 <NA>     <NA>         1
 6 10276348 <NA>     <NA>         1
 7  8241466 <NA>     <NA>         1
 8  7631300 평점     영화         1
 9  5453346 <NA>     <NA>         0
10  6682980 <NA>     <NA>         1
11  3775688 <NA>     <NA>         0
12  9677551 <NA>     <NA>         1
13  4649132 <NA>     <NA>         1
14  8693988 <NA>     <NA>         1
15  7616081 <NA>     <NA>         1
16  2183087 <NA>     <NA>         0
17   728878 <NA>     <NA>         1
18  7285865 <NA>     <NA>         1
19  9394907 <NA>     <NA>         1
20  8189082 <NA>     <NA>         1

6.3 사례 5

actors <- c("덴젤", "장국영", "왕가위")

actor_pattern <- glue_collapse(actors, sep = "|")

surronding_text <- "([\\w[:punct:]]+\\s){0,5}"

actor_surrounding_text <- glue(
  "{surronding_text}?({actor_pattern})\\s?{surronding_text}"
)

str_extract_all(
  review$document,
  pattern = actor_surrounding_text
) %>% unlist
[1] "왕가위, 장국영, 비포 선라이즈. 서울의 "  
[2] "역시 난 덴젤의 연기가 좋다. 점점 주름이 "