Manipulate Documents

Manipulate Documents라 쓰고 텍스트 텍스트 데이터 정제라 이야기 합니다.

텍스트 데이터 정제

신문 기사나 소설, 수필과 같은 잘 정리된 텍스트 문서와 뉴스 진행자들이 전하는 뉴스 멘트들은 맞춤법에 부합하는 품질 높은 텍스트 데이터들입니다.

실제로 텍스트 분석에 직면하면 환상은 저 먼 나라의 이야기가 되어 버립니다. 맞춤법, 띄어쓰기가 무시된 텍스트는 그나마 애교가 있는 수준입니다.

통화 내용을 STT(Speech to Text)기법으로 텍스트로 변환한 데이터는 변환기의 성능이 완벽하지 않아서 품질이 매우 낮습니다. 화자와 청자의 의도는 유추하여 이해할 수 있겠으나, 텍스트 분석이라는 기계를 시켜서 수행하는 데이터 분석에는 부족함이 많습니다.

카페나 블로그의 게시글, SNS 채널의 글은 맞춤법, 띄어쓰기에 취약하고, 신조어나 암호같은 줄임말, 완전하지 않은 문장들이 포함됩니다. 경우에 따라서는 수집 과정에서 기술적인 한계로, 불필요한 텍스트들이 포함되기도 합니다. 그래서 데이터 정제없이 분석할 수 없는 경우가 많습니다. 어떤 경우는 수집한 텍스트 데이터가 데이터 분석을 수행하려는 목적과 부합하지 않아서 제거해야할 경우도 있습니다.

이처럼 텍스트 데이터 분석은 일반적인 데이터 분석에 비해서 데이터 정제가 차지하는 비중은 매우 큽니다. 텍스트 데이터 정제 성능은 텍스트 데이터 분석 성능과 직결되기 때문입니다.

형태소분석과 데이터의 품질

텍스트 데이터 분석은 보통 형태소분석을 통해서 품사를 태깅하고, 품사 기반으로 토큰화된 단어로 텍스트 분석을 수행합니다.

문제는 분석에 사용하는 형태소분석기가, 문법과 띄어쓰기에 부합되는 품질 좋은 양질의 텍스트 데이터를 학습해서 만들어진 모델을 이용한다는 점입니다. 그래서 형태소분석을 수행하는 데이터의 품질이 떨어진다면, 형태소분석의 결과도 만족스럽지 못합니다. 어찌 보면 이러한 점이 데이터 정제를 하는 가장 큰 이유 중에 하나입니다.

문서의 품질이 높은 경우에도 문제가 발생할 수 있습니다. 형태소분석기에 사용한 학습 데이터는 우리가 일상 생활에서 이야기하는 대화의 주제, 혹은 직업, 학문과 예술, 종교 등 여러 분야의 내용을 모두 담지 못합니다. 학습 데이터는 지극히 일부의 샘플링된 문장들이라는 점입니다. 그래서 통상적인 생활에서 발화되는 단어가 아닌 전문성이 필요한 영역의 단어를 이해하지 못합니다.

알파고가 쏘아 올린 화두가 학계와 필드의 AI 혁신을 이끌었습니다. 아마도 5년전에는 대중들은 알파고라는 단어에 익숙하지 못했을 겁니다.

이처럼 형태소분석기가 취약한 신조어나, 특정 영역에서 사용하는 전문용어들은 사용자 정의 사전에 등록해서 형태소분석기가 이를 이해할 수 있도록 도와줘야 합니다. 이러한 작업들도 광의적으로 데이터를 정제를 수행하는 덱트스 데이터의 조작(Manipulate Documents)입니다.

텍스트 데이터 정제를 위한 bitTA의 기능

bitTA의 텍스트 데이터 조작 기능을 정리하면 다음과 같습니다.

  • 문서 단위의 전처리
    • 문서 필터링 (Filter Documents)
  • 텍스트 단위의 전처리
    • 텍스트 대체 (Replace Texts)
    • 텍스트 연결 (Concatenate Texts)
    • 텍스트 분리 (Split Texts)
    • 텍스트 제거 (Remove Texts)

bitTA는 대용량의 텍스트 데이터에서 상기 데이터 조작을 수행할 수 있도록 도와줍니다. 그래서 다음과 같은 방법으로 작업합니다.

  • 병렬 처리를 통한 속도의 개선
  • 데이터 조작 룰을 등록한 메타(meta) 파일 활용

