텍스트 사전 처리 : 온라인 텍스트 정리 (즉, 트윗, 댓글, 블로그)

  1. 공백
  2. 구두점
  3. 숫자
  4. 불용어
  5. 영어 이외의 텍스트 (ASCII) https://en.wikipedia.org/wiki/UTF-8: 알파벳 문자를 컴퓨터에 입력하기 위해 고안된 인코딩 방식
  6. URLs: 웹 address

“stringr’ 패키지 II

앞서 배운 stringr패키지의 함수에 대해서 간단히 복습해봅시다.

함수 설명 유사한 기본 함수
str_length() 문자의 수 nchar()
str_split() 문자열 분리 strsplit()
str_c() 문자열 연결 paste()
str_detect() 문자열의 패턴 유무 인식하여 결과(T/F)를 반환합니다) none
str_view_all() 매칭된 문자열 모두를 보여줌 none

stringr 패키지에 있는 모든 함수는 "str_"로 시작하고 뒤에는 수행 작업과 관련된 용어가 따라옵니다.

패턴 매칭을 위한 유용한 stringr 함수

대부분의 stringr 함수는 처리하고자 하는 특정 패턴의 텍스트를 찾는데 필요한 정규 표현식과 함께 쓰입니다.

정규 표현식에 대해선 앞으로 자세히 배울 것입니다. 지금은 우선,

다음은 텍스트 사전처리에 유용한 stringr의 함수들을 좀 더 소개해드리겠습니다:

함수 정의
str_trim() 문자열의 선/후 공백을 제거합니다.
str_which() 문자열 벡터에서 일치하는 텍스트 패턴의 모든 위치를 반환합니다.
str_extract() 각 문자열 요소에서 처음으로 일치하는 텍스트 패턴을 추출합니다.
str_extract_all() 각 문자열 요소에서 일치하는 모든 텍스트 패턴을 추출합니다.
str_replace() 각 문자열 요소에서 처음으로 일치하는 텍스트 패턴을 원하는 문자열로 바꿉니다.
str_replace_all() 각 문자열에서 일치한는 모든 텍스트 패턴을 원하는 문자열로 바꿉니다.

그럼 이런 stringr 패키지의 기능을 바탕으로 지난 시간에 텍스트 전처리를 진행한 과정을 살펴봅시다.

텍스트 전처리

library(pdftools)
library(stringr)
bts_text <- pdf_text("BTS_(band).pdf")
class(bts_text)
## [1] "character"
bts_text[1]
## [1] "BTS (band)\r\nBTS (Hangul:                  ; RR: Bangtan Sonyeondan), also known as\r\n                                                                                                   BTS\r\nthe Bangtan Boys, is a seven-member South Korean boy band formed\r\nby Big Hit Entertainment. They debuted on June 12, 2013 with the\r\nsong \"No More Dream\" from their first album 2 Cool 4 Skool. They\r\nwon several New Artist of the Year awards for the track, including at\r\nthe 2013 Melon Music Awards and Golden Disc Awards and the 2014\r\nSeoul Music Awards. The band continued to rise to widespread\r\nprominence with their subsequent albums Dark & Wild (2014), The\r\nMost Beautiful Moment in Life, Part 2 (2015) and The Most Beautiful\r\nMoment in Life: Young Forever (2016), with the latter two entering the\r\n                                                                               BTS at the 32nd Golden Disk Awards on\r\nU.S. Billboard 200.[4] The Most Beautiful Moment in Life: Young\r\n                                                                                            January 10, 2018\r\nForever went on to win the Album of the Year award at the 2016\r\n                                                                            From left to right: V, Suga, Jin, Jungkook, RM,\r\nMelon Music Awards.[5]\r\n                                                                                            Jimin and J-Hope\r\nTheir second full album, Wings (2016),[6] peaked at number 26 on the                   Background information\r\nBillboard 200, which marked the highest chart ranking for a K-pop\r\n                                                                           Also known as Bangtan Boys · Bulletproof\r\nalbum ever.[7] In their native South Korea, Wings became the best\r\n                                                                                              Boy Scouts · Beyond The\r\nselling album in the Gaon Album Chart history at the time.[8] The\r\n                                                                                              Scene · Bangtan Sonyeondan\r\nalbum went on to sell more than 1.5 million copies, making it BTS'\r\n                                                                           Origin             Seoul, South Korea\r\nfirst \"million seller\",[9] and the group was subsequently awarded Artist\r\nof the Year at the 2016 Mnet Asian Music Awards.[10] The group's           Genres             K-pop · hip hop[1] · R&B · EDM\r\nnext release, Love Yourself: Her (2017),[11] debuted at number seven       Years active       2013<U+2013>present\r\non the Billboard   200,[12]  marking the highest rank for an Asian artist  Labels             Big Hit Entertainment · Pony\r\nin history. The group also managed to debut on the Billboard Hot 100                          Canyon · Def Jam Japan[2] ·\r\nfor the first time with the album's title track, \"DNA\", which entered at                      Columbia[3]\r\nnumber 85 and peaked at number 67.[13] Another track from the\r\n                                                                           Website            bts.ibighit.com\r\nalbum, \"Mic Drop\", was remixed by Steve Aoki with a feature by\r\nDesiigner and peaked at number 28 on the Billboard Hot 100. Both           Members            Jin\r\ntracks went on to be certified Gold by the Recording Industry                                 Suga\r\nAssociation of America, a first for any Korean act ever.[14] The album\r\n                                                                                              J-Hope\r\nsold over 1.2 million copies on South Korea's Gaon Album Chart in its\r\nfirst month, reclaiming its title as the best selling album in the chart's\r\n                                                                                              RM\r\nhistory and becoming the biggest selling Korean album by month in 16                          Jimin\r\nyears, coming second to G.o.d's album in      2001.[15] BTS was awarded                       V\r\nthe Mnet Asian Music Award for Artist of the Year for the second year                         Jungkook\r\nin a row in 2017.[16] Their third full album Love Yourself: Tear (2018),\r\ndebuted at number one on the Billboard 200,[17] making them the only                          Korean name\r\nK-Pop act to achieve this feat so far. Since their debut, they have sold   Hangul\r\nan estimated 7 million albums worldwide.[18][19]\r\n                                                                           Hanja                 彈少年團\r\nKnown for their large social media presence, BTS were listed by            Transcriptions\r\nForbes as the most retweeted artist on Twitter in March 2016.[20]\r\n                                                                           Revised Romanization Bangtan Sonyeondan\r\nFollowing that, Twitter launched its first ever K-pop Twitter emoji\r\n                                                                           McCune<U+2013>Reischauer          Pangt'an Sony<U+01D2>ndan\r\nfeaturing BTS in May.[21] In October 2016, Billboard placed BTS as\r\nnumber one on their Social 50 chart, making them the first Korean                           Japanese name\r\n"
unlist(str_extract_all(bts_text[1], pattern = " "))[1:30]
##  [1] " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
## [18] " " " " " " " " " " " " " " " " " " " " " " " " " "
bts_text_token <- str_split(unlist(bts_text), pattern = " ")
unlist(bts_text_token)[1:30]
##  [1] "BTS"           "(band)\r\nBTS" "(Hangul:"      ""             
##  [5] ""              ""              ""              ""             
##  [9] ""              ""              ""              ""             
## [13] ""              ""              ""              ""             
## [17] ""              ""              ""              ""             
## [21] ";"             "RR:"           "Bangtan"       "Sonyeondan)," 
## [25] "also"          "known"         "as\r\n"        ""             
## [29] ""              ""
bts_sent_trim <- unlist(bts_text_token)[str_length(unlist(bts_text_token))>0]
bts_sent_trim[1:30]
##  [1] "BTS"            "(band)\r\nBTS"  "(Hangul:"       ";"             
##  [5] "RR:"            "Bangtan"        "Sonyeondan),"   "also"          
##  [9] "known"          "as\r\n"         "BTS\r\nthe"     "Bangtan"       
## [13] "Boys,"          "is"             "a"              "seven-member"  
## [17] "South"          "Korean"         "boy"            "band"          
## [21] "formed\r\nby"   "Big"            "Hit"            "Entertainment."
## [25] "They"           "debuted"        "on"             "June"          
## [29] "12,"            "2013"
  1. 공백 : 해결!

그러나 구두점과 숫자는 어떻게 되었나요? 그리고 공백 문자는?

  1. 구두점 : 다양한 구두점의 종류, 어떤 구두점을 제거해야 할지를 고민. 일일이 지정하기 귀찮고, 소모적.

  2. 숫자 : 어떤 숫자를 제거해야 할지를 지정할 수 없습니다. 등장하는 숫자 모두를 일일이 지정하기 힘듬.

모든 구두점 또는 모든 숫자를 찾아내서 한꺼번에 지워줄 수 있는 방법?

