with_tidytext.Rmd
요즘 R 진영에는 tidy
한 작업이 대세입니다.
깔끔하게 정도로 번역을 할 수도 있지만, 최적의 단어로
번역이 어렵습니다. 쉽게 생각한다면, tidyverse
패키지군의
“tibble
구조로 데이터를 구조화하여 분석할 수 있다.” 정도의
의미 해석이 가능합니다.
이것은 한편으로는 tidyverse
패키지군과의 협업 용이성을
의미하기도 합니다. 즉, tidyverse
패키지군에서 제공하는 여러
장점을 사용할 수 있다는 것입니다. 아마도 dplyr
와의 협업이
주가 될 것입니다.
다음은 CRAN에 등록된 패키지중에서 tidy
라는 단어가 들어간
패키지 이름을 조사한 결과입니다. 앞으로 계속 늘어날텐데, 이 작업을
수행한 시점인 2022-07-31에는 73개 패키지가 있습니다.
library(tidyverse)
available.packages(repos = "https://cran.rstudio.com/") %>%
row.names() %>%
str_subset("tidy")
1] "spotidy" "tidybayes" "tidyBdE" "tidybins"
[5] "tidyboot" "tidycat" "tidyCDISC" "tidycensus"
[9] "tidycharts" "tidyclust" "tidycmprsk" "tidycode"
[13] "tidycomm" "tidyCpp" "tidycwl" "tidydatatutor"
[17] "tidydice" "tidyDisasters" "tidydr" "tidyEmoji"
[21] "tidyestimate" "tidyfast" "tidyfit" "tidyfst"
[25] "tidygapminder" "tidygate" "tidygenomics" "tidygeocoder"
[29] "tidygeoRSS" "tidygraph" "tidyHeatmap" "tidyhydat"
[33] "tidyjson" "tidylda" "tidylo" "tidylog"
[37] "tidyLPA" "tidymodels" "tidymv" "tidync"
[41] "tidypaleo" "tidyplus" "tidypmc" "tidyposterior"
[45] "tidypredict" "tidyquant" "tidyquery" "tidyqwi"
[49] "tidyr" "tidyREDCap" "tidyrgee" "tidyRSS"
[53] "tidyrules" "tidyselect" "tidySEM" "tidyseurat"
[57] "tidysmd" "tidysq" "tidystats" "tidystopwords"
[61] "tidystringdist" "tidysynth" "tidytable" "tidytags"
[65] "tidyterra" "tidytext" "tidytidbits" "tidytransit"
[69] "tidytreatment" "tidytree" "tidytuesdayR" "tidyUSDA"
[73] "tidyverse" "tidyvpc" "tidywikidatar" "tidyxl" [
목록의 63번째 tidytext
패키지는 텍스트 데이터 분석을
수행할 때, tidyverse 패키지군의 dplyr
과
ggplot2
의 기능과 더불어 쉽고 효과적인 텍스트 분석을 수행할
수 있습니다.
아마도 한국 텍스트분석을 수행하는 분석가중 많은 수가
tidytext
패키지를 이용할 것입니다. 그러나 영문 텍스트
분석을 수행할 목적으로 개발된 tidytext
패키지에서는 한글을
분석하는데 다소 부족한 영역이 존재합니다. 그래서 bitNLP
패키지는 이 지점을 지원하여, 한글 텍스트 분석을 수행함에 있어서
tidytext
패키지를 원활히 사용할 수 있도록 도와줍니다.
교착어인 한글은 영문과 달리, 띄어쓰기 단위인 words
가
아닌 형태소 단위로 토큰화를 수행해야 텍스트 분석을 수행할 수 있습니다.
물론 경우에 따라서 words
단위의 토큰화가 유용한 경우도
있습니다.
bitNLP에서는 tidytext
패키지에서 지원하지 않는, 엄밀히
말하면 tidytext
패키지 내부에서 사용하는
tokenizers
패키지에서 제공하지 않는 두개의
토크나이저(tokenizers)를 제공합니다.:
tidytext 구문에서 형태소 토크나이저인 morpho_mecab()는 다음과 같이 사용합니다.
library(bitNLP)
library(tidyverse)
library(tidytext)
<- president_speech %>%
nho_noun_indiv filter(president %in% "노무현") %>%
filter(str_detect(category, "^외교")) %>%
::unnest_tokens(
tidytextout = "speech_noun",
input = "doc",
token = morpho_mecab
)
nho_noun_indiv 38;5;246m# A tibble: 44,316 × 7
[39m
[
id president category type title date speec…¹38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;250m1
[39m DOC_0001 노무현 외교-통상 치사
[38;5;246m"
[39m2005 한일 우정의 해 개막식 … 2005… 우정
[
[38;5;250m2
[39m DOC_0001 노무현 외교-통상 치사
[38;5;246m"
[39m2005 한일 우정의 해 개막식 … 2005… 해
38;5;250m3
[39m DOC_0001 노무현 외교-통상 치사
[38;5;246m"
[39m2005 한일 우정의 해 개막식 … 2005… 개막식
[
[38;5;250m4
[39m DOC_0001 노무현 외교-통상 치사
[38;5;246m"
[39m2005 한일 우정의 해 개막식 … 2005… 축하
38;5;246m# … with 44,312 more rows, and abbreviated variable name ¹speech_noun
[39m
[
만약 사용자 사전이 있다면 다음과 같이 user_dic 인수를 사용할 수도 있습니다.
president_speech %>%
filter(president %in% "노무현") %>%
filter(str_detect(category, "^외교")) %>%
tidytext::unnest_tokens(
out = "speech_noun",
input = "doc",
token = morpho_mecab,
user_dic = user_dic
)
명사 n-grams 토크나이저는 다음과 같이 사용합니다.
tokenize_noun_ngrams(president_speech$doc[1:2])
1]]
[[1] "우정 해" "해 개막식" "개막식 축하" "축하 행사"
[5] "행사 축하" "축하 참석" "참석 모두" "모두 환영"
[9] "환영 감사" "감사 인사" "인사 전" "전 이웃"
[13] "이웃 옛날" "옛날 이웃" "이웃 이웃" "이웃 사정"
[17] "사정 통신사" "통신사 절" "절 시절" "시절 연락선"
[21] "연락선 시대" "시대 항공기" "항공기 하루" "하루 안"
[25] "안 시대" "시대 교통" "교통 발달" "발달 통신"
[29] "통신 관계" "관계 경제" "경제 교류" "교류 말"
[33] "말 협력" "협력 국음" "국음 마음" "마음 실행"
[37] "실행 가공" "가공 과학" "과학 기술" "기술 옛날"
[41] "옛날 사이" "사이 불편" "불편 문제" "문제 생각"
[45] "생각 상황" "상황 양국" "양국 관계" "관계 불편"
[49] "불편 생존" "생존 자체" "자체 위협" "위협 사이"
[53] "사이 유감" "유감 친구" "친구 방법" "방법 관계"
[57] "관계 숙명" "숙명 친구" "친구 관계" "관계 친구"
[61] "친구 친구" "친구 미래" "미래 적극" "적극 친구"
[65] "친구 손" "손 불행" "불행 평화" "평화 번영"
[69] "번영 미래" "미래 관계" "관계 자리" "자리 양국"
[73] "양국 관계" "관계 도로" "도로 표현" "표현 전"
[77] "전 경제" "경제 도로는" "도로는 고속도" "고속도 수준"
[81] "수준 정치" "정치 안보" "안보 측면" "측면 협력"
[85] "협력 도로" "도로 개통" "개통 문화" "문화 도로"
[89] "도로 길" "길 길" "길 위" "위 장애물"
[93] "장애물 양국" "양국 협력" "협력 관계" "관계 고속"
[97] "고속 도로" "도로 장애물" "장애물 직시" "직시 양국"
[101] "양국 정부" "정부 국민" "국민 적극" "적극 노력"
[105] "노력 가슴" "가슴 우정" "우정 불" "불 자리"
[109] "자리 우정" "우정 불" "불 양국" "양국 국민"
[113] "국민 사이" "사이 우정" "우정 계기" "계기 이틀"
[117] "이틀 전" "전 주최" "주최 행사" "행사 성공"
[121] "성공 성원" "성원 참석" "참석 격려" "격려 총리"
[125] "총리 국민" "국민 자리" "자리 감사" "감사 올해"
[129] "올해 양국" "양국 수교" "수교 해" "해 일"
[133] "일 양국" "양국 우정" "우정 성공" "성공 때"
[137] "때 보람" "보람 생각" "생각 올해" "올해 이전"
[141] "이전 양국" "양국 국민" "국민 교류" "교류 국민"
[145] "국민 교류" "교류 해" "해 감사"
[
2]]
[[1] "각하 국민" "국민 신년" "신년 인사" "인사 새해" "새해 축복"
[6] "축복 해" "해 기원" "기원 올해" "올해 양국" "양국 관계"
[11] "관계 발전" "발전 전기" "전기 중" "중 교류" "교류 해"
[16] "해 경제" "경제 학술" "학술 문화" "문화 체육" "체육 청소년"
[21] "청소년 분야" "분야 행사" "행사 본격" "본격 국민" "국민 교류"
[26] "교류 협력" "협력 시대" "시대 나라" "나라 교역" "교역 상대국"
[31] "상대국 투자" "투자 대상" "대상 국" "국 한국인" "한국인 방문"
[36] "방문 서로" "서로 문화" "문화 이웃" "이웃 양국" "양국 우호"
[41] "우호 협력" "협력 올해" "올해 교류" "교류 행사" "행사 강화"
[46] "강화 각하" "각하 합의" "합의 전면" "전면 협력" "협력 동반자"
[51] "동반자 관계" "관계 심화" "심화 평화" "평화 공동" "공동 번영"
[56] "번영 미래" "미래 기대" "기대 각하" "각하 건강" "건강 무궁"
[61] "무궁 발전" "발전 기원"
[
# simplify = TRUE
tokenize_noun_ngrams(president_speech$doc[1], simplify = TRUE)
1] "우정 해" "해 개막식" "개막식 축하" "축하 행사"
[5] "행사 축하" "축하 참석" "참석 모두" "모두 환영"
[9] "환영 감사" "감사 인사" "인사 전" "전 이웃"
[13] "이웃 옛날" "옛날 이웃" "이웃 이웃" "이웃 사정"
[17] "사정 통신사" "통신사 절" "절 시절" "시절 연락선"
[21] "연락선 시대" "시대 항공기" "항공기 하루" "하루 안"
[25] "안 시대" "시대 교통" "교통 발달" "발달 통신"
[29] "통신 관계" "관계 경제" "경제 교류" "교류 말"
[33] "말 협력" "협력 국음" "국음 마음" "마음 실행"
[37] "실행 가공" "가공 과학" "과학 기술" "기술 옛날"
[41] "옛날 사이" "사이 불편" "불편 문제" "문제 생각"
[45] "생각 상황" "상황 양국" "양국 관계" "관계 불편"
[49] "불편 생존" "생존 자체" "자체 위협" "위협 사이"
[53] "사이 유감" "유감 친구" "친구 방법" "방법 관계"
[57] "관계 숙명" "숙명 친구" "친구 관계" "관계 친구"
[61] "친구 친구" "친구 미래" "미래 적극" "적극 친구"
[65] "친구 손" "손 불행" "불행 평화" "평화 번영"
[69] "번영 미래" "미래 관계" "관계 자리" "자리 양국"
[73] "양국 관계" "관계 도로" "도로 표현" "표현 전"
[77] "전 경제" "경제 도로는" "도로는 고속도" "고속도 수준"
[81] "수준 정치" "정치 안보" "안보 측면" "측면 협력"
[85] "협력 도로" "도로 개통" "개통 문화" "문화 도로"
[89] "도로 길" "길 길" "길 위" "위 장애물"
[93] "장애물 양국" "양국 협력" "협력 관계" "관계 고속"
[97] "고속 도로" "도로 장애물" "장애물 직시" "직시 양국"
[101] "양국 정부" "정부 국민" "국민 적극" "적극 노력"
[105] "노력 가슴" "가슴 우정" "우정 불" "불 자리"
[109] "자리 우정" "우정 불" "불 양국" "양국 국민"
[113] "국민 사이" "사이 우정" "우정 계기" "계기 이틀"
[117] "이틀 전" "전 주최" "주최 행사" "행사 성공"
[121] "성공 성원" "성원 참석" "참석 격려" "격려 총리"
[125] "총리 국민" "국민 자리" "자리 감사" "감사 올해"
[129] "올해 양국" "양국 수교" "수교 해" "해 일"
[133] "일 양국" "양국 우정" "우정 성공" "성공 때"
[137] "때 보람" "보람 생각" "생각 올해" "올해 이전"
[141] "이전 양국" "양국 국민" "국민 교류" "교류 국민"
[145] "국민 교류" "교류 해" "해 감사"
[
<- "신혼부부나 주말부부는 놀이공원 자유이용권을 즐겨 구매합니다."
str
tokenize_noun_ngrams(str)
1]]
[[1] "신혼 부부" "부부 주말" "주말 부부" "부부 놀이" "놀이 공원" "공원 자유"
[7] "자유 이용" "이용 구매"
[
# 불용어 처리
tokenize_noun_ngrams(str, stopwords = "구매")
1]]
[[1] "신혼 부부" "부부 주말" "주말 부부" "부부 놀이" "놀이 공원" "공원 자유"
[7] "자유 이용"
[
# 사용자 정의 사전 사용
<- system.file("dic", package = "bitNLP")
dic_path <- glue::glue("{dic_path}/buzz_dic.dic")
dic_file tokenize_noun_ngrams(str, simplify = TRUE, user_dic = dic_file)
1] "신혼부부 주말부부" "주말부부 놀이" "놀이 공원"
[4] "공원 자유이용권" "자유이용권 구매"
[
# n_min
tokenize_noun_ngrams(str, n_min = 1, user_dic = dic_file)
1]]
[[1] "신혼부부" "신혼부부 주말부부" "주말부부"
[4] "주말부부 놀이" "놀이" "놀이 공원"
[7] "공원" "공원 자유이용권" "자유이용권"
[10] "자유이용권 구매" "구매"
[
# ngram_delim
tokenize_noun_ngrams(str, ngram_delim = ":", user_dic = dic_file)
1]]
[[1] "신혼부부:주말부부" "주말부부:놀이" "놀이:공원"
[4] "공원:자유이용권" "자유이용권:구매"
[
# bi-grams
tokenize_noun_ngrams(str, n = 2, ngram_delim = ":", user_dic = dic_file)
1]]
[[1] "신혼부부:주말부부" "주말부부:놀이" "놀이:공원"
[4] "공원:자유이용권" "자유이용권:구매" [
bitNLP의 한글 unnest_tokens
에는 명사 n-grams
토크나이즈를 지원하는 unnest_noun_ngrams()
함수가 있습니다.
이 함수는 tidytext
패키지의 unnest_tokens
함수군의 사용법과 거의 동일합니다.
tidy
와 같이 회자되는 단어인 unnest는
“중첩을 해제한다”고 번역되지만, 이것 또한 어렵게
번역되고 있습니다.
tidy
데이터의 핵심은 데이터를 관측치인 행과 변수인 열로
구조화는 것입니다. 그리고 열에는 하나의 값인 단일 값(길이가 1인 벡터)을
포함해야 합니다. 즉, 행의 차원와 열의 차원으로 구성된 2차원 데이터
구조가 tidy
데이터입니다.
그런데 하나의 관측치에서 특정 변수의 값이 단일 값이 아닌 경우가
있습니다. 마치 R의 리스트처럼 여러 값으로 구성되어 있습니다. 이 경우에서
문제가 되는 특정 열의 여러 정보를 풀어서 단일 정보로 변환하는 것이
unnest
입니다. 결국의 해당 변수의 단일 정보 개수만큼
관측치(행)를 복제한 후, 해당 열에 각각의 단일 정보만 넣는 작업이
unnest
입니다.
다음의 한용운님의 님의 침묵
시에서 첫번째와 두번째 줄을
tibble
객체로 만든 것입니다.
<- c("님은 갔습니다. 아아, 사랑하는 나의 님은 갔습니다.",
docs "푸른 산빛을 깨치고 단풍나무 숲을 향하여 난 작은 길을 걸어서, 차마 떨치고 갔습니다.")
<- tibble(
poem = rep(1, 2),
연 = 1:2,
행 = docs
내용
)
poem38;5;246m# A tibble: 2 × 3
[39m
[
연 행 내용 38;5;246m<dbl>
[39m
[23m
[3m
[38;5;246m<int>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;250m1
[39m 1 1 님은 갔습니다. 아아, 사랑하는 나의 님은 갔습니다.
[38;5;250m2
[39m 1 2 푸른 산빛을 깨치고 단풍나무 숲을 향하여 난 작은 길을 걸어서, 차마…
[
우리는 시의 내용에서 다음처럼 일반명사만 추출했습니다. 명사를 추출한
변수 명사
에는 첫 행에는 3개의 명사(정보)가, 둘째 행에는
5개의 명사(정보)가 들어있습니다.
%>%
poem mutate(명사 = collapse_noun(내용)) %>%
select(-내용)
38;5;246m# A tibble: 2 × 3
[39m
[
연 행 명사 38;5;246m<dbl>
[39m
[23m
[3m
[38;5;246m<int>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;250m1
[39m 1 1 님 사랑 님
[38;5;250m2
[39m 1 2 산 빛 단풍나무 숲 길
[
그런데 텍스트 분석에서는 문장 레벨의 분석보다는 토큰(한글
텍스트 데이터에서는 명사) 레벨로 분석합니다. 이것은 개별
행에서의 분석의 대상이 되는 컬럼에는 단일 토큰을 넣어야 한다는
의미입니다. 즉 개별 토큰 레벨의 unnest
작업이
필요합니다.
한글 텍스트 데이터에서 일반명사를 추출한 후 이것을
tidy
한 데이터로 만들기 위해서는 tidytext
의
unnest_tokens()
함수에 토크나이저로 bitNLP
의
morpho_mecab()
를 사용합니다. 원하는 모습의
tidy
데이터가 만들어진 것입니닫.
%>%
poem unnest_tokens(
명사,
내용,token = morpho_mecab
)38;5;246m# A tibble: 8 × 3
[39m
[
연 행 명사 38;5;246m<dbl>
[39m
[23m
[3m
[38;5;246m<int>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;250m1
[39m 1 1 님
[38;5;250m2
[39m 1 1 사랑
[38;5;250m3
[39m 1 1 님
[38;5;250m4
[39m 1 2 산
[38;5;246m# … with 4 more rows
[39m
[
unnest
작업은 다음처럼 얻는 것과 잃는 것이 있습니다.
tidy
한 데이터 구조로 변환하였기 때문에 연산이 쉽고,
이해하기 쉽다.bitNLP
의 unnest_noun_ngrams()
는 추출된
n-grams 명사 토큰들을 tibble의 컬럼에 하나씩 붙여줍니다.
다음은 대통령 연설문의 명사 bi-grams
를 추출하여,
noun_bigram
변수에 개별 토큰을 넣습니다.
%>%
president_speech select(title, doc) %>%
filter(row_number() <= 2) %>%
unnest_noun_ngrams(
noun_bigram,
doc,n = 2,
ngram_delim = ":",
type = "noun2"
)38;5;246m# A tibble: 264 × 2
[39m
[
title noun_bigram38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;250m1
[39m
[38;5;246m"
[39m2005 한일 우정의 해 개막식 축사
[38;5;246m"
[39m 일:우정
[38;5;250m2
[39m
[38;5;246m"
[39m2005 한일 우정의 해 개막식 축사
[38;5;246m"
[39m 우정:해
[38;5;250m3
[39m
[38;5;246m"
[39m2005 한일 우정의 해 개막식 축사
[38;5;246m"
[39m 해:개막식
[38;5;250m4
[39m
[38;5;246m"
[39m2005 한일 우정의 해 개막식 축사
[38;5;246m"
[39m 개막식:축하
[38;5;246m# … with 260 more rows
[39m
[
noun_bigram 변수에, ngram_delim = ":"
로 토큰의 개별
단어들을 묶어주는 문자에 기본값인 공백이 아닌 콜론(:)을 지정합니다.
그리고 drop = FALSE
는 토큰화하려는 변수인
doc
를 보존합니다.
%>%
president_speech select(title, doc) %>%
filter(row_number() <= 2) %>%
unnest_noun_ngrams(
noun_bigram,
doc,n = 2,
ngram_delim = ":",
drop = FALSE
) 38;5;246m# A tibble: 209 × 3
[39m
[
title doc noun_b…¹38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;250m1
[39m
[38;5;246m"
[39m2005 한일 우정의 해 개막식 축사
[38;5;246m"
[39m
[38;5;246m"
[39m 먼저 한,일 우정의 해 개막식을 … 우정:해
[
[38;5;250m2
[39m
[38;5;246m"
[39m2005 한일 우정의 해 개막식 축사
[38;5;246m"
[39m
[38;5;246m"
[39m 먼저 한,일 우정의 해 개막식을 … 해:개막…
38;5;250m3
[39m
[38;5;246m"
[39m2005 한일 우정의 해 개막식 축사
[38;5;246m"
[39m
[38;5;246m"
[39m 먼저 한,일 우정의 해 개막식을 … 개막식:…
[
[38;5;250m4
[39m
[38;5;246m"
[39m2005 한일 우정의 해 개막식 축사
[38;5;246m"
[39m
[38;5;246m"
[39m 먼저 한,일 우정의 해 개막식을 … 축하:행…
38;5;246m# … with 205 more rows, and abbreviated variable name ¹noun_bigram
[39m
[
unnest_noun_ngrams()
함수는 group_by()
함수
함께 사용할 수 있습니다.
# grouping using group_by() function
%>%
president_speech filter(row_number() <= 4) %>%
mutate(speech_year = substr(date, 1, 4)) %>%
select(speech_year, title, doc) %>%
group_by(speech_year) %>%
unnest_noun_ngrams(
noun_bigram,
doc,n = 2,
ngram_delim = ":"
)38;5;246m# A tibble: 1,759 × 2
[39m
[38;5;246m# Groups: speech_year [2]
[39m
[
speech_year noun_bigram38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;250m1
[39m 2005 우정:해
[38;5;250m2
[39m 2005 해:개막식
[38;5;250m3
[39m 2005 개막식:축하
[38;5;250m4
[39m 2005 축하:행사
[38;5;246m# … with 1,755 more rows
[39m
[
group_by()
함수를 사용하지 않고도 동일한 작업을 수행할
수 있습니다. collapse
인수를 사용하면 됩니다.
# grouping using collapse argument
%>%
president_speech filter(row_number() <= 4) %>%
mutate(speech_year = substr(date, 1, 4)) %>%
select(speech_year, title, doc) %>%
unnest_noun_ngrams(
noun_bigram,
doc,n = 2,
ngram_delim = ":",
collapse = "speech_year"
)38;5;246m# A tibble: 1,759 × 2
[39m
[
speech_year noun_bigram38;5;246m<chr>
[39m
[23m
[3m
[38;5;246m<chr>
[39m
[23m
[3m
[38;5;250m1
[39m 2005 우정:해
[38;5;250m2
[39m 2005 해:개막식
[38;5;250m3
[39m 2005 개막식:축하
[38;5;250m4
[39m 2005 축하:행사
[38;5;246m# … with 1,755 more rows
[39m
[
unnest_noun_ngrams()
는 … 인수를 지원해서
tokenize_noun_ngrams()
에서 사용할 수 있는 인수도
사용가능합니다. 즉, 사용자 정의 사전으로 명사를 추출할 수도 있습니다.
그리고 이런 일련의 작업들이 병렬로 처리됩니다.
args(unnest_noun_ngrams)
function (tbl, output, input, n = 2L, n_min = n, ngram_delim = " ",
drop = TRUE, collapse = NULL, ...)
NULL