bitTA의 메타 데이터 관리

메타 데이터 종류 및 데이터 포맷

메터 데이터 이름 메타 데이터 아이디 변수이름 변수 설명
문서 필터링 filter rule_nm 개별 필터 룰의 이름
pattern 문서 필터링을 위한 패턴 매치 정규표현식
accept allow/deny 여부, TRUE는 allow 패턴, FALSE는 deny 패턴
use 개별 룰 사용여부, FALSE이면 미사용, TRUE인 건만 사용
텍스트 대체 replace rule_nm 개별 대체 룰의 이름
rule_class 텍스트 대체 룰의 그룹 이름
pattern 텍스트 대체를 위한 패턴 매치 정규표현식
replace 패턴에 매치된 텍스트를 대체할 텍스트 정의
use 개별 룰 사용여부, FALSE이면 미사용, TRUE인 건만 사용
텍스트 연결 concat rule_nm 개별 연결 룰의 이름
pattern 텍스트 연결을 위한 패턴 매치 정규표현식
replace 패턴에 매치된 텍스트를 대체할 텍스트 정의
use 개별 룰 사용여부, FALSE이면 미사용, TRUE인 건만 사용
텍스트 분리 split rule_nm 개별 분리 룰의 이름
pattern 텍스트 분리를 위한 패턴 매치 정규표현식
replace 패턴에 매치된 텍스트를 대체할 텍스트 정의
use 개별 룰 사용여부, FALSE이면 미사용, TRUE인 건만 사용
텍스트 제거 remove rule_nm 개별 제거 필터의 이름
pattern 텍스트 제거를 위한 패턴 매치 정규표현식
use 개별 룰 사용여부, FALSE이면 미사용, TRUE인 건만 사용

메타 데이터의 설정과 확인

set_meta() 함수는 세션 안에서 bitTA 패키지의 메타 데이터를 등록합니다.

다음의 set_meta() 함수의 원형을 보면 데이터 파일을 읽는 방법과 유사합니다.

set_meta(
  id = c("filter", "replace", "remove", "concat", "split"),
  filename,
  sep = ",",
  fileEncoding = "utf-8",
  append = FALSE
)

bitTA 패키지는 샘플 메타 데이터 파일을 제공하는데, 문서 필터링을 위한 샘플 메타 데이터 파일을 읽어 봅니다.

library(bitTA)

meta_path <- system.file("meta", package = "bitTA")
fname <- glue::glue("{meta_path}/preparation_filter.csv")

## 데이터 필터링 메타 신규 등록
set_meta("filter", fname, fileEncoding = "utf8")

get_meta() 함수는 세션 안에서 등록된 메타 데이터를 조회합니다.

## 기 등록된 데이터 필터링 메타 조회
get_meta("filter")
   rule_nm
1 신문기사
2 제품홍보
3 설문조사
4     출처
5   이벤트
6     방송
                                                                                    pattern
1                                   (팍스넷|파이낸셜|연합|(PT)|오마이|경제)[[:space:]]*뉴스
2 ((입법|정치|교육)[[:space:]]*플랫폼)|맘마미아[[:space:]]*가계부[[:print:]]*인증샷|Playtex
3                                                              좌담회|구글설문|채용대행업체
4                                                    출처[[:space:]]*:|문의처보건복지콜센터
5                                     (증정|기념)이벤트|허니스크린|이벤트를[[:space:]]*진행
6                                 제작진|기억저장소|추모카페|블랙홀|푸드스튜디오|연금정보넷
  accept  use
1  FALSE TRUE
2  FALSE TRUE
3  FALSE TRUE
4  FALSE TRUE
5  FALSE TRUE
6  FALSE TRUE

filter_text()를 이용한 문서 필터링

텍스트 데이터(문서들) 중에서 분석을 수행하려는 목적과 부합하지 않은 텍스트(문서)를 제거해야할 경우에는 filter_text()를 사용합니다.

이미 앞에서 문서 필터링을 위한 메타 데이터 파일을 읽어들였습니다. 6개의 룰은 accept 값이 FALSE인 deny 룰입니다. 즉 해당 검색 패턴을 만족하는 텍스트 데이터를 제거하는 작업을 수행합니다.

문자 벡터의 필터링

버즈 데이터의 본문은 길이가 1000인 문자 벡터입니다. 이 벡터는 5개의 결측치를 포함하고 있습니다.