이 문제를 해결하기 위해, 정규 표현에 대해 배워봅시다.

정규 표현식

지금까지 R에서 텍스트를 다루고 처리하기 위한 몇가지 기본 기능을 학습하였습니다. 하지만 이 과정에서 우리는 문자열을 자유롭게 다룰 수 있어야 한다는 점도 느낌. 그래서 우리는 정규 표현식(regular expressions)에 대해 알아볼 것입니다.

정규 표현식이란 무엇일까요?

정규 표현식이라는 용어는 낯설게 느껴지고 그 예는 복잡하게 보일 수 있습니다. 하지만 정규 표현식은 텍스트를 구성하는 방식에 관한 것입니다. 오늘날 우리는 이메일, 문자 메세지, 뉴스 기사, 블로그, 댓글, 트윗과 같은 다양한 디지털 텍스트를 쉽게 접할 수 있죠. 이러한 데이터들은 모두 디지털 기호로 이뤄져 있기 때문에, 우리가 텍스트를 구성하는 방식을 안다면 많은 양의 데이터를 즉 빅데이터를 다룰 수 있는 힘을 갖게 됩니다. 다시 말해, 정규 표현식을 이용해서 디지털 텍스트를 처리할 수 있는 것이죠.

그럼 정규 표현식은 무엇일까요? regular expression, 즉 정규 표현식은 텍스트의 특정 pattern을 찾아내기 위한 특수 문자열입니다. 결국 정규 표현식은 문자열 집합을 매칭하기 위한 기호 집합이죠. 매칭한다는 표현을 앞으로 제가 자주 쓸 텐데요. 이는 정규표현과 맞는 (또는 일치하는) 문자열을 찾아낸다는 의미로 사용될 것입니다.

정규 표현은 영어로 regular expression이지만, 이 용어 자체가 다소 길기 때문에, 흔히 regex 라고 짧게 줄여서 지칭합니다. 저도 이 수업에서 정규 표현과 regex를 혼용해서 사용할 것입니다.

정규표현식은 복잡한 표현 방식으로 보이지만, 사실 프로그래밍 언어는 아닙니다. regex가 프로그래밍 언어처럼 보일지도 모르겠습니다. 왜냐하면 정규 표현은 컴퓨터를 통해 텍스트에서 우리가 원하는 것을 찾아내는데 사용된는 규칙의 집합이기 때문입니다. 그러나 regex는 변수를 지정할 수 없고, 2+2와 같은 계산을 추가할 수 없기 때문에, 프로그래밍 언어라 말할 순 없습니다.

정규식에 들어가기 전에…

정규 표현식은 처음에는 이해하기 어려울 수 있습니다. 무의미한 방식으로 결합된 문자, 숫자 및 구두점이 포함된 문자열을 볼 수 있기 때문입니다. 프로그래밍 및 데이터 분석과 마찬가지로, 정규 표현식을 학습하고 정규 표현식 패턴을 정의하는 데에 시간이 걸리므로 많은 연습이 필요합니다. 그러나 연습을 많이하면 할수록 더 복잡한 패턴을 정의할 수 있고 제거할 수도 있습니다. 그리고 정규 표현식은 Python, Perl, Java와 같은 다른 프로그래밍 언어의 대부분에서 지원되기 때문에 사용에 익숙해지면 매우 유용합니다.

정규 표현식(RegEx)은 어디에 사용될까요?

우리는 정규 표현식을 사용하여 텍스트 작업을 할 것입니다. 가령, 아까 예시에서 언급했던, 구두점, 숫자 그리고 원하지 않는 문자 등을 찾아내서 지우거나 원하는 문자로 바꿔주는 작업을 regex를 이용해서 할 것입니다. 예를 들어, 우리가 전처리하고자 하는 텍스트에서 “centre”라고 표기된 영국식 어휘를 모두 찾아내 미국식 어휘인 “center”로 바궈주는 작업을 할 수 있습니다. 또는 한국을 표기하는 다양한 영어 방식인 “Korea, South”, “Republic of Korea”, “R.O.K”를 “South Korea”등을 찾아내서 동일한 단어로 일치시킬 수 도 있습니다. 더 나아가, 텍스트에 등장하는 구두점과 숫자 표현들, 영어 알파벳 이외의 문자 그리고 웹페이지 주소와 같은 URL을 일일이 지정해서 지워내는 번거로운 작업을 피할 수 있기 때문에 효율적인 텍스트 분석을 가능하게 합니다.

지난 시간에 BTS에 대한 위키피디아의 문서로부터 텍스트를 전처리하여 어휘 빈도수를 계산해서 wordcloud를 만드는 작업을 함께 수행했었죠. 그 과정해서 생겼던 문제점들을 다시한번 생각해 봅시다. 어휘 리스트에서 제거해야 하는 구두점과 숫자, 한자와 한글들이 있었습니다. 그리고 공백으로는 제대로 구별되지 않는 단어들도 있습니다. 예를 들어, South Korea와 같은 단어 말이죠. 그렇다면 이렇게 처리되어야할 문자들을 어떻게 문서에서 발견하고 추출해 낼 수 있을까요?

사실, 우리는 이미 regex를 문자 패턴을 찾는데 사용했습니다. 위키피디아 페이지의 “references” 섹션을 지우기 위해서 문자열 벡터에서 “references”와 일치하는 요소의 위치를 찾아냈었죠. 이때 사용했던 패턴 “references”가 바로 regex 입니다. 마찬가지로, 문서에서 “2017”이라는 숫자와 일치하는 문자열 백터의 요소 위치를 찾고자 한다면, which(str_detect(bts_text_word_main, "2017"))를 입력해서 실행하면 되죠.

그러나, 처리해줘 야 할 텍스트 패턴은 매우 다양할 수 있기 때문에 일일이 모든 경우를 포함시킬 수는 없습니다. 가령, 연도를 가리키는 숫자만 해도 “2018”, “2016”, “2017”에 국한된 것이 아니라 더 많은 다양한 경우가 올 수 있기 때문에, 이러한 규칙을 가진 숫자 표현 모두를 매칭해서 처리해주는 정규 표현이 필요합니다. 이런 방식으로 위키피디아 페이지에서 등장하는 그리고 우리가 지워줘야 하는 모든 숫자 또는 구두점을 찾아서 처리할 수 있죠.

정규식의 기본

다시 한번 강조하자면, 정규 표현식은 특정 텍스트 문자열을 일괄적으로 찾아낼 수 있는 패턴을 구성하는 것이라 할 수 있습니다. 따라서 regex를 사용할 경우 우리가 처리하고자 하는 텍스트 패턴을 모두 찾아낼 수 있습니다. 그리고 stringr 패키지에서 regex pattern을 잘 구성해서 이용하는 것은 그래서 매우 중요합니다.

그러면 어떻게 regex를 구성해야 할까요. 위에서 언급했듯, 패턴 매칭의 가장 간단한 방법은 특정 문자열 그대로를 찾도록 만드는 것입니다. 예를 들어, 지난 시간에 우리는 위키피디아 페이지의 텍스트 문서에서 “references”라는 단어의 문자열을 매칭하고자 하는 문자 패턴 그대로의 regex를 사용해서 그 위치를 검색했습니다.

하지만 대부분의 경우 보다 복잡한 구조의 정규식 패턴을 만들어야 할 필요가 생깁니다. 예를 들어서 위키피디아 페이지에서 모든 숫자, 또는 모든 구두점, 또는 해시 태그나 url 또는 영어가 아닌 글자들을 모두 매칭해서 처리하려면 어떻게 해야 할까요? 이러한 필요를 충족시키는 regex 구성 방법에 대해서 설명드리도록 하겠습니다.

리터럴 문자의 일치

우선, 가장 단순한 매칭하는 것부터 시작해봅시다. : 문자 그대로의 패턴….

리터럴 문자 일치는 문자 “A”와 같은 주어진 문자가 문자 “A”와 일치한다는 것입니다. 이 것은 그 자체로 일치하기 때문에 literal이라고 불리웁니다. 이 유형의 일치는 가장 기본적인 유형의 정규식 연산입니다. 일반 텍스트와 따옴표만 일치시킵니다.

다음은 정규식에 대한 기본적인 이해의 예입니다.

우리가 정규식으로 매칭할 첫번째 문자열은 단어 "the"입니다. 이 문자열은 문자 “t”, 문자 “h”, 문자 “e로 구성됩니다. 그러나 우리가 regex 패턴을”the“로 구성한다면 단어”the“뿐만 아니라 단어”they“와”soothe" 또한 매칭하는 결과를 얻게 될 것입니다. 따라서 따라서 정규 표현식 패턴은 시작과 끝에 공백이 있어야 하겠죠: " the "

다시 한번 강조하자면, 정규식에서 공백은 문자로 인식됩니다.

자 이제, 문자열 객체 bts_text에서 첫번째 요소인 bts_text[1]에 정규식을 적용해 봅시다.

우리가 구성한 정규표현이 문자열 객체에서 찾아낸 즉, 매칭된 결과를 확인하기 위해 우리는 str_extract_all() 함수를 사용하겠습니다.

bts_text[1]
## [1] "BTS (band)\r\nBTS (Hangul:                  ; RR: Bangtan Sonyeondan), also known as\r\n                                                                                                   BTS\r\nthe Bangtan Boys, is a seven-member South Korean boy band formed\r\nby Big Hit Entertainment. They debuted on June 12, 2013 with the\r\nsong \"No More Dream\" from their first album 2 Cool 4 Skool. They\r\nwon several New Artist of the Year awards for the track, including at\r\nthe 2013 Melon Music Awards and Golden Disc Awards and the 2014\r\nSeoul Music Awards. The band continued to rise to widespread\r\nprominence with their subsequent albums Dark & Wild (2014), The\r\nMost Beautiful Moment in Life, Part 2 (2015) and The Most Beautiful\r\nMoment in Life: Young Forever (2016), with the latter two entering the\r\n                                                                               BTS at the 32nd Golden Disk Awards on\r\nU.S. Billboard 200.[4] The Most Beautiful Moment in Life: Young\r\n                                                                                            January 10, 2018\r\nForever went on to win the Album of the Year award at the 2016\r\n                                                                            From left to right: V, Suga, Jin, Jungkook, RM,\r\nMelon Music Awards.[5]\r\n                                                                                            Jimin and J-Hope\r\nTheir second full album, Wings (2016),[6] peaked at number 26 on the                   Background information\r\nBillboard 200, which marked the highest chart ranking for a K-pop\r\n                                                                           Also known as Bangtan Boys · Bulletproof\r\nalbum ever.[7] In their native South Korea, Wings became the best\r\n                                                                                              Boy Scouts · Beyond The\r\nselling album in the Gaon Album Chart history at the time.[8] The\r\n                                                                                              Scene · Bangtan Sonyeondan\r\nalbum went on to sell more than 1.5 million copies, making it BTS'\r\n                                                                           Origin             Seoul, South Korea\r\nfirst \"million seller\",[9] and the group was subsequently awarded Artist\r\nof the Year at the 2016 Mnet Asian Music Awards.[10] The group's           Genres             K-pop · hip hop[1] · R&B · EDM\r\nnext release, Love Yourself: Her (2017),[11] debuted at number seven       Years active       2013<U+2013>present\r\non the Billboard   200,[12]  marking the highest rank for an Asian artist  Labels             Big Hit Entertainment · Pony\r\nin history. The group also managed to debut on the Billboard Hot 100                          Canyon · Def Jam Japan[2] ·\r\nfor the first time with the album's title track, \"DNA\", which entered at                      Columbia[3]\r\nnumber 85 and peaked at number 67.[13] Another track from the\r\n                                                                           Website            bts.ibighit.com\r\nalbum, \"Mic Drop\", was remixed by Steve Aoki with a feature by\r\nDesiigner and peaked at number 28 on the Billboard Hot 100. Both           Members            Jin\r\ntracks went on to be certified Gold by the Recording Industry                                 Suga\r\nAssociation of America, a first for any Korean act ever.[14] The album\r\n                                                                                              J-Hope\r\nsold over 1.2 million copies on South Korea's Gaon Album Chart in its\r\nfirst month, reclaiming its title as the best selling album in the chart's\r\n                                                                                              RM\r\nhistory and becoming the biggest selling Korean album by month in 16                          Jimin\r\nyears, coming second to G.o.d's album in      2001.[15] BTS was awarded                       V\r\nthe Mnet Asian Music Award for Artist of the Year for the second year                         Jungkook\r\nin a row in 2017.[16] Their third full album Love Yourself: Tear (2018),\r\ndebuted at number one on the Billboard 200,[17] making them the only                          Korean name\r\nK-Pop act to achieve this feat so far. Since their debut, they have sold   Hangul\r\nan estimated 7 million albums worldwide.[18][19]\r\n                                                                           Hanja                 彈少年團\r\nKnown for their large social media presence, BTS were listed by            Transcriptions\r\nForbes as the most retweeted artist on Twitter in March 2016.[20]\r\n                                                                           Revised Romanization Bangtan Sonyeondan\r\nFollowing that, Twitter launched its first ever K-pop Twitter emoji\r\n                                                                           McCune<U+2013>Reischauer          Pangt'an Sony<U+01D2>ndan\r\nfeaturing BTS in May.[21] In October 2016, Billboard placed BTS as\r\nnumber one on their Social 50 chart, making them the first Korean                           Japanese name\r\n"
unlist(str_extract_all(bts_text[1], pattern = " the ")) # 문자열 객체가 먼저 오고 정규식의 지정된 패턴이 뒤따릅니다. 
##  [1] " the " " the " " the " " the " " the " " the " " the " " the "
##  [9] " the " " the " " the " " the " " the " " the " " the " " the "
## [17] " the " " the " " the " " the " " the " " the " " the " " the "
## [25] " the " " the " " the " " the " " the " " the " " the " " the "
# pattern 즉 정규표현 구성을 지정하는 옵션값 설정

이처럼 간단해 보일지 모르지만 강조해야할 몇 가지 세부 사항이 있습니다. 첫 번째는 정규식 검색이 대소 문자를 구분한다는 것입니다. 이 말은 곧, " THE “라는 표현은 bts_text[1]에서 어떤 패턴과도 매칭되지 않는다는 것을 의미합니다..

str_extract_all(bts_text[1], " THE ") # 정규식은 대소 문자를 구별하므로 "THE"와는 아무 것도 일치하지 않습니다.
## [[1]]
## character(0)

숫자는 어떨까요? 다행히 모든 숫자(문자도 마찬가지입니다)는 리터럴 문자로 간주됩니다. 패턴 “2016”을 테스트 해봅시다.

str_extract_all(bts_text[1], "2016") # 모든 숫자는 리터럴 문자입니다.
## [[1]]
## [1] "2016" "2016" "2016" "2016" "2016" "2016"

메타 문자

이제 우리는 메타문자(metacharacter)에 대해서 알아볼 것입니다. 가장 기본적인 유형의 정규 표현식은 자신과 일치하는 리터럴 문자입니다. 하지만 모든 문자가 리터럴 문자는 아닙니다. 정규 표현을 구성하는 데 있어서 특수 목적을 갖고 그 역할을 수행하는 문자들도 있습니다. 그것이 바로 메타문자입니다. 이런 유형의 문자는 특별한 의미가 있기 때문에 일괄적으로 리터럴 문자들을 찾아내는 데 유용합니다.

정규식에서 metacharacters에는 15개의 종류가 있습니다. 각 기능에 대해서는 앞으로 적용해가면서 배워봅시다.

앞으로 우리는 이러한 메타 문자를 응용하여 정규 표현을 구성하는 법을 알아 볼 것입니다. 사실, 정규 표현식에서 중요한 것은 이 메타 문자가 어떻게 작동하는지 이해하고 적용하는 것입니다. 다행히, 메타 문자의 종류가 아주 많지는 않죠. 하지만 몇몇 메타 문자에는 하나 이상의 의미를 가진 것도 있습니다. 그리고 이 경우 메타 문자의 의미는 사용하는 맥락, 사용 방법, 사용 장소에 따라 달라지게 되죠. 마치 우리가 일상적으로하는 대화에서도 같은 어휘라도, 문맥에 따라 그 의미가 완전히 달라지게 되는 경우가 있는 것 처럼 말이죠. 따라서 이러한 정규 표현의 메타 문자를 이해하고 응용에 익숙해지는 데에는 시간이 다소 걸릴 수 있으며 많은 연습이 필요합니다.

와일드 메타 문자

우리가 배울 첫 메타 문자는 wild 메타 문자로 더 잘 알려진 “.”(점)입니다. 이 메타 문자는 모든 리터럴 문자를 찾는데 (매칭하는데) 사용됩니다.

예를 들어, "t.e"로 구성된 regex를 생각해봅시다. 이 표현은 “the”라는 패턴 뿐만 아니라, “tae”, “tee”, “tie”, “toe” 등 과도 매칭됩니다. 그러나 하나의 점은 하나의 단일 문자와만 일치하므로 “thee”, “tree”, 또는 “tube”와는 매칭되지 않습니다.

위키피디아 페이지의 텍스트를 단어 단위로 토큰화한 결과가 벡터로 할당된 bts_sent_trim 객체에 이 정규표현을 적용해 보겠습니다.