doc_content <- buzz$CONTENT
is.character(doc_content)
[1] TRUE
length(doc_content)
[1] 1000

sum(is.na(doc_content))
[1] 5

8개의 코어를 이용해서 필터링을 수행합니다. as_logical = FALSE을 지정하면 문자 벡터의 필터링을 수행할 수 있습니다.

doc_after_character <- filter_text(doc_content, as_logical = FALSE, mc.cores = 8)
── rejects: 방송 ──────────────────────────────────────────────────────── 3건 ──
── rejects: 설문조사 ──────────────────────────────────────────────────── 1건 ──
── rejects: 신문기사 ──────────────────────────────────────────────────── 1건 ──
── rejects: 이벤트 ────────────────────────────────────────────────────── 1건 ──
── rejects: 제품홍보 ──────────────────────────────────────────────────── 2건 ──
── rejects: 출처 ──────────────────────────────────────────────────────── 2건 ──
── Missing Check: Removing NA ─────────────────────────────────────────── 5건 ──

length(doc_after_character)
[1] 985

5개의 결측치와 6개의 룰에서 10개의 문서가 제거되어서 길이가 985인 문자 벡터가 만들어졌습니다.

데이터 프레임의 필터링

tidytext 패키지를 이용해서 텍스트 데이터 분석을 수행한다면, 문자 벡터의 필터링이 아니라 문자 변수를 이용한 필터링을 수행해야 합니다.

다음처럼 as_logical 인수의 기본값인 TRUE를 사용합니다. 이 경우는 CONTENT 변수의 모든 원소에 대해서 allow 필터링 여부를 의미하는 논리 벡터를 만들어 반환합니다. 그러므로 dplyr 패키지의 filter 함수와 사용하여 필터링합니다.

library(dplyr)

buzz %>% 
  filter(filter_text(CONTENT, verbos = FALSE)) %>% 
  select(KEYWORD, SRC, CONTENT)