unique(unlist(str_extract_all(bts_sent_trim, "t.e")))
##  [1] "the" "tte" "tle" "twe" "tie" "tre" "t-e" "tee" "tme" "tne" "tYe"
## [12] "t/e"

와일드 메타 문자는 정규 표현식에서 가장 많이 사용되는 메타 문자 중 하나이지만 많은 실수의 이유가 되기도 합니다. 예를 들어, "e.g"와 일치하는 정규 표현식을 만들고 싶다고 가정해봅시다. 이 패턴은 “e.g”뿐만 아니라, “eng”, “e g”, “e-g”와도 일치할 수 있습니다.

왜?

왜냐하면 "."은 모든 것과 일치하는 메타 문자이기 때문입니다.

메타 문자 이스케이프

그렇다면 메타 문자 대신 리터럴 문자로서의 마침표를 찾아내고 매칭시킬 수 있을까요? 예를 들어, 다음과 같은 문자열 벡터가 있다고 가정해 봅시다.

dot_words <- c("e.g", "eng", "e g", "e-g")

패턴 "e.g"를 사용하면, dot_words에 있는 모든 요소와 일치합니다.

unlist(str_extract_all(dot_words, "e.g"))
## [1] "e.g" "eng" "e g" "e-g"

따라서 리터럴 문자인 마침표와 매칭시키려면, 메타 문자를 이스케이프 처리해야 합니다.

대부분의 프로그래밍 언어에서 메타 문자를 이스케이프하는 방법은 메타 문자 앞에 백 슬래시 문자를 추가하는 것입니다. 메타 문자 앞에 백 슬래시를 붙이면 메타문자로서의 더 이상 특별한 의미를 가지지 않으며 리터럴 문자로서 인식하게 됩니다. 그러나 R은 조금 다릅니다. 단일 백슬래시를 넣는 것이 아니라 2개의 백 슬래시를 넣어야 합니다. : "e\\.g". 단일 백 슬래시 "\"가 R에서는 메타 문자로 다른 의미를 가지기 때문입니다.

unlist(str_extract_all(dot_words, "e\\.g"))
## [1] "e.g"

퀴즈

위키피디아 텍스트의 문자열 벡터 객체인 bts_sent_trim에서 리터럴 문자,“a”,로 시작되는 세 글자 요소들을 모두 찾아내어 추출한 후, 각 요소의 등장 빈도수를 보여주는 명령어는 다음 중 무엇입니까?

  1. table(unlist(str_extract_all(bts_sent_trim, "a..")))
  2. table(unlist(str_extract_all(bts_sent_trim, "a ")))
  3. table(str_extract_all(bts_sent_trim, "a.."))
  4. table(unlist(str_extract_all(bts_sent_trim, "a\\.\\.")))
  5. table(str_extract_all(bts_sent_trim, "a\\.\\."))

정규 표현식 연습

지금까지 메타 문자를 이스케이프 (탈출)하여 리터럴 문자로서 매칭하는 방법에 대해 학습하였습니다. 이제부터는 문자 집합을 정의하는데에 사용되는 메타 문자인 대괄호 []에 대해 알아보도록 하겠습니다.

가령, 우리가 “t”로 시작되어 “e”로 끝나는 세글자 단어 모두를 찾아내서 지워주고 싶다고 가정해 봅시다. 이 경우 우리는 중간에 올 수 있는 모든 알파벳 경우를 가정해 가능한 단어 하나 하나씩 원하는 문자열에 매칭하는 방식으로 단어를 삭제할 수 있겠죠. 하지만, 그러나 이러한 방식으로 우리가 원하는 모든 문자열을 매칭하는 것은 매우 소모적이고 불필요한 일일 것입니다. 이 경우에 우리는 문자 하나 하나를 모두 정해줄 필요없이 원하는 문자의 범위를 지정해주는 방법을 택할 수 있습니다. 이러한 정규 표현식을 우리는 문자 집합이라 합니다.

문자 집합

문자 집합은 집합 안에 있는 속한 문자 중 어떤 것이라도 그 중 하나와 일치하는 문자를 매칭합니다. 가령, "[abc]"라는 문자 집합은 문자 “a”, “b”, “c” 중 어떤 것과도 일치하는 문자를 찾아내죠. 따라서 "t[abc]e라는 정규 표현은 “tae”, “tbe”, “tce” 등과 같은 문자열과 모두 매칭됩니다. 여기서 대괄호 []는 문자 집합을 정의하는 표현입니다.

한가지 유의할 점은, 문자 집합 내에서의 문자 순서는 중요하지 않다는 점입니다. 중요한 것은 대괄호 안에 있는 문자의 종류입니다. 따라서 문자 집합 "[abc]""[cba]"와 같은 결과를 도출합니다.

문자 집합의 적용

"f[aeiou]n"라는 알파벳 모음 문자 집합의 정규 표현 패턴으로 “fan”, “fin”, “fun”라는 단어 등이 속해있는 문자열 벡터로 부터 해당 단어를 매칭하여 추출해 봅시다.

library(stringr)
fns <- c("fan","fen","fin","fon","fun")
unlist(str_extract_all(fns, "f[aeiou]n"))
## [1] "fan" "fen" "fin" "fon" "fun"

“f[aeiou]n”으로 구성된 정규 표현은 fns의 모든 요소와 일치합니다. 이제 같은 문자 집합 패턴을 다른 벡터 fnx에 적용해 봅시다.

fnx <- c("fan","fin","fun","f0n","f.n","f1n","fain")
unlist(str_extract_all(fnx, "f[aeiou]n"))
## [1] "fan" "fin" "fun"

보다시피 fnx 안에 모음 문자가 있는 처음 세 요소만 일치합니다. 그리고 마지막 요소 “fain”은 일치하지 않습니다. 문자 집합은 하나의 문자만을 매칭하기 때문에 “a” 또는 “i” 에만 일치하고 “ai”는 매칭하지 않습니다.

문자 범위

위에서 설명한 문자 집합은 찾고자 하는 문자 모두를 지정하는 방식을 사용합니다. 그런데 만약 우리가 가능한 모든 문자열과 일치하지만, 구체적으로 어떠한 문자가 올 지 모르는 상황에서 문자열을 매칭하려면 어떻게 해야 할까요? 위에서 우리는 “t” 로 시작하고 “e”로 끝나는 세글자 단어를 모두 매칭하는 경우를 생각해봤습니다. 이 경우 우리는 어떻게 해야 할까요? 가능한 문자를 일일히 지정해주지 않더라도, 특정 문자들의 전체 범위를 설정하는 방법을 이용하면 효율적인 문자열 처리가 가능하겠죠.

이를 위해 메타 문자 하이픈을(hyphen) "-" 사용해서 문자 범위를 구성하는 방법을 소개해 드리겠습니다. 이 표현 방식은 매칭하고자 하는 문자들의 범위를 하이픈 "-" 기호로 지정하는 것입니다.

이러한 방식으로 다음과 같이 대문자, 소문자, 그리고 숫자 문자 집합을 정의할 수 있습니다.

uppercase <- "[A-Z]"

lowercase <- "[a-z]"

number <- "[0-9]"

*하이픈 기호는 문자 집합 안에 있을 때만 메타 문자입니다. 문자 집합 이외에서 쓰이는 하이픈은 리터럴 문자입니다.

이렇게 문자 범위로 구성된 정규 표현은 매우 유용합니다. 다음의 예를 함께 보실까요.

3개의 문자로 구성된 문자열을 가진 다음의 벡터 객체, triplets,를 상대로 문자 범위를 사용해 구성한 정규표현으로 매칭한 결과를 살펴봅시다.

triplets <- c("bts","the","BTS","The","010","070",":~)","^^;")
unlist(str_extract_all(triplets, "[a-z][a-z][a-z]")) # 3개의 연속된 소문자 
## [1] "bts" "the"
unlist(str_extract_all(triplets, "[A-Z]{3}")) # 3개의 연속된 대문자 
## [1] "BTS"
unlist(str_extract_all(triplets, "[A-Z][a-z]+")) # 대문자로 시작되고 그 다음 소문자 1회 이상 
## [1] "The"
unlist(str_extract_all(triplets, "[0-9]+$")) # 연속된 숫자 1회 이상이고 숫자로 종결
## [1] "010" "070"

이렇게 문자 범위로 구성된 문자 집합을 이용하면 편리하게 원하는 문자열을 매칭할 수 있습니다. 하지만, 위의 문자 범위로는 구두점으로 구성된 웃음 표시 ":~)""^^;"를 매칭할 수는 없었네요.

부정 문자 집합

정규 표현식으로 텍스트를 처리할 때, 문자 집합에서 벗어나는 즉, 문자 집합의 일부가 아닌 문자를 찾아내야 하는 상황은 빈번하게 발생합니다.

위키피디아 페이지의 전처리 과정을 예로 들어 볼까요. 이 때 우리는 알파벳 이외의 문자 즉, 한글 또는 한자로 표시된 문자를 매칭해서 지워줘야 할 필요가 있었습니다. 이 경우, 부정 문자 집합을 사용한다면, 우리가 원하는 문자 집합에 없는 문자를 찾아낼 수 있습니다. 이 부정 집합은 위해 메타 문자 캐럿 "^"으로 구성할 수 있습니다.

캐럿 "^"은 정규 표현식의 패턴에 하나 이상의 의미를 가지는 메타 문자 중 하나입니다. 그 중, 문자 집합 안의 첫번째 위치에 캐럿을 사용하면 : 예 : "[^a-z]", 다음의 문자 이외의 문자 즉, 부정 기능을 수행합니다. 따라서 알파벳 소문자를 제외한 모든 문자를 매칭하는 데 사용할 수 있죠.

이렇게 부정 문자 범위를 사용하면 우리는 다음과 같은 "[^a-zA-Z0-9]{3}" 패턴을 정의함으로써 문자와 숫자가 아닌 기호로 표시된 문자열인 웃음 표시 ":~)", "^^;"와 매칭시킬 수 있습니다.

unlist(str_extract_all(triplets, "[^a-zA-Z0-9]{1,}")) # 문자 및 숫자 이외의 문자가 1회 이상 매칭되는 모든 요소
## [1] ":~)" "^^;"

캐럿은 문자 집합 안에 처음으로 오는 경우에만 부정을 의미하며, 그렇지 않으면 부정 문자 집합이 아닙니다. 가령,

unlist(str_extract_all(triplets, "[a-zA-Z0-9^]+")) # 문자/숫자/캐럿 1회 이상 연결되는 문자열 매칭
## [1] "bts" "the" "BTS" "The" "010" "070" "^^"

이 경우, 정규 표현 패턴인 "[a-zA-Z0-9^]"은 부정 집합 "[^a-zA-Z0-9]"과 완전히 다른 “문자 또는 숫자 또는 리터럴 캐럿 문자”를 의미합니다.

문자 집합 안의 메타 문자

지금까지 문자 집합이 무엇인지, 그리고 문자 범위를 정의하는 방법과 부정 문자 집합을 지정하는 방법에 대해 알아보았습니다. 그렇다면, 문자 집합 안에 캐럿 이외에 다른 메타 문자를 포함시키면 어떻게 될까요?

문자 집합을 사용할 경우 첫 번째 위치에 있는 캐럿, 그리고 문자 범위를 지정하는 하이픈, 그리고 문자 집합 기호인 대괄호, 그리고 백슬래시를 제외하고는, 문자 집합 내의 메타 문자는 리터럴 문자로 이용됩니다! 즉, 문자 집합 안에서는 이중 백 슬래시를 사용하여 메타문자를 이스케이프 처리할 필요가 없습니다.

예를 들어, fnx 문자열 벡터를 다시 볼까요. 이 벡터의 문자열 요소들은 “f” 와 “n” 사이에 다양한 문자로 구성되어 있고, 여기에는 리터럴 문자로서의 마침표도 포함되어 있죠. 이 때 우리가 “f.n”이라는 문자열을 매칭하기 위해서 구성할 수 있는 정규 표현은 메타문자를 이스케이프하거나 문자집합을 사용하는 것입니다.

unlist(str_extract_all(fnx, "f\\.n")) 
## [1] "f.n"
unlist(str_extract_all(fnx, "f[.]n")) 
## [1] "f.n"

문자 클래스

문자 범위 이외에도 문자 클래스는 특정 문자 집단을 매칭하는데 유용한 정규 표현 구조를 제공합니다. 가령, 전체 알파벳 또는 숫자 클래스로 어떠한 알파벳 또는 숫자와도 매칭시킬 수 있죠. 이러한 문자 클래스는 이중 백슬래시와 이니셜 문자로 구성됩니다.

그리고 대부분의 정규 표현식 엔진에서 사용되는 가장 일반적인 문자 클래스는 다음과 같습니다.

문자 일치 같은 표현
\\d 임의의 숫자 [0-9]
\\D 임의의 숫자 이외의 문자 [^0-9]
\\w 밑줄 문자 “_“를 포함하여 영어 단어의 일부로 간주되는 문자 [a-zA-Z0-9_]
\\W 영어 단어의 일부로 간주되지 않는 문자 [^a-zA-Z0-9_]
\\s 공백 문자 [\f\n\r\t\v]
\\S 비공백 문자 [^\f\n\r\t\v]

문자 클래스는 문자 집합을 대체하여 편리하게 정규표현식을 구성할 수 있게 해줍니다.

unlist(str_extract_all(triplets, "\\d{3}")) # 3개의 임의의 숫자 연결된 패턴
## [1] "010" "070"
unlist(str_extract_all(triplets, "\\D+")) # 1개 이상 연속된 비 숫자 패턴
## [1] "bts" "the" "BTS" "The" ":~)" "^^;"
unlist(str_extract_all(triplets, "\\w+")) # 1개 이상 연속된 문자/숫자
## [1] "bts" "the" "BTS" "The" "010" "070"
unlist(str_extract_all(triplets, "\\W+")) # 1개 이상 연속된 문자/숫자
## [1] ":~)" "^^;"
unlist(str_extract_all(triplets, "\\s+")) # 1개 이상 연속된 공백
## character(0)
unlist(str_extract_all(triplets, "\\S+")) # 1개 이상 연속된 비공백 문자
## [1] "bts" "the" "BTS" "The" "010" "070" ":~)" "^^;"

텍스트 사전처리를 하다 보면 텍스트 공백이 다양한 방식의 문자 표현으로 구성되어 있다는 것을 발견하게 될 것입니다. 다음은 공백을 나타내는 문자를 보여주는 표입니다.

문자 정의
\f 페이지 넘김 (다음 페이지 또는 섹션을 구분하여 하단으로 넘어가는 기호)
\n 줄바꿈 for Mac OS (Windows: \r\n)
\r 캐리지 리턴 (줄의 처음으로 돌아가는 기호)
\t
\v 수직 탭

또 가끔은 공백 문자가 텍스트에 제대로 표시되어 있지 않는 경우도 있습니다. (\t, \n, \r\n): 이러한 공백문자는 그 기능이 각기 다르고, 단순 공백 문자로는 매칭되지 않습니다. 그렇기 때문에 모든 공백 문자 유형을 찾아내기 위해서는 공백 문자 클래스 \\s를 사용해야합니다.

POSIX (Portable Operating System Interface) 문자 클래스

정규 표현식을 구성 방법을 마무리하기 위해 POSIX로 알려진 또 다른 유형의 문자 클래스를 소개합니다. POSIX는 위의 문자 클래스와 같은 기능을 하지만 다른 방식으로 표현되고, 보다 다양한 문자 집합을 구성할 수 있게 해줍니다.

다음은 R에서 정규식 엔진이 지원하는 POSIX 문자 클래스 구조입니다.

문자 일치 같은 구조
[[:alnum:]] 영숫자 [a-zA-Z0-9]
[[:alpha:]] 알파벳 문자 [a-zA-Z]
[[:digit:]] 숫자 [0-9]
[[:lower:]] 소문자 [a-z]
[[:upper:]] 대문자 [A-Z]
[[:word:]] 단어 (문자, 숫자, 언더스코어) [a-zA-Z0-9_]
[[:blank:]] 공백과 탭 [ \t]
[[:space:]] 모든 공백 문자(줄 바꿈 포함) [ \f\n\r\t\v]
[[:punct:]] 구두점과 기호
[[:graph:]] 공백을 제외한 모든 인쇄 가능한 문자 [:alnum:][:punct:]
[[:print:]] 인쇄 가능한 모든 문자 [:alnum:][:punct:][:space:]
[[:ascii:]] 모든 ASCII 문자 (알파벳)

POSIX 문자 클래스는 여는 대괄호 [, 뒤에 콜론 :, 키워드 뒤에 이어 콜론 :, 닫는 대괄호 ]로 구성됩니다.

R에서 POSIX 문자 클래스를 사용하려면 문자 집합 안에 POSIX 클래스를 위치시켜야 합니다. 즉, POSIX 클래스는 이중 대괄호로 묶이는 형식이죠. POSIX 클래스를 사용하여 triplets라는 문자 벡터의 요소를 매치시켜 봅시다.