[38;5;246m# A tibble: 985 × 3
[39m
  KEYWORD SRC              CONTENT                                              
  
[3m
[38;5;246m<chr>
[39m
[23m   
[3m
[38;5;246m<chr>
[39m
[23m            
[3m
[38;5;246m<chr>
[39m
[23m                                                

[38;5;250m1
[39m 맞벌이  17,18년 베이비맘 
[38;5;246m"
[39m지금 둘째 임신중인 어머니예요 첫째는 16년 1월생 둘… 

[38;5;250m2
[39m 맞벌이  20대 수다방      
[38;5;246m"
[39m저희 부부는 맞벌이인데요 남편 회사 사람들도 거의 다…

[38;5;250m3
[39m 맞벌이  20대 수다방      
[38;5;246m"
[39m신랑지출 제지출 구분해서 따로적으시나요 제가쓴돈은 …

[38;5;250m4
[39m 맞벌이  20대 수다방      
[38;5;246m"
[39m너무 고민이 되서 하소연 할때 없어서 여기서 하소연 … 

[38;5;246m# … with 981 more rows
[39m

replace_text()를 이용한 텍스트 대체

문서 안에 포함된 특정 텍스트를 다른 텍스트로 대체하기 위해서는 replace_text()를 사용합니다. as_logical 인수만 없을 뿐 사용 방법은 filter_text()와 유사합니다.

meta_path <- system.file("meta", package = "bitTA")
fname <- glue::glue("{meta_path}/preparation_replace.csv")
set_meta("replace", fname, fileEncoding = "utf8")

# 등록된 문자열 대체 룰 확인하기
get_meta("replace")
       rule_nm    rule_class
1  다중 구두점   구두점 대체
2         남편 유사단어 대체
3   베이비시터 유사단어 대체
4     텔레비전 유사단어 대체
5         CCTV 유사단어 대체
6       할머니 유사단어 대체
7       어머니 유사단어 대체
8       아버지 유사단어 대체
9         아들 유사단어 대체
10          딸 유사단어 대체
11      화이팅 유사단어 대체
12      모유량 유사단어 대체
13        베개 유사단어 대체
14        초산 유사단어 대체
15        급여 유사단어 대체
                                                  pattern    replace  use
1                                               (\\.){2,}        \\. TRUE
2                                               신랑|남편       남편 TRUE
3  베비시터|((육아|아이|아기)[[:space:]]*(도우미|돌보미)) 베이비시터 TRUE
4                          TV|테레비|티브이|텔레비젼|티비   텔레비전 TRUE
5           (CC|cc|씨씨)[[:space:]]?(텔레비전|티비|tv|TV)       CCTV TRUE
6                                     할(미|머님|무니|매)     할머니 TRUE
7                                 엄마|어머님|엄니|어무니     어머니 TRUE
8                                      아버님|아빠|아부지     아버지 TRUE
9                              아들(래미|아이||내미|램)       아들 TRUE
10                               딸(래미|아이||내미|램)         딸 TRUE
11                                     파이팅|홧팅|퐈이팅     화이팅 TRUE
12                                모유[[:space:]]?[양|량]     모유량 TRUE
13                                           [베배][게개]       베개 TRUE
14                              (첫|처음)[[:space:]]*출산       초산 TRUE
15                                              월급|봉급       급여 TRUE

남편이라는 단어와 신랑이라는 단어를 포함한 문장의 수는 각각 175개와 177개입니다. 그러나 이 두 단어는 동의어입니다. 그래서 텍스트 대체 룰에는 이 두 단어를 남편이라는 하나의 단어로 표준화했습니다.

doc_content <- buzz$CONTENT

stringr::str_detect(doc_content, "남편") %>% 
  sum(na.rm = TRUE)
[1] 175

stringr::str_detect(doc_content, "신랑") %>% 
  sum(na.rm = TRUE)
[1] 177

문서들에서 몇 개의 룰이 적용되는지 결과를 보면서 텍스트를 대체합니다. 신랑이라는 단어가 남편으로 대체되었음을 알 수 있습니다.

buzz_after <- buzz %>% 
  mutate(CONTENT = replace_text(CONTENT, verbos = TRUE))
── Replace: [구두점 대체] - 다중 구두점 ───────────────────────────────── 2건 ──
── Replace: [유사단어 대체] - CCTV ────────────────────────────────────── 3건 ──
── Replace: [유사단어 대체] - 급여 ────────────────────────────────────── 0건 ──
── Replace: [유사단어 대체] - 남편 ──────────────────────────────────── 323건 ──
── Replace: [유사단어 대체] - 딸 ──────────────────────────────────────── 0건 ──
── Replace: [유사단어 대체] - 모유량 ──────────────────────────────────── 1건 ──
── Replace: [유사단어 대체] - 베개 ────────────────────────────────────── 5건 ──
── Replace: [유사단어 대체] - 베이비시터 ──────────────────────────────── 0건 ──
── Replace: [유사단어 대체] - 아들 ────────────────────────────────────── 0건 ──
── Replace: [유사단어 대체] - 아버지 ──────────────────────────────────── 0건 ──
── Replace: [유사단어 대체] - 어머니 ──────────────────────────────────── 0건 ──
── Replace: [유사단어 대체] - 초산 ────────────────────────────────────── 0건 ──
── Replace: [유사단어 대체] - 텔레비전 ────────────────────────────────── 3건 ──
── Replace: [유사단어 대체] - 할머니 ──────────────────────────────────── 0건 ──
── Replace: [유사단어 대체] - 화이팅 ──────────────────────────────────── 0건 ──

stringr::str_detect(buzz_after$CONTENT, "남편") %>% 
  sum(na.rm = TRUE)
[1] 323

stringr::str_detect(buzz_after$CONTENT, "신랑") %>% 
  sum(na.rm = TRUE)
[1] 0

concat_text()를 이용한 텍스트 연결

띄어쓰기된 단어들을 하나의 단어로 묶어주기 위해서 concat_text()를 사용합니다.

meta_path <- system.file("meta", package = "bitTA")
fname <- glue::glue("{meta_path}/preparation_concat.csv")
set_meta("concat", fname, fileEncoding = "utf8")

# 등록된 문자열 결합 룰 확인하기
get_meta("concat")
                rule_nm                pattern    replace  use
1 (하원도우미) 붙여쓰기 하원[[:space:]]+도우미 하원도우미 TRUE
2 (가사도우미) 붙여쓰기 가사[[:space:]]+도우미 가사도우미 TRUE
3 (산후도우미) 붙여쓰기 산후[[:space:]]+도우미 산후도우미 TRUE
4 (친정어머니) 붙여쓰기 친정[[:space:]]+어머니 친정어머니 TRUE
5 (베이비시터) 붙여쓰기 베이비[[:space:]]+시터 베이비시터 TRUE
6   (연말정산) 붙여쓰기   연말[[:space:]]+정산   연말정산 TRUE
7   (출산휴가) 붙여쓰기   출산[[:space:]]+휴가   출산휴가 TRUE
8   (시어머니) 붙여쓰기   시[[:space:]]+어머니   시어머니 TRUE
9   (육아휴직) 붙여쓰기   육아[[:space:]]+휴직   육아휴직 TRUE

일반적으로 복합명사를 정의하는 사례들입니다.

가사도우미라는 단어는 가사도우미가 결합된 복합명사입니다. 그런데 두 단어가 띄어쓰기된 경우가 있습니다.

doc_content <- buzz$CONTENT

stringr::str_detect(doc_content, "가사도우미") %>% 
  sum(na.rm = TRUE)
[1] 1

stringr::str_detect(doc_content, "가사[[:space:]]+도우미") %>% 
  sum(na.rm = TRUE)
[1] 22

문서들에서 몇 개의 룰이 적용되는지 결과를 보면서 텍스트를 연결합니다. 두 단어가 띄어쓰기된 가사 도우미가 수정되었습니다.

buzz_after <- buzz %>% 
  mutate(CONTENT = concat_text(CONTENT, verbos = TRUE))
── Concat: (가사도우미) 붙여쓰기 ─────────────────────────────────────── 22건 ──
── Concat: (베이비시터) 붙여쓰기 ──────────────────────────────────────── 1건 ──
── Concat: (산후도우미) 붙여쓰기 ──────────────────────────────────────── 1건 ──
── Concat: (시어머니) 붙여쓰기 ────────────────────────────────────────── 1건 ──
── Concat: (연말정산) 붙여쓰기 ────────────────────────────────────────── 1건 ──
── Concat: (육아휴직) 붙여쓰기 ────────────────────────────────────────── 2건 ──
── Concat: (출산휴가) 붙여쓰기 ────────────────────────────────────────── 1건 ──
── Concat: (친정어머니) 붙여쓰기 ──────────────────────────────────────── 5건 ──
── Concat: (하원도우미) 붙여쓰기 ──────────────────────────────────────── 1건 ──

stringr::str_detect(buzz_after$CONTENT, "가사도우미") %>% 
  sum(na.rm = TRUE)
[1] 23

stringr::str_detect(buzz_after$CONTENT, "가사[[:space:]]+도우미") %>% 
  sum(na.rm = TRUE)
[1] 0

이렇게 수정된 문서들이 형태소분석을 통해서 토큰화되어 분석을 수행한다면, 형태소분석기에도 복합명사가 등록되어 있어야 합니다. 안그러면 단어를 연경하여 복합명사를 만든어 놓아도 토큰화 과정에서 다시 분리됩니다.

다음처럼 mecab-ko의 사전에는 가사도우미라는 명사가 등록되어 있지 않습니다. 이 경우에는 사용자 정의 사전으로 토큰화 과정에서 다시 분리되지 않도록 유도해야 합니다.

morpho_mecab("가사도우가 집안 청소를 했다.")
   NNG    NNG    NNG    NNG 
"가사" "도우" "집안" "청소" 

split_text()를 이용한 텍스트 분리

묶어진 단어를 다시 분리할 경우에는 split_text()를 사용합니다.

meta_path <- system.file("meta", package = "bitTA")
fname <- glue::glue("{meta_path}/preparation_split.csv")
set_meta("split", fname, fileEncoding = "utf8")

# 등록된 문자열 분리 룰 확인하기
get_meta("split")
                 rule_nm
1 (도우미) 유형 띄어쓰기
                                                   pattern replace  use
1 (하원|등하원|등원|입주|교포|가사|산후|보육|산모)(도우미) \\1 \\2 TRUE

가사도우미를 주제로 하는 것이 아니라 도우미를 주제로 분석하려 합니다. 도우미가 들어간 복합명사를 분리해서 도우미라는 독립된 단어를 만들고자 합니다. concat_text()의 사례와는 반대의 경우입니다.

doc_content <- buzz$CONTENT

stringr::str_extract_all(doc_content, "(하원|등하원|등원|입주|교포|가사|산후|보육|산모)(도우미)") %>% 
  unlist() %>% 
  na.omit() %>% 
  as.vector()
 [1] "산후도우미" "입주도우미" "입주도우미" "입주도우미" "입주도우미"
 [6] "등원도우미" "등원도우미" "가사도우미" "등원도우미" "등원도우미"
[11] "보육도우미"

도우미가 들어간 복합명사들이 모두 분리되었습니다.

buzz_after <- buzz %>% 
  mutate(CONTENT = split_text(CONTENT, verbos = TRUE))
── Split: (도우미) 유형 띄어쓰기 ──────────────────────────────────────── 6건 ──

stringr::str_detect(buzz_after$CONTENT, "(하원|등하원|등원|입주|교포|가사|산후|보육|산모)(도우미)") %>% 
  sum(na.rm = TRUE)
[1] 0

remove_text()를 이용한 텍스트 제거

문서 안에 불필요한 텍스트들이 포함되어 있을 수 있습니다. 그래서 문서 내에서 패턴 검색으로 불필요한 텍스트를 골라내어 제거할 수 있습니다. remove_text()를 사용합니다.

meta_path <- system.file("meta", package = "bitTA")
fname <- glue::glue("{meta_path}/preparation_remove.csv")
set_meta("remove", fname, fileEncoding = "utf8")

# 등록된 문자열 제거 룰 확인하기
get_meta("remove")
          rule_nm                                         pattern  use
1 카페 안내문구 1 게시판[[:space:]]*이용전[[:print:]]*이동됩니다. TRUE
2 카페 안내문구 2                  카페이용 전[[:print:]]*참고\\) TRUE
3 카페 안내문구 3                 게시글 작성[[:print:]]*35756864 TRUE
4 카페 안내문구 4             흥부야[[:print:]]*기타 하고 싶은 말 TRUE
5        URL 문구   (http|www)([a-zA-Z0-9\\>\\/\\.\\:\\=\\&\\_])* TRUE

수집한 카페의 게시글에는 불필요한 텍스트들이 포함될 수 있습니다.

다음은 카페의 게시글을 작성할 때, 관리자가 미리 설정해 놓은 주의사항을 삭제하지 않고 게시글을 작성한 문서들을 조회한 사례입니다. 그리고 불필요한 주의사항을 제거한 후의 내용은 어느 정도 정제가 되었습니다.

doc_content <- buzz$CONTENT

stringr::str_detect(doc_content, "게시판[[:space:]]*이용전[[:print:]]*이동됩니다.") %>% 
  which
 [1]  61  65  67  69  79  82 237 239 245 251 252 256 257 400 403 406 416 417 419
[20] 563 569 571 584 732 737 739 740 903 910 912 915 921 922

doc_content[61]
[1] " 게시판 이용전반드시 카페규정 http://cafe.naver.com/imsanbu/28123090을 미리 숙지 당부드립니다.수다방 성격에 맞지않는 이탈글은 적합한 게시판으로 이동 또는 삭제예정게시판으로 이동됩니다.인천살구 아기는 15개월이에요 선천성기형인 이루공인데 수술하신분들은 어디서하셨나요 지금은 인천국제성모에서진료중이고 고름나기시작해서 수술을하게되면 아주대에서할까하는데 아기가 너무어려서 걱정입니다경험하신분들 조언좀해주세요"

stringr::str_remove(doc_content[61], "게시판[[:space:]]*이용전[[:print:]]*이동됩니다.") 
[1] " 인천살구 아기는 15개월이에요 선천성기형인 이루공인데 수술하신분들은 어디서하셨나요 지금은 인천국제성모에서진료중이고 고름나기시작해서 수술을하게되면 아주대에서할까하는데 아기가 너무어려서 걱정입니다경험하신분들 조언좀해주세요"

remove_text()로 불필요한 텍스트를 제거한 후에, 앞의 사례인 “게시판[[:space:]]*이용전[[:print:]]*이동됩니다.”를 조회했습니다. 해당 문장이 삭제되어 패턴 검색이 되지 않았습니다.

buzz_after <- buzz %>% 
  mutate(CONTENT = remove_text(CONTENT, verbos = TRUE))
── Removes: URL 문구 ─────────────────────────────────────────────────── 40건 ──
── Removes: 카페 안내문구 1 ──────────────────────────────────────────── 33건 ──
── Removes: 카페 안내문구 2 ───────────────────────────────────────────── 9건 ──
── Removes: 카페 안내문구 3 ──────────────────────────────────────────── 47건 ──
── Removes: 카페 안내문구 4 ──────────────────────────────────────────── 16건 ──

stringr::str_detect(buzz_after$CONTENT, "게시판[[:space:]]*이용전[[:print:]]*이동됩니다.") %>% 
  sum(na.rm = TRUE)
[1] 0