triplets
## [1] "bts" "the" "BTS" "The" "010" "070" ":~)" "^^;"
unlist(str_extract_all(triplets, "[[:lower:]]+")) # 1개 이상의 연속된 문자
## [1] "bts" "the" "he"
unlist(str_extract_all(triplets, "[[:alpha:]]+"))
## [1] "bts" "the" "BTS" "The"
unlist(str_extract_all(triplets, "[[:digit:]]{1,3}")) # 1개 이상 3개 이하로 연속된 숫자
## [1] "010" "070"
unlist(str_extract_all(triplets, "[[:punct:]~^]+")) # [:punct:] 리터럴 문자 캐럿 "^" "~"을 매칭하지 않습니다.
## [1] ":~)" "^^;"
unlist(str_extract_all(triplets, "[[:lower:][:punct:]]")) # 모든 단일 소문자/구두점
##  [1] "b" "t" "s" "t" "h" "e" "h" "e" ":" ")" ";"

위키피디아 텍스트의 어휘 빈도수 재분석

자 지금까지 배운 정규표현식으로 지난 시간에 수행했던 위키피디아 페이지의 텍스트를 다시 한번 처리해 보도록 하겠습니다.

library(stringr)
bts_string <- str_c(bts_text, collapse = " ") # 문자 벡터 bts_text를 단일 문자열로 축소

이제 BTS에 관한 위키피디아 페이지의 텍스트가 하나로 연결된 단일 문자열을 가지게 되었습니다.

문자열을 전처리해봅시다.

첫 번째로, References 섹션의 모든 것을 제거합시다.

str_locate_all(tolower(bts_string), "references") # 문자열에서 "references" 패턴의 위치를 찾습니다.
## [[1]]
##      start   end
## [1,]  7027  7036
## [2,] 32170 32179
bts_trunc <- str_trunc(bts_string, 31828, side = "right") # 문자열을 줄일 수 있다. 해당 위치에서 오른쪽의 문자열을 삭제.

이제 문자 “bts_string”에 "references"라는 정규 표현식이 어디에 나타나는지 알아보고 정규 표현식 패턴의 위치 이후의 모든 것을 제거하여 잘라냅니다.

다음으로는 공백(/n 또는 /r/n 또는 여러 공백)을 처리해야합니다.

줄 바꿈을 포함하여 모든 공백 문자를 나타내는 POSIX 문자 클래스를 기억하세요. : [[:space:]]

bts_nospace <- str_replace_all(bts_trunc, "[[:space:]]{1,}", " ") # 하나 이상의 공백을 하나의 공백 문자로 바꾸십시오.
# {1,}이 정규 표현식에서 어떤 역할을 하는지 생각해봅시다.

이전보다 나아보일 것입니다. 문자열 객체로 이제 무엇을 해야할까요? 문자열에 영어 이외의 문자가 있는지 확인해 봅시다. POSIX 문자 클래스 [:ascii:]를 사용하여 모든 비알파벳 문자를 제거해주어야 합니다. 자 그리고, 남은 모든 알파벳 문자를 표준화(소문자 또는 대문자로 표준화) 처리해야 합니다. 따라서 문자열의 문자를 소문자로 변환하는데 ‘tolower’ 함수를 사용해야합니다.

str_extract_all(bts_nospace, "[^[:ascii:]]{1,}") # 모든 영어 이외의 문자 추출(선행 문자 집합과 적어도 1번 이상 일치)
## [[1]]
##  [1] "·"                     "·"                    
##  [3] "·"                     "·"                    
##  [5] "·"                     "·"                    
##  [7] "<U+2013>"               "·"                    
##  [9] "·"                     "·"                    
## [11] "彈少年團"               "<U+2013>"              
## [13] "<U+01D2>"               "<U+5F3E>少年<U+56E3>"  
## [15] "ぼうだんしょうねんだん" "<U+014D>"              
## [17] "<U+014D>"               "o"                     
## [19] "o"                      "<U+2013>"              
## [21] "<U+2013>"               "<U+2013>"              
## [23] "彈少年團"               "’"                    
## [25] "<U+014D>"               "<U+014D>"              
## [27] "<U+5F3E>少年<U+56E3>"   "<U+2013>"              
## [29] "<U+2013>"               "<U+2013>"              
## [31] "<U+2013>"               "<U+2013>"              
## [33] "”"                     "’"                    
## [35] "“"                     "”"                    
## [37] "’"                     "“"                    
## [39] "”"                     "<U+2013>"              
## [41] "“"                     "’"                    
## [43] "”"                     "\\"                    
## [45] "\\"                     "\\"                    
## [47] "\\"                     "\\"                    
## [49] "<U+2013>"               "<U+2013>"              
## [51] "<U+2013>"               "<U+2013>"              
## [53] "<U+2013>"               "<U+2013>"              
## [55] "<U+2013>"               "<U+2013>"              
## [57] "<U+2013>"               "<U+2013>"              
## [59] "<U+2013>"               "<U+2013>"
bts_eng <- str_replace_all(bts_nospace, "[^[:ascii:]]", "") # 영어 이외의 문자를 "" 문자로 바꾸세요.(즉, 제거하세요.)
bts_eng_lower <- tolower(bts_eng) # Translate all characters into lower-case letters
# 오류 메세지가 뜨면 str_to_lower 문자열 함수를 사용하세요. 

bts_tidy <- str_to_lower(bts_tidy)

이제 구두점과 숫자를 처리하는 방법에 대해 생각해봅시다.

# 제거할 구두점을 확인하세요.
unlist(str_extract_all(bts_eng_lower, "[[:punct:]]+"))[1:100] # '+'가 정규식에서 어떤 역할을 하는지 생각해봅시다.
##   [1] "("    ")"    "("    ":"    ";"    ":"    "),"   ","    "-"    "."   
##  [11] ","    "\""   "\""   "."    ","    "."    "&"    "("    "),"   ","   
##  [21] "("    ")"    ":"    "("    "),"   "."    "."    ".["   "]"    ":"   
##  [31] ","    ":"    ","    ","    ","    ","    ","    ".["   "]"    "-"   
##  [41] ","    "("    "),["  "]"    ","    "-"    ".["   "]"    ","    ".["  
##  [51] "]"    "."    ","    "'"    ","    "\""   "\",[" "]"    ".["   "]"   
##  [61] "'"    "-"    "["    "]"    "&"    ","    ":"    "("    "),["  "]"   
##  [71] ",["   "]"    "."    "["    "]"    "'"    ","    "\""   "\","  "["   
##  [81] "]"    ".["   "]"    "."    "."    ","    "\""   "\","  "."    ","   
##  [91] ".["   "]"    "-"    "."    "'"    ","    "'"    ","    "."    "."

텍스트에서 모든 구두점이 추출된 것 같습니다.

그러나 단어를 형성하는데 포함되어 있는 구두점은 어떻게 해야할까요? 무엇일까요? 예를 들면…

# 제거할 구두점을 확인하세요. 
unlist(str_extract_all(bts_eng_lower, "[[:graph:]]*[[:punct:]]{1,}[[:graph:]]+"))[1:100] # "*" 는 선행 패턴과 0번 이상 일치합니다.
##   [1] "(band)"               "(hangul:"             "sonyeondan),"        
##   [4] "seven-member"         "\"no"                 "(2014),"             
##   [7] "(2015)"               "(2016),"              "u.s."                
##  [10] "200.[4]"              "awards.[5]"           "j-hope"              
##  [13] "(2016),[6]"           "k-pop"                "ever.[7]"            
##  [16] "time.[8]"             "1.5"                  "\"million"           
##  [19] "seller\",[9]"         "awards.[10]"          "group's"             
##  [22] "k-pop"                "hop[1]"               "r&b"                 
##  [25] "(2017),[11]"          "200,[12]"             "japan[2]"            
##  [28] "album's"              "\"dna\","             "columbia[3]"         
##  [31] "67.[13]"              "bts.ibighit.com"      "\"mic"               
##  [34] "drop\","              "ever.[14]"            "j-hope"              
##  [37] "1.2"                  "korea's"              "chart's"             
##  [40] "g.o.d's"              "2001.[15]"            "2017.[16]"           
##  [43] "(2018),"              "200,[17]"             "k-pop"               
##  [46] "worldwide.[18][19]"   "2016.[20]"            "k-pop"               
##  [49] "pangt'an"             "may.[21]"             "chart.[22]"          
##  [52] "chart.[23]"           "youtube's"            "chart.[24]"          
##  [55] "abbma.[25][26]"       "kunrei-shiki"         "internet.[27]"       
##  [58] "\"having"             "world's"              "group\".[28]"        
##  [61] "\"liked"              "(502"                 "million)\""          
##  [64] "u.s."                 "combined.[29]"        "[30]"                
##  [67] "korea's"              "group's"              "(hangul:"            
##  [70] "),"                   "\"bulletproof"        "scouts\"."           
##  [73] "adolescents.[31][32]" "),"                   "similarly.[33]"      
##  [76] "\"beyond"             "identity.[34]"        "\"growing"           
##  [79] "forward.\"[35]"       "j-hope,"              "soundcloud.[36][37]" 
##  [82] "group's"              "\"school"             "\"no"                
##  [85] "2013.[38][39]"        "105,000"              "copies,[40][41]"     
##  [88] "\"no"                 "\"we"                 "hits.[42][43]"       
##  [91] "\"no"                 "re-recorded"          "2014.[44]"           
##  [94] "\"school"             "o!rul8,2?,"           "120,000"             
##  [97] "four.[45][46]"        "\"n.o\""              "\"attack"            
## [100] "(korean:"

위의 단어 목록을 참고하여 보면, 구두점을 삭제할 때 주의해야 할 점을 알 수 있습니다. 첫째, “’”만 제거하는 것보다는 “’s”를 같이 제거해주는 것이 좋습니다. 둘째, “u.s”, “r&b”는 “usa”나 “rnb”로 바꾸어 주는 것이 좋습니다. 마지막으로, "o!rul8,2?"는 그 자체로는 의미를 가지지만 편의상 여기서는 구두점과 숫자의 기능을 무시합니다.

unlist(str_extract_all(bts_eng_lower, "[']s ")) # 패턴을 바꾸기 전에 원하는 패턴과 일치하는지 항상 먼저 확인하세요. 
##  [1] "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s "
## [12] "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s "
## [23] "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s "
## [34] "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s " "'s "
bts_noapos <- str_replace_all(bts_eng_lower, "[']s ", " ") # 패턴을 하나의 공백 문자로 바꾸세요. 
str_extract_all(bts_noapos, " u\\.s\\. | r\\&b ")
## [[1]]
## [1] " u.s. " " r&b "  " u.s. " " r&b "
bts_usa <- str_replace_all(bts_noapos,  " u\\.s\\. ", " usa ")
bts_rnb <- str_replace_all(bts_usa,  " r\\&b ", " rnb ")
str_extract_all(bts_rnb, " usa | rnb ")
## [[1]]
## [1] " usa " " rnb " " usa " " rnb "

위키피디아 페이지에는 [digits] 또는 (digits) 형태의 인용 표시가 많습니다.

str_trunc(bts_rnb, 1000)
## [1] "bts (band) bts (hangul: ; rr: bangtan sonyeondan), also known as bts the bangtan boys, is a seven-member south korean boy band formed by big hit entertainment. they debuted on june 12, 2013 with the song \"no more dream\" from their first album 2 cool 4 skool. they won several new artist of the year awards for the track, including at the 2013 melon music awards and golden disc awards and the 2014 seoul music awards. the band continued to rise to widespread prominence with their subsequent albums dark & wild (2014), the most beautiful moment in life, part 2 (2015) and the most beautiful moment in life: young forever (2016), with the latter two entering the bts at the 32nd golden disk awards on usa billboard 200.[4] the most beautiful moment in life: young january 10, 2018 forever went on to win the album of the year award at the 2016 from left to right: v, suga, jin, jungkook, rm, melon music awards.[5] jimin and j-hope their second full album, wings (2016),[6] peaked at number 26 on t..."

이러한 인용 표시를 제거해보겠습니다.

str_extract_all(bts_rnb, "\\[\\d+\\]|\\(\\d+\\)")
## [[1]]
##   [1] "(2014)" "(2015)" "(2016)" "[4]"    "[5]"    "(2016)" "[6]"   
##   [8] "[7]"    "[8]"    "[9]"    "[10]"   "[1]"    "(2017)" "[11]"  
##  [15] "[12]"   "[2]"    "[3]"    "[13]"   "[14]"   "[15]"   "[16]"  
##  [22] "(2018)" "[17]"   "[18]"   "[19]"   "[20]"   "[21]"   "[22]"  
##  [29] "[23]"   "[24]"   "[25]"   "[26]"   "[27]"   "[28]"   "[29]"  
##  [36] "[30]"   "[31]"   "[32]"   "[33]"   "[34]"   "[35]"   "[36]"  
##  [43] "[37]"   "[38]"   "[39]"   "[40]"   "[41]"   "[42]"   "[43]"  
##  [50] "[44]"   "[45]"   "[46]"   "[47]"   "[48]"   "[49]"   "(2014)"
##  [57] "[50]"   "[51]"   "[52]"   "[53]"   "[54]"   "[55]"   "[56]"  
##  [64] "[57]"   "[58]"   "[59]"   "(2014)" "[60]"   "[61]"   "[62]"  
##  [71] "[63]"   "[64]"   "[65]"   "(2015)" "[66]"   "[67]"   "[68]"  
##  [78] "[69]"   "[70]"   "[71]"   "[71]"   "[72]"   "[73]"   "[74]"  
##  [85] "[63]"   "[75]"   "[76]"   "[77]"   "[78]"   "[79]"   "[80]"  
##  [92] "(2016)" "[81]"   "[82]"   "[83]"   "[84]"   "[5]"    "[85]"  
##  [99] "[86]"   "[87]"   "[88]"   "[89]"   "[90]"   "[91]"   "[10]"  
## [106] "[92]"   "[93]"   "[94]"   "[95]"   "[96]"   "[97]"   "[98]"  
## [113] "[99]"   "[100]"  "[101]"  "[102]"  "[103]"  "[104]"  "[105]" 
## [120] "[106]"  "[107]"  "[108]"  "[109]"  "[110]"  "[111]"  "[112]" 
## [127] "[113]"  "[114]"  "[115]"  "[116]"  "[117]"  "[118]"  "[119]" 
## [134] "[120]"  "[121]"  "[122]"  "[123]"  "[124]"  "[125]"  "[14]"  
## [141] "[126]"  "[127]"  "[128]"  "[129]"  "[130]"  "[131]"  "[132]" 
## [148] "[133]"  "[134]"  "[135]"  "[136]"  "[137]"  "[138]"  "[139]" 
## [155] "[140]"  "[141]"  "[142]"  "[143]"  "[144]"  "[145]"  "[146]" 
## [162] "[147]"  "[148]"  "[149]"  "[150]"  "[151]"  "[152]"  "[153]" 
## [169] "(2013)" "(2013)" "(2014)" "[154]"  "[155]"  "(2016)" "[156]" 
## [176] "[154]"  "[157]"  "[158]"  "[156]"  "[159]"  "[160]"  "[161]" 
## [183] "[25]"   "[26]"   "[162]"  "[29]"   "[163]"  "[164]"  "[165]" 
## [190] "[166]"  "[167]"  "[168]"  "[169]"  "[170]"  "[171]"  "[172]" 
## [197] "[173]"  "[174]"  "[175]"  "[176]"  "[177]"  "[178]"  "[179]" 
## [204] "[180]"  "[181]"  "[182]"  "[183]"  "[184]"  "[185]"  "[186]" 
## [211] "[187]"  "[188]"  "[189]"  "[190]"  "[191]"  "[192]"  "[193]" 
## [218] "[194]"  "[195]"  "[196]"  "[197]"  "[198]"  "[199]"  "[200]" 
## [225] "[201]"  "[202]"  "[203]"  "[204]"  "[205]"  "[206]"  "[207]" 
## [232] "[206]"  "[208]"  "[206]"  "[209]"  "[206]"  "[210]"  "[206]" 
## [239] "[211]"  "[206]"  "[212]"  "[206]"  "[213]"  "(2014)" "(2014)"
## [246] "(2016)" "(2016)" "(2018)" "(2018)" "(2014)" "(2015)" "(2015)"
## [253] "(2015)" "(2016)" "(2017)" "(2018)" "[214]"
bts_nocite <- str_replace_all(bts_rnb, "\\[[[:digit:]]+\\]|\\([[:digit:]]+\\)", "")

이제 문자열에서 모든 구두점을 제거할 준비가 되었습니다.

unlist(str_extract_all(bts_nocite, "[[:graph:]]*[[:punct:]]{1,}[[:graph:]]*"))[1:100]
##   [1] "(band)"          "(hangul:"        ";"              
##   [4] "rr:"             "sonyeondan),"    "boys,"          
##   [7] "seven-member"    "entertainment."  "12,"            
##  [10] "\"no"            "dream\""         "skool."         
##  [13] "track,"          "awards."         "&"              
##  [16] ","               "life,"           "life:"          
##  [19] ","               "200."            "life:"          
##  [22] "10,"             "right:"          "v,"             
##  [25] "suga,"           "jin,"            "jungkook,"      
##  [28] "rm,"             "awards."         "j-hope"         
##  [31] "album,"          ","               "200,"           
##  [34] "k-pop"           "ever."           "korea,"         
##  [37] "time."           "1.5"             "copies,"        
##  [40] "bts'"            "seoul,"          "\"million"      
##  [43] "seller\","       "awards."         "k-pop"          
##  [46] "release,"        "yourself:"       ","              
##  [49] "200,"            "history."        "track,"         
##  [52] "\"dna\","        "67."             "bts.ibighit.com"
##  [55] "album,"          "\"mic"           "drop\","        
##  [58] "100."            "america,"        "ever."          
##  [61] "j-hope"          "1.2"             "month,"         
##  [64] "years,"          "g.o.d"           "2001."          
##  [67] "2017."           "yourself:"       ","              
##  [70] "200,"            "k-pop"           "far."           
##  [73] "debut,"          "worldwide."      "presence,"      
##  [76] "2016."           "that,"           "k-pop"          
##  [79] "pangt'an"        "may."            "2016,"          
##  [82] "chart,"          "chart."          "date,"          
##  [85] "chart."          "year,"           "100:"           
##  [88] "chart,"          "chart."          "2017,"          
##  [91] "awards,"         "abbma."          "2017,"          
##  [94] "kunrei-shiki"    "internet."       "20,"            
##  [97] "\"having"        "group\"."        "december,"      
## [100] "2017,"
bts_nopunct <- str_replace_all(bts_nocite, "[[:punct:]^]+", "")
str_trunc(bts_nopunct, 1000)
## [1] "bts band bts hangul  rr bangtan sonyeondan also known as bts the bangtan boys is a sevenmember south korean boy band formed by big hit entertainment they debuted on june 12 2013 with the song no more dream from their first album 2 cool 4 skool they won several new artist of the year awards for the track including at the 2013 melon music awards and golden disc awards and the 2014 seoul music awards the band continued to rise to widespread prominence with their subsequent albums dark  wild  the most beautiful moment in life part 2  and the most beautiful moment in life young forever  with the latter two entering the bts at the 32nd golden disk awards on usa billboard 200 the most beautiful moment in life young january 10 2018 forever went on to win the album of the year award at the 2016 from left to right v suga jin jungkook rm melon music awards jimin and jhope their second full album wings  peaked at number 26 on the background information billboard 200 which marked the highest cha..."

str_extract_all 함수를 사용하여 수치 표현식을 제거하는 방법을 살펴봅시다.

# 제거할 숫자 확인 
# 단어를 형성하기 위해 숫자 뒤에 오는 문자를 제거하지 않겠습니다. (ex> 2nd)
# 따라서 숫자는 공백이나 문자가 뒤따라야 합니다. 
unlist(str_extract_all(bts_nopunct, " [[:digit:]]+[[:space:]]?[[:alpha:]]*"))[1:100] # 정규 표현식에서 "?"는 어떤 역할을 할까요? 
##   [1] " 12 "                 " 2 cool"              " 4 skool"            
##   [4] " 2013 melon"          " 2014 seoul"          " 2 "                 
##   [7] " 32nd"                " 200 the"             " 10 "                
##  [10] " 2016 from"           " 26 on"               " 200 which"          
##  [13] " 15 million"          " 2016 mnet"           " 2013present"        
##  [16] " 200 marking"         " 100 canyon"          " 85 and"             
##  [19] " 67 another"          " 28 on"               " 100 both"           
##  [22] " 12 million"          " 16 jimin"            " 2001 bts"           
##  [25] " 2017 their"          " 200 making"          " 7 million"          
##  [28] " 2016 revised"        " 2016 billboard"      " 50 chart"           
##  [31] " 78 weeks"            " 50 chart"            " 100 "               
##  [34] " 6th"                 " 14th"                " 2017 they"          
##  [37] " 2017 kunreishiki"    " 25 most"             " 20 "                
##  [40] " 2018 edition"        " 2017 being"          " 502 million"        
##  [43] " 2018 a"              " 20102014 debut"      " 20152016 mainstream"
##  [46] " 2017present"         " 2017 bts"            " 20102014 debut"     
##  [49] " 2010 and"            " 2011 by"             " 2012 six"           
##  [52] " 2 cool"              " 4 skool"             " 12 "                
##  [55] " 2014 performing"     " 105000 copies"       " 2 were"             
##  [58] " 4 "                  " 11 "                 " 120000 copies"      
##  [61] " 2013melon"           " 2014 seoul"          " 200000 copies"      
##  [64] " 200000 copies"       " 28000 copies"        " 2014 bts"           
##  [67] " 20152016 mainstream" " 2015 bts"            " 1 bts"              
##  [70] " 1 "                  " 27 best"             " 2015 so"            
##  [73] " 2016 in"             " 2 "                  " 44 on"              
##  [76] " 100 million"         " 1have"               " 300000 copies"      
##  [79] " 42000 copies"        " 2 later"             " 30 "                
##  [82] " 171 on"              " 200 chart"           " 5000 copies"        
##  [85] " 2015 mnet"           " 1 and"               " 2 were"             
##  [88] " 40 hit"              " 10 hit"              " 20 hit"             
##  [91] " 7 "                  " 44000 copies"        " 500000 copies"      
##  [94] " 6 million"           " 24 hours"            " 24 hours"           
##  [97] " 2016 mnet"           " 2017present"         " 2017 bts"           
## [100] " 700000 copies"
bts_nonum <- str_replace_all(bts_nopunct, "[[:digit:]]+", "") # 모든 숫자 제거
str_trunc(bts_nonum, 1000)
## [1] "bts band bts hangul  rr bangtan sonyeondan also known as bts the bangtan boys is a sevenmember south korean boy band formed by big hit entertainment they debuted on june   with the song no more dream from their first album  cool  skool they won several new artist of the year awards for the track including at the  melon music awards and golden disc awards and the  seoul music awards the band continued to rise to widespread prominence with their subsequent albums dark  wild  the most beautiful moment in life part   and the most beautiful moment in life young forever  with the latter two entering the bts at the nd golden disk awards on usa billboard  the most beautiful moment in life young january   forever went on to win the album of the year award at the  from left to right v suga jin jungkook rm melon music awards jimin and jhope their second full album wings  peaked at number  on the background information billboard  which marked the highest chart ranking for a kpop also known as b..."

자 이제, 영어가 아닌 문자, 모든 숫자 및 구두점이 제거하는 방식으로 텍스트를 전처리 했습니다. 그러나 텍스트 사전처리에서 생성된 여러 개의 공백을 여전히 볼 수 있습니다.

bts_nospace <- str_replace_all(bts_nonum, "[[:space:]]{1,}", " ") # 공백 삭제 과정을 반복할 수 있습니다. 

마지막으로 문자열 객체인 bts_tidy를 " "로 구분된 단어로 토큰화할 준비가 되었습니다.

bts_tidy_word <- unlist(str_split(bts_nospace, " "))
bts_tidy_word_freq <- sort(table(bts_tidy_word), decreasing = TRUE) # 각 단어 출현 빈도 수 세서, 내림차순 정렬
bts_tidy_word_freq[1:50]
## bts_tidy_word
##       the       and        in        on        to       bts        of 
##       305       108       106        97        73        68        65 
##       for     their        at     first         a      that        as 
##        63        61        50        46        45        42        39 
##     group     album     chart     music    number      they      with 
##        39        38        34        33        33        32        30 
##    korean       was    awards        by      also    artist billboard 
##        28        28        27        24        23        22        22 
##       top      year      love      over  released    single        an 
##        21        21        20        20        20        19        17 
##      most      were       its     korea      kpop     world  yourself 
##        17        17        16        16        16        16        16 
##     later      song    copies      from    albums  japanese       new 
##        15        15        14        14        13        13        13 
##    second 
##        13

단어 빈도 테이블에서 wordcloud를 만들어봅시다.

library(wordcloud)
## Loading required package: RColorBrewer
pal <- brewer.pal(8, "Dark2") # "Dark2"에서 8가지 색상 검색
set.seed(405)
wordcloud(words = names(bts_tidy_word_freq), # 고유 단어의 열
          freq = bts_tidy_word_freq, # 단어의 빈도
          min.freq = 5, # 표시된 단어의 최소 빈도
          max.words = 500, # 빈도 순서로 표시된 500개의 단어
          random.order = FALSE, # 중앙에 위치한 최다 빈도 단어들
          rot.per = 0.1, # 플롯에서 회전하는 단어의 비율
          scale = c(4, 0.3), # 단어의 크기 범위
          colors = pal) # 단어 색상

관심 있는 위키피디아 페이지에서 직접 텍스트 처리를 해봅시다.

첫번째 숙제

  1. Wikipedia에서 관심있는 주제를 선택하세요.
  2. 페이지의 PDF 파일을 다운로드하여 RStudio에 로드하세요.
  3. stringr 패키지를 사용하여 선택한 작업의 텍스트를 사전처리합니다.
  4. 빈도 상위 50개 단어를 표시하는 표를 만드세요.
  5. 빈도 테이블에서 wordcloud 만들기

질문있나요?