class: title0 Do it! 쉽게 배우는 R 텍스트 마이닝 --- class: no-page-num <br> .pull-left[ <img src="https://raw.githubusercontent.com/youngwoos/Doit_textmining/main/cover.png" width="70%" height="70%" /> ] .pull-right[ <br> <br> <br> <svg viewBox="0 0 496 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg> [github.com/youngwoos/Doit_textmining](https://github.com/youngwoos/Doit_textmining) <svg viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M400 32H48A48 48 0 0 0 0 80v352a48 48 0 0 0 48 48h137.25V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.27c-30.81 0-40.42 19.12-40.42 38.73V256h68.78l-11 71.69h-57.78V480H400a48 48 0 0 0 48-48V80a48 48 0 0 0-48-48z"></path></svg> [facebook.com/groups/datacommunity](https://facebook.com/groups/datacommunity) - [네이버책](https://book.naver.com/bookdb/book_detail.nhn?bid=17891971) - [yes24](http://bit.ly/3oUuJOB) - [알라딘](http://bit.ly/3oXOSDn) - [교보문고](https://bit.ly/2LtNOcB) ] --- class: title0 05 의미망 분석: <br> 어떤 맥락에서 단어를 썼을까? --- class: title0-2 We'll make <br-back-20> <img src="../Image/05/05_2_6.png" width="70%" height="70%" /> --- class: title0-2 We'll make <br-back-20> <img src="../Image/05/05_3_2.png" width="70%" height="70%" /> --- class: title0-2 and <br-back-40> <img src="../Image/05/05_4_2.png" width="65%" height="65%" /> --- <br> .large2[.font-jua[목차]] .large[.font-jua[05-1 동시 출현 단어 분석: Co-occurrence analysis]]([link](#05-1)) .large[.font-jua[05-2 동시 출현 네트워크: Co-occurrence network]]([link](#05-2)) .large[.font-jua[05-3 단어 간 상관 분석: Phi coefficient]]([link](#05-3)) .large[.font-jua[05-4 연이어 사용된 단어쌍 분석: n-gram]]([link](#05-4)) --- name: 05-1 class: title1 05-1 동시 출현 단어 분석: Co-occurrence analysis --- ##### 동시 출현 단어 분석(Co-occurrence analysis) - 단어 간의 관계를 살펴보는 분석 방법 - '손-장갑', '머리-모자' 처럼 관계가 있는 단어 파악 - 단어의 관계를 표현한 의미망(semantic network) 만드는데 활용 --- #### 기본적인 전처리 ```r # 기생충 기사 댓글 불러오기 library(readr) raw_news_comment <- read_csv("news_comment_parasite.csv") # 전처리 library(dplyr) library(stringr) library(textclean) news_comment <- raw_news_comment %>% select(reply) %>% mutate(reply = str_replace_all(reply, "[^가-힣]", " "), reply = str_squish(reply), id = row_number()) ``` --- #### 토큰화하기 - 단어가 사용된 맥락을 살펴봐야 하므로 명사, 형용사, 동사 함께 추출 ##### 1. 형태소 분석기를 이용해 품사 기준으로 토큰화하기 - `SimplePos22()` : 문장의 단어를 22개의 품사로 구분 ```r library(tidytext) library(KoNLP) comment_pos <- news_comment %>% unnest_tokens(input = reply, output = word, token = SimplePos22, drop = F) comment_pos %>% select(reply, word) ``` --- ``` ## # A tibble: 39,956 x 2 ## reply word ## <chr> <chr> ## 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 정말/ma ## 2 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 우리/np ## 3 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 집/nc+에/jc… ## 4 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 좋/pa+은/et… ## 5 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 일/nc+이/jc… ## 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 생기/pv+어/e… ## 7 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 기쁘/pa+고/e… ## 8 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 행복한/nc ## 9 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 것/nb+처럼/j… ## 10 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인… 나/np+의/jc… ## # … with 39,946 more rows ``` --- ##### 품사 분리하여 행 구성하기 - 원하는 품사를 추출하기 쉽도록 한 행을 한 품사로 구성하기 - `tidyr::separate_rows()`: - 정규 표현식에 따라 텍스트를 여러 행으로 나누기 - `sep = "[+]"`: `"+"`가 등장할 때마다 행을 나눔 ```r # 품사별로 행 분리 library(tidyr) comment_pos <- comment_pos %>% separate_rows(word, sep = "[+]") comment_pos %>% select(word, reply) ``` --- ``` ## # A tibble: 70,553 x 2 ## word reply ## <chr> <chr> ## 1 정말/ma 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 2 우리/np 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 3 집/nc 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 4 에/jc 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 5 좋/pa 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 6 은/et 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 7 일/nc 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 8 이/jc 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 9 생기/pv 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## 10 어/ec 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 … ## # … with 70,543 more rows ``` --- ##### 3. 품사 추출하기 <br10> ##### (1) 명사 추출하기 - `"/n"`이 붙어있는 단어 추출 - 태그 제거: '/로 시작하는 모든 문자' 제거 ```r # 명사 추출하기 noun <- comment_pos %>% filter(str_detect(word, "/n")) %>% mutate(word = str_remove(word, "/.*$")) noun %>% select(word, reply) ``` --- ``` ## # A tibble: 27,457 x 2 ## word reply ## <chr> <chr> ## 1 우리 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 2 집 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 3 일 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 4 행복한 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 5 것 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 6 나 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 7 일 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 8 양 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 9 행복 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## 10 행복 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 양 행… ## # … with 27,447 more rows ``` --- ```r # 명사 빈도 구하기 noun %>% count(word, sort = T) ``` ``` ## # A tibble: 8,069 x 2 ## word n ## <chr> <int> ## 1 영화 463 ## 2 기생충 445 ## 3 봉준호 372 ## 4 것 353 ## 5 아카데미 252 ## 6 축하 232 ## 7 나 230 ## 8 대한민국 226 ## 9 자랑 222 ## 10 작품상 218 ## # … with 8,059 more rows ``` --- ##### (2) 동사, 형용사 추출하기 - 동사 `"/pv"`, 형용사:`"/pa"` 붙어있는 단어 추출 - 단어 뒤에 태그 대신 '다'를 붙여 이해하기 편하게 수정하기 - ex) "받" → "받다", "멋지" → "멋지다" ```r # 동사, 형용사 추출하기 pvpa <- comment_pos %>% filter(str_detect(word, "/pv|/pa")) %>% # "/pv", "/pa" 추출 mutate(word = str_replace(word, "/.*$", "다")) # "/"로 시작 문자를 "다"로 바꾸기 pvpa %>% select(word, reply) ``` --- ``` ## # A tibble: 5,317 x 2 ## word reply ## <chr> <chr> ## 1 좋다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 2 생기다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 3 기쁘다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 4 축하드리다… 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 5 기쁘다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 6 기쁘다 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다 축하드… ## 7 기쁘다 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다 축하드… ## 8 축하드리다… 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다 축하드… ## 9 불다 우리나라의 영화감독분들 그리고 앞으로 그 꿈을 그리는 분들에게 큰… ## 10 크다 우리나라의 영화감독분들 그리고 앞으로 그 꿈을 그리는 분들에게 큰… ## # … with 5,307 more rows ``` --- ##### (3) 추출한 데이터 결합하기 - 추출한 단어 결합하기 - 이해할 수 있는 두 글자 이상 단어만 남기기 ```r # 품사 결합 comment <- bind_rows(noun, pvpa) %>% filter(str_count(word) >= 2) %>% arrange(id) comment %>% select(word, reply) ``` --- ``` ## # A tibble: 26,860 x 2 ## word reply ## <chr> <chr> ## 1 우리 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 2 행복한 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 3 행복 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 4 행복 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 5 좋다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 6 생기다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 7 기쁘다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 8 축하드리다… 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 9 기쁘다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인 … ## 10 시국 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다 축하드… ## # … with 26,850 more rows ``` --- .box[ .info[<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;position:relative;display:inline-block;top:.1em;fill:#FF7333;"> [ comment ] <path d="M505.12019,19.09375c-1.18945-5.53125-6.65819-11-12.207-12.1875C460.716,0,435.507,0,410.40747,0,307.17523,0,245.26909,55.20312,199.05238,128H94.83772c-16.34763.01562-35.55658,11.875-42.88664,26.48438L2.51562,253.29688A28.4,28.4,0,0,0,0,264a24.00867,24.00867,0,0,0,24.00582,24H127.81618l-22.47457,22.46875c-11.36521,11.36133-12.99607,32.25781,0,45.25L156.24582,406.625c11.15623,11.1875,32.15619,13.15625,45.27726,0l22.47457-22.46875V488a24.00867,24.00867,0,0,0,24.00581,24,28.55934,28.55934,0,0,0,10.707-2.51562l98.72834-49.39063c14.62888-7.29687,26.50776-26.5,26.50776-42.85937V312.79688c72.59753-46.3125,128.03493-108.40626,128.03493-211.09376C512.07526,76.5,512.07526,51.29688,505.12019,19.09375ZM384.04033,168A40,40,0,1,1,424.05,128,40.02322,40.02322,0,0,1,384.04033,168Z"></path></svg> 명사, 동사, 형용사를 한 번에 추출하기] - 명사, 동사, 형용사를 추출해 결합한 후 두 글자 이상만 남기기 ```r comment_new <- comment_pos %>% separate_rows(word, sep = "[+]") %>% filter(str_detect(word, "/n|/pv|/pa")) %>% mutate(word = ifelse(str_detect(word, "/pv|/pa"), str_replace(word, "/.*$", "다"), str_remove(word, "/.*$"))) %>% filter(str_count(word) >= 2) %>% arrange(id) ``` ] --- #### 단어 동시 출현 빈도 구하기 - `widyr::pairwise_count()` - `item`: 단어 - `feature`: 텍스트 구분 기준 - `sort = T`: 빈도가 높은 순으로 출력 결과 정렬 ```r install.packages("widyr") library(widyr) pair <- comment %>% pairwise_count(item = word, feature = id, sort = T) pair ``` --- ``` ## # A tibble: 245,920 x 3 ## item1 item2 n ## <chr> <chr> <dbl> ## 1 영화 기생충 111 ## 2 기생충 영화 111 ## 3 감독 봉준호 86 ## 4 봉준호 감독 86 ## 5 감독님 봉준호 66 ## 6 봉준호 감독님 66 ## 7 만들다 영화 57 ## 8 영화 만들다 57 ## 9 기생충 봉준호 54 ## 10 블랙리스트 감독 54 ## # … with 245,910 more rows ``` -- - 한 단어를 기준으로 함께 사용된 모든 단어의 빈도를 구함 - 순서를 바꿔가며 같은 빈도를 지니는 두 개의 행으로 구성됨 - ex) "영화-기생충", "기생충-영화" --- ##### 특정 단어와 자주 함께 사용된 단어 살펴보기 .pull-left[ ```r pair %>% filter(item1 == "영화") ``` ``` ## # A tibble: 2,313 x 3 ## item1 item2 n ## <chr> <chr> <dbl> ## 1 영화 기생충 111 ## 2 영화 만들다 57 ## 3 영화 봉준호 52 ## 4 영화 받다 48 ## 5 영화 한국 46 ## 6 영화 아카데미 42 ## 7 영화 같다 41 ## 8 영화 감독 39 ## 9 영화 아니다 38 ## 10 영화 좋다 35 ## # … with 2,303 more rows ``` ] .pull-right[ ```r pair %>% filter(item1 == "봉준호") ``` ``` ## # A tibble: 1,579 x 3 ## item1 item2 n ## <chr> <chr> <dbl> ## 1 봉준호 감독 86 ## 2 봉준호 감독님 66 ## 3 봉준호 기생충 54 ## 4 봉준호 영화 52 ## 5 봉준호 블랙리스트 48 ## 6 봉준호 대한민국 38 ## 7 봉준호 자랑 33 ## 8 봉준호 축하드리다 30 ## 9 봉준호 송강호 30 ## 10 봉준호 축하 25 ## # … with 1,569 more rows ``` ] --- class: title1 05-2 동시 출현 네트워크: Co-occurrence network --- ##### 동시 출현 네트워크(co-occurrence network) - 동시 출현 빈도를 이용해 단어의 관계를 네트워크 형태로 표현 - 단어들이 어떤 맥락에서 함께 사용되었는지 이해할 수 있다 -- #### 네트워크 그래프 데이터 만들기 - `tidygraph::as_tbl_graph()` - 동시 출현 빈도 데이터를 '네트워크 그래프 데이터'로 변환하기 - 단어를 나타내는 노드(node, 꼭짓점) - 단어를 연결하는 엣지(edge, 선) <svg viewBox="0 0 352 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M176 80c-52.94 0-96 43.06-96 96 0 8.84 7.16 16 16 16s16-7.16 16-16c0-35.3 28.72-64 64-64 8.84 0 16-7.16 16-16s-7.16-16-16-16zM96.06 459.17c0 3.15.93 6.22 2.68 8.84l24.51 36.84c2.97 4.46 7.97 7.14 13.32 7.14h78.85c5.36 0 10.36-2.68 13.32-7.14l24.51-36.84c1.74-2.62 2.67-5.7 2.68-8.84l.05-43.18H96.02l.04 43.18zM176 0C73.72 0 0 82.97 0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16v.06h48v-.12c-.01-4.77-.72-9.51-2.15-14.07-5.59-17.81-22.82-64.77-62.17-109.67-20.54-23.43-31.52-53.15-31.61-84.14-.2-73.64 59.67-128 127.95-128 70.58 0 128 57.42 128 128 0 30.97-11.24 60.85-31.65 84.14-39.11 44.61-56.42 91.47-62.1 109.46a47.507 47.507 0 0 0-2.22 14.3v.1h48v-.05c9.68-33.37 35.78-73.18 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0z"></path></svg> 네트워크가 너무 복잡하지 않도록 25회 이상 사용된 단어 추출해 생성 ```r install.packages("tidygraph") library(tidygraph) graph_comment <- pair %>% filter(n >= 25) %>% as_tbl_graph() graph_comment ``` ``` ## # A tbl_graph: 30 nodes and 108 edges ## # ## # A directed simple graph with 2 components ## # ## # Node Data: 30 x 1 (active) ## name ## <chr> ## 1 영화 ## 2 기생충 ## 3 감독 ## 4 봉준호 ## 5 감독님 ## 6 만들다 ## # … with 24 more rows ## # ## # Edge Data: 108 x 3 ## from to n ## <int> <int> <dbl> ## 1 1 2 111 ## 2 2 1 111 ## 3 3 4 86 ## # … with 105 more rows ``` --- #### 네트워크 그래프 만들기 - `ggraph::ggraph()` ```r install.packages("ggraph") library(ggraph) ggraph(graph_comment) + geom_edge_link() + # 엣지 geom_node_point() + # 노드 geom_node_text(aes(label = name)) # 텍스트 ``` <img src="../Image/05/05_2_1.png" width="40%" height="40%" /> --- <img src="../Image/05/05_2_1.png" width="80%" height="80%" /> --- .box[ .info[<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;position:relative;display:inline-block;top:.1em;fill:#FF7333;"> [ comment ] <path d="M505.12019,19.09375c-1.18945-5.53125-6.65819-11-12.207-12.1875C460.716,0,435.507,0,410.40747,0,307.17523,0,245.26909,55.20312,199.05238,128H94.83772c-16.34763.01562-35.55658,11.875-42.88664,26.48438L2.51562,253.29688A28.4,28.4,0,0,0,0,264a24.00867,24.00867,0,0,0,24.00582,24H127.81618l-22.47457,22.46875c-11.36521,11.36133-12.99607,32.25781,0,45.25L156.24582,406.625c11.15623,11.1875,32.15619,13.15625,45.27726,0l22.47457-22.46875V488a24.00867,24.00867,0,0,0,24.00581,24,28.55934,28.55934,0,0,0,10.707-2.51562l98.72834-49.39063c14.62888-7.29687,26.50776-26.5,26.50776-42.85937V312.79688c72.59753-46.3125,128.03493-108.40626,128.03493-211.09376C512.07526,76.5,512.07526,51.29688,505.12019,19.09375ZM384.04033,168A40,40,0,1,1,424.05,128,40.02322,40.02322,0,0,1,384.04033,168Z"></path></svg> 그래프를 큰 화면에 출력하는 방법] - Plots 창의 Zoom 아이콘 클릭 이미지 출력 창을 별도로 열어 큰 화면에서 보기 - 윈도우: `windows()` - macOS: `x11()` ] --- ##### 그래프 다듬기 ```r # 한글 폰트 설정 library(showtext) font_add_google(name = "Nanum Gothic", family = "nanumgothic") showtext_auto() ``` --- ##### 엣지와 노드의 색깔, 크기, 텍스트 위치 수정 - `ggraph(layout = "fr")`: 네트워크 형태 결정 - 난수를 이용해 매번 형태 달라짐 → `set.seed()`로 난수 고정 ```r set.seed(1234) # 난수 고정 ggraph(graph_comment, layout = "fr") + # 레이아웃 geom_edge_link(color = "gray50", # 엣지 색깔 alpha = 0.5) + # 엣지 명암 geom_node_point(color = "lightcoral", # 노드 색깔 size = 5) + # 노드 크기 geom_node_text(aes(label = name), # 텍스트 표시 repel = T, # 노드밖 표시 size = 5, # 텍스트 크기 family = "nanumgothic") + # 폰트 theme_graph() # 배경 삭제 ``` <svg viewBox="0 0 576 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;position:relative;display:inline-block;top:.1em;fill:#FF7333;"> [ comment ] <path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg> 노드 텍스트 폰트 `geom_node_text()`의 `family`로 별도 설정. `theme()`으로 적용 안됨. --- <img src="../Image/05/05_2_2.png" width="80%" height="80%" /> --- ##### 네트워크 그래프 함수 만들기 ```r word_network <- function(x) { ggraph(x, layout = "fr") + geom_edge_link(color = "gray50", alpha = 0.5) + geom_node_point(color = "lightcoral", size = 5) + geom_node_text(aes(label = name), repel = T, size = 5, family = "nanumgothic") + theme_graph() } ``` ```r set.seed(1234) word_network(graph_comment) ``` --- #### 유의어 처리하기 - 유의어(synonyms): 표현은 다르지만 의미가 비슷한 단어 - ex) `"감독"`, `"봉감독"`, `"봉준호감독"` - 유의어 통일하기: 네트워크 구조가 간결해지고 단어의 관계가 좀 더 분명하게 드러남 --- ```r # 유의어 처리하기 comment <- comment %>% mutate(word = ifelse(str_detect(word, "감독") & !str_detect(word, "감독상"), "봉준호", word), word = ifelse(word == "오르다", "올리다", word), word = ifelse(str_detect(word, "축하"), "축하", word)) # 단어 동시 출현 빈도 구하기 pair <- comment %>% pairwise_count(item = word, feature = id, sort = T) # 네트워크 그래프 데이터 만들기 graph_comment <- pair %>% filter(n >= 25) %>% as_tbl_graph() # 네트워크 그래프 만들기 set.seed(1234) word_network(graph_comment) ``` ![](05-co-occurrenceAnalysis_files/figure-html/unnamed-chunk-36-1.png)<!-- --> --- <img src="../Image/05/05_2_3.png" width="80%" /> --- #### 연결 중심성과 커뮤니티 표현하기 - 네트워크 그래프는 단어 노드가 많아 어떤 단어 노드 중심으로 해석할지 판단 어려움 - 연결 중심성과 커뮤니티를 표현하면 단어의 관계를 더 분명하게 파악할 수 있다 -- ##### 연결 중심성(degree centrality) - 노드가 다른 노드들과 얼마나 밀접하게 연결되는지 나타낸 값 - 연결 중심성으로 노드 크기를 조정하면 어떤 단어를 눈여겨봐야 할지 판단하기 쉬워진다 <img src="../Image/05/05_2_4.png" width="50%" /> --- #### 연결 중심성과 커뮤니티 표현하기 - 네트워크 그래프는 단어 노드가 많아 어떤 단어 노드 중심으로 해석할지 판단 어려움 - 연결 중심성과 커뮤니티를 표현하면 단어의 관계를 더 분명하게 파악할 수 있다 ##### 커뮤니티(community) - 단어 간의 관계가 가까워 빈번하게 연결된 노드 집단 - 노드를 커뮤니티별로 구분 지어 서로 다른 색으로 표현하면 네트워크 구조를 이해하기 쉬워진다 <img src="../Image/05/05_2_5.png" width="50%" /> --- ##### 1. 네트워크 그래프 데이터에 연결 중심성, 커뮤니티 변수 추가하기 <br10> - 네트워크 그래프 데이터 만들기: `as_tbl_graph()` - `directed = F`: 방향성 없도록 설정 - `group_infomap()`은 방향성 없는 네트워크 그래프 데이터에서만 커뮤니티를 찾아줌 -- - 연결 중심성 변수 추가하기: `centrality_degree()` - 커뮤니티 변수 추가하기: `group_infomap()` - 커뮤니티가 정수형 숫자이므로 노드가 그라데이션으로 표현됨 - `as.factor()`: factor 타입으로 변환해 노드 그룹별로 다른 색으로 표현 ```r set.seed(1234) graph_comment <- pair %>% filter(n >= 25) %>% as_tbl_graph(directed = F) %>% mutate(centrality = centrality_degree(), # 연결 중심성 group = as.factor(group_infomap())) # 커뮤니티 graph_comment ``` --- ``` ## # A tbl_graph: 36 nodes and 152 edges ## # ## # An undirected multigraph with 1 component ## # ## # Node Data: 36 x 3 (active) *## name centrality group *## <chr> <dbl> <fct> *## 1 봉준호 62 4 *## 2 축하 34 2 *## 3 영화 26 3 *## 4 블랙리스트 6 6 *## 5 기생충 26 1 *## 6 대한민국 10 3 *## # … with 30 more rows ## # ## # Edge Data: 152 x 3 ## from to n ## <int> <int> <dbl> ## 1 1 2 198 ## 2 1 2 198 ## 3 1 3 119 ## # … with 149 more rows ``` --- #### 2. 네트워크 그래프에 연결 중심성, 커뮤니티 표현하기 - `geom_node_point(aes())` - `size = centrality`: 연결 중심성에 따라 노드 크기 설정 - `color = group`: 커뮤니티 별로 노드 색깔 다르게 - `geom_node_point(show.legend = F)`: 범례 제거 - `scale_size(range = c(5, 15))`: 노드 크기 5~15 범위 유지 - 너무 크거나 작으면 알아보기 불편 --- ```r set.seed(1234) ggraph(graph_comment, layout = "fr") + # 레이아웃 geom_edge_link(color = "gray50", # 엣지 색깔 alpha = 0.5) + # 엣지 명암 geom_node_point(aes(size = centrality, # 노드 크기 color = group), # 노드 색깔 show.legend = F) + # 범례 삭제 scale_size(range = c(5, 15)) + # 노드 크기 범위 geom_node_text(aes(label = name), # 텍스트 표시 repel = T, # 노드밖 표시 size = 5, # 텍스트 크기 family = "nanumgothic") + # 폰트 theme_graph() # 배경 삭제 ``` --- <img src="../Image/05/05_2_6.png" width="80%" /> --- #### 3. 네트워크의 주요 단어 살펴보기 ##### 주요 단어의 커뮤니티 살펴보기 ```r graph_comment %>% filter(name == "봉준호") ``` ``` ## # A tbl_graph: 1 nodes and 0 edges ## # ## # An unrooted tree ## # ## # Node Data: 1 x 3 (active) ## name centrality group ## <chr> <dbl> <fct> ## 1 봉준호 62 4 ## # ## # Edge Data: 0 x 3 ## # … with 3 variables: from <int>, to <int>, n <dbl> ``` --- #### 같은 커뮤니티로 분류된 단어 살펴보기 ```r graph_comment %>% filter(group == 4) %>% arrange(-centrality) %>% data.frame() ``` ``` ## name centrality group ## 1 봉준호 62 4 ## 2 받다 10 4 ## 3 자랑 6 4 ## 4 만들다 4 4 ``` --- #### 연결 중심성이 높은 주요 단어 살펴보기 .pull-left[ ```r graph_comment %>% arrange(-centrality) ``` ] .pull-right[ ``` ## # A tbl_graph: 36 nodes and 152 edges ## # ## # An undirected multigraph with 1 component ## # ## # Node Data: 36 x 3 (active) ## name centrality group ## <chr> <dbl> <fct> ## 1 봉준호 62 4 ## 2 축하 34 2 ## 3 영화 26 3 ## 4 기생충 26 1 ## 5 작품상 14 5 ## 6 대한민국 10 3 ## # … with 30 more rows ## # ## # Edge Data: 152 x 3 ## from to n ## <int> <int> <dbl> ## 1 1 2 198 ## 2 1 2 198 ## 3 1 3 119 ## # … with 149 more rows ``` ] --- #### 연결 중심성이 높은 주요 단어 살펴보기 - 2번 커뮤니티로 분류된 단어 ```r graph_comment %>% filter(group == 2) %>% arrange(-centrality) %>% data.frame() ``` ``` ## name centrality group ## 1 축하 34 2 ## 2 좋다 8 2 ## 3 진심 4 2 ## 4 수상 4 2 ## 5 없다 4 2 ## 6 대단 2 2 ## 7 기쁘다 2 2 ``` --- #### 4. 주요 단어가 사용된 원문 살펴보기 ```r news_comment %>% filter(str_detect(reply, "봉준호") & str_detect(reply, "대박")) %>% select(reply) ``` ``` ## # A tibble: 19 x 1 ## reply ## <chr> ## 1 대박 대박 진짜 대박 봉준호 감독님과 우리 배우들 너무 다랑스러워요… ## 2 내가 죽기전에 아카데미에서 한국어를 들을줄이야 봉준호대박 기생충대박… ## 3 대박 관왕이라니 축하합니다 봉준호를 배출한 충무로 그리고 문화강국 대한 민국… ## 4 우와 대박 진자 대단하다 봉준호 ## 5 봉준호 경사났네 대박중에 대에박 축하합니다 ## 6 봉준호 작품상 탔다 대박 ## 7 봉준호 군대 면제시켜도될듯 대박 여윽시 위대한 한국에는 위대한 봉준호 형님이 계시지… ## 8 아니 다른상을 받은것도 충분히 대단하고 굉장하지만 최고의 영예인 작품상을 받은거는 … ## 9 봉준호 군대 면제시켜도될듯 대박 여윽시 위대한 한국에는 위대한 봉준호 형님이 계시지… ## 10 봉준호감독님대박 축하합니다 ## # … with 9 more rows ``` --- ```r news_comment %>% filter(str_detect(reply, "박근혜") & str_detect(reply, "블랙리스트")) %>% select(reply) ``` ``` ## # A tibble: 63 x 1 ## reply ## <chr> ## 1 일베와 자한당이 싫어하는 봉준호 감독이 아카데미에서 상받으니 쪽바리들처럼 엄청 싫어… ## 2 박근혜 블랙리스트 로 낙인찍은 봉준호 감독님이 아시아 최초로 오스카에서 상을 받아버… ## 3 우리나라에서만 좌파다 빨갱이다 라고 비하함 박근혜 때 이런 세계적 감독을 블랙리스트… ## 4 박근혜 최순실 블랙리스트에 오른 훌륭하신 감독님 축하합니다… ## 5 박근혜정부가 얼마나 썩고 무능했냐면 각종 영화제에서 최고상 수상을 받는 유능한 감독… ## 6 넷상 보수들 만큼 이중적인 새 끼들 없음 봉준호 송강호 보고 종북좌빨 홍어드립 치던… ## 7 박근혜 자한당 독재시절 봉준호 송강호를 블랙리스트 올려놓고 활동 방해 감시하면서 괴… ## 8 대단합니다 김연아 방탄 봉준호 스포츠 음악 영화 못하는게 없어요 좌빨 감독이라 좌파… ## 9 송강호 봉준호 박근혜 이명박 시절 블랙리스트 이제 어떻게 깔려구… ## 10 이명박근혜정권당시 좌파감독이라고 블랙리스트까지 올랏던 봉준호 역사적위업을 달성햇네 … ## # … with 53 more rows ``` --- ```r news_comment %>% filter(str_detect(reply, "기생충") & str_detect(reply, "조국")) %>% select(reply) ``` ``` ## # A tibble: 64 x 1 ## reply ## <chr> ## 1 조국이가 받아야 한다 기생충 스토리 제공 ## 2 한번도경험하지 못한 조국가족사기단기생충 개봉박두… ## 3 와 조국 가족 사기단 부제 기생충 최고 ## 4 문재인과 조국 기생충 리얼 ## 5 기생충은 좌좀 조국 가족을 패러디한 영화라서 우파들도 열광하고 있는 것이다 같은 영… ## 6 조국 가족이 기생충 영화를 꼭 봐야되는데 ## 7 좌파 인생영화인데 좌파 기생충들에게 이 상을 받쳐라 조국 서울대 문서위조학과 교수님… ## 8 기생충 조국 봉준호 만세 ## 9 봉준호감독님 글로벌 영화계 큰상수상을 진심으로 축하합니다 다만 기생충 작품은 조국 … ## 10 기생충보면서 조국생각난사람 나쁜일라나 봉준호 감독님이 현 시대를 참 잘 반영해서 만… ## # … with 54 more rows ``` <br> <svg viewBox="0 0 352 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M176 80c-52.94 0-96 43.06-96 96 0 8.84 7.16 16 16 16s16-7.16 16-16c0-35.3 28.72-64 64-64 8.84 0 16-7.16 16-16s-7.16-16-16-16zM96.06 459.17c0 3.15.93 6.22 2.68 8.84l24.51 36.84c2.97 4.46 7.97 7.14 13.32 7.14h78.85c5.36 0 10.36-2.68 13.32-7.14l24.51-36.84c1.74-2.62 2.67-5.7 2.68-8.84l.05-43.18H96.02l.04 43.18zM176 0C73.72 0 0 82.97 0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16v.06h48v-.12c-.01-4.77-.72-9.51-2.15-14.07-5.59-17.81-22.82-64.77-62.17-109.67-20.54-23.43-31.52-53.15-31.61-84.14-.2-73.64 59.67-128 127.95-128 70.58 0 128 57.42 128 128 0 30.97-11.24 60.85-31.65 84.14-39.11 44.61-56.42 91.47-62.1 109.46a47.507 47.507 0 0 0-2.22 14.3v.1h48v-.05c9.68-33.37 35.78-73.18 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0z"></path></svg> `tidygraph` 패키지의 연결 중심성 지표, 커뮤니티 탐지 알고리즘: [tidygraph.data-imaginist.com](https://tidygraph.data-imaginist.com) --- class: title1 05-3 단어 간 상관 분석: Phi coefficient --- ##### 동시 출현 빈도의 한계 - 대부분의 단어와 자주 함께 사용되는 단어쌍 다수 - ex) `"영화"-"기생충"` - 다른 단어에 비해 상대적으로 자주 함께 사용된 단어가 무엇인지 살펴봐야 한다 -- ##### 파이 계수(phi coefficient) - 두 단어가 함께 사용되는 경우가 각각 사용되는 경우에 비해 얼마나 많은지 나타낸 지표 - 상대적으로 관련성이 큰 단어 파악하는데 활용 - 어떤 단어와 자주 함께 사용되지만 다른 단어와는 자주 함께 사용되지 않는 단어 --- ##### 파이 계수의 의미 - X, Y 두 단어가 있을 때, 여러 텍스트에서 두 단어의 사용 여부를 놓고 가능한 모든 경우 - X, Y 모두 있음( `\(a\)` ) - X, Y 모두 없음( `\(d\)` ) - X만 있음( `\(b\)` ) - Y만 있음( `\(c\)` ) <br10> .center[ <img src="../Image/etc/05_3_table1.png" width="60%" height="60%" /> ] -- <br> `$$\phi=\frac{ad-bc}{\sqrt{(a+b)(c+d)(a+c)(b+d)}}$$` --- ##### 파이 계수의 의미 - -1 ~ +1 - +1에 가까울수록 두 단어가 자주 함께 사용되어 관련성이 크다는 의미 - -1에 가까울수록 함께 사용되는 경우가 드물어 관련성이 작다는 의미 --- #### 파이 계수 구하기 - `widyr::pairwise_cor()` - `item`: 단어 - `feature`: 텍스트 구분 기준 - `sort = T`: 파이 계수 높은순 정렬 .pull-left[ ```r word_cors <- comment %>% add_count(word) %>% filter(n >= 20) %>% pairwise_cor(item = word, feature = id, sort = T) word_cors ``` <svg viewBox="0 0 352 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M176 80c-52.94 0-96 43.06-96 96 0 8.84 7.16 16 16 16s16-7.16 16-16c0-35.3 28.72-64 64-64 8.84 0 16-7.16 16-16s-7.16-16-16-16zM96.06 459.17c0 3.15.93 6.22 2.68 8.84l24.51 36.84c2.97 4.46 7.97 7.14 13.32 7.14h78.85c5.36 0 10.36-2.68 13.32-7.14l24.51-36.84c1.74-2.62 2.67-5.7 2.68-8.84l.05-43.18H96.02l.04 43.18zM176 0C73.72 0 0 82.97 0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16v.06h48v-.12c-.01-4.77-.72-9.51-2.15-14.07-5.59-17.81-22.82-64.77-62.17-109.67-20.54-23.43-31.52-53.15-31.61-84.14-.2-73.64 59.67-128 127.95-128 70.58 0 128 57.42 128 128 0 30.97-11.24 60.85-31.65 84.14-39.11 44.61-56.42 91.47-62.1 109.46a47.507 47.507 0 0 0-2.22 14.3v.1h48v-.05c9.68-33.37 35.78-73.18 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0z"></path></svg> `add_count()` 원자료에 빈도 나타낸 변수 추가 ] -- .pull-right[ ``` ## # A tibble: 26,732 x 3 ## item1 item2 correlation ## <chr> <chr> <dbl> ## 1 올리다 블랙리스트 0.478 ## 2 블랙리스트 올리다 0.478 ## 3 역사 쓰다 0.370 ## 4 쓰다 역사 0.370 ## 5 박근혜 블랙리스트 0.322 ## 6 블랙리스트 박근혜 0.322 ## 7 가족 조국 0.306 ## 8 조국 가족 0.306 ## 9 작품상 감독상 0.276 ## 10 감독상 작품상 0.276 ## # … with 26,722 more rows ``` ] --- #### 특정 단어와 관련성이 큰 단어 살펴보기 .pull-left[ ```r word_cors %>% filter(item1 == "대한민국") ``` ``` ## # A tibble: 163 x 3 ## item1 item2 correlation ## <chr> <chr> <dbl> ## 1 대한민국 국민 0.182 ## 2 대한민국 자랑 0.158 ## 3 대한민국 위상 0.149 ## 4 대한민국 국격 0.129 ## 5 대한민국 위대한 0.100 ## 6 대한민국 세계 0.0910 ## 7 대한민국 문화 0.0757 ## 8 대한민국 감사합 0.0724 ## 9 대한민국 나라 0.0715 ## 10 대한민국 오늘 0.0715 ## # … with 153 more rows ``` ] .pull-right[ ```r word_cors %>% filter(item1 == "역사") ``` ``` ## # A tibble: 163 x 3 ## item1 item2 correlation ## <chr> <chr> <dbl> ## 1 역사 쓰다 0.370 ## 2 역사 최초 0.117 ## 3 역사 한국 0.0982 ## 4 역사 순간 0.0910 ## 5 역사 한국영화 0.0821 ## 6 역사 아니다 0.0774 ## 7 역사 감사 0.0654 ## 8 역사 영광 0.0640 ## 9 역사 영화제 0.0596 ## 10 역사 오스카 0.0593 ## # … with 153 more rows ``` ] --- #### 파이 계수로 막대 그래프 만들기 ##### 1. 관심 단어별로 파이 계수가 큰 단어 추출하기 ```r # 관심 단어 목록 생성 target <- c("대한민국", "역사", "수상소감", "조국", "박근혜", "블랙리스트") top_cors <- word_cors %>% filter(item1 %in% target) %>% group_by(item1) %>% slice_max(correlation, n = 8) ``` --- ##### 2. 막대 그래프 만들기 ```r # 그래프 순서 정하기 top_cors$item1 <- factor(top_cors$item1, levels = target) library(ggplot2) ggplot(top_cors, aes(x = reorder_within(item2, correlation, item1), y = correlation, fill = item1)) + geom_col(show.legend = F) + facet_wrap(~ item1, scales = "free") + coord_flip() + scale_x_reordered() + labs(x = NULL) + theme(text = element_text(family = "nanumgothic")) ``` --- <img src="05-co-occurrenceAnalysis_files/figure-html/unnamed-chunk-60-1.png" width="90%" /> --- #### 파이 계수로 네트워크 그래프 만들기 ##### 1. 네트워크 그래프 데이터 만들기. 연결 중심성과 커뮤니티 추가하기 ```r set.seed(1234) graph_cors <- word_cors %>% filter(correlation >= 0.15) %>% as_tbl_graph(directed = F) %>% mutate(centrality = centrality_degree(), group = as.factor(group_infomap())) ``` --- ##### 2. 네트워크 그래프 만들기 ```r set.seed(1234) ggraph(graph_cors, layout = "fr") + geom_edge_link(color = "gray50", aes(edge_alpha = correlation, # 엣지 명암 edge_width = correlation), # 엣지 두께 show.legend = F) + # 범례 삭제 scale_edge_width(range = c(1, 4)) + # 엣지 두께 범위 geom_node_point(aes(size = centrality, color = group), show.legend = F) + scale_size(range = c(5, 10)) + geom_node_text(aes(label = name), repel = T, size = 5, family = "nanumgothic") + theme_graph() ``` --- <img src="../Image/05/05_3_2.png" width="80%" /> --- ##### 동시 출현 빈도, 파이 계수로 만든 네트워크 그래프의 차이점 <br10> - **동시 출현 빈도**를 이용한 네트워크 그래프 - 여러 단어와 자주 함께 사용된 단어쌍 중심으로 네트워크 형성 - 노드 대부분이 서로 연결되어 구조가 복잡하고 군집이 잘 드러나지 않음 - 자주 사용된 단어를 파악할 때 활용 -- <br> - **파이 계수**를 이용한 네트워크 그래프 - 다른 단어에 비해 상대적으로 자주 함께 사용된 단어쌍 중심으로 네트워크 형성 - 관련성이 큰 단어끼리만 연결되어 단어 군집이 명확하게 드러남 - 밀접하게 관련된 단어쌍 파악할 때 활용 --- ##### 동시 출현 빈도, 파이 계수로 만든 네트워크 그래프의 차이점 <br10> - **동시 출현 빈도**를 이용한 네트워크 그래프 - .orange[**여러 단어와 자주 함께 사용된 단어쌍**] 중심으로 네트워크 형성 - 노드 대부분이 서로 연결되어 구조가 복잡하고 군집이 잘 드러나지 않음 - 자주 사용된 단어를 파악할 때 활용 <br> - **파이 계수**를 이용한 네트워크 그래프 - .orange[**다른 단어에 비해 상대적으로 자주 함께 사용된 단어쌍**] 중심으로 네트워크 형성 - 관련성이 큰 단어끼리만 연결되어 단어 군집이 명확하게 드러남 - 밀접하게 관련된 단어쌍 파악할 때 활용 --- class: title1 05-4 연이어 사용된 단어쌍 분석: n-gram --- - 같은 단어도 함께 사용된 단어에 따라 의미가 달라짐 - 어떤 단어는 다른 단어와 연결되어 새로운 의미를 만들어냄 - ex) - '사과를 먹다', '사과를 하다' - '감을 잡다', '귀가 얇다' -- - 동시 출현 빈도와 파이 계수의 한계: 단어가 함께 사용된 횟수만 고려 - 단어가 연결될 때 생기는 의미 무시 - 이해하기 어려운 단어쌍 등장 -- <br> - 단어가 연결될 때 생기는 의미를 고려하려면 **'자주 연이어 사용된 단어'**를 살펴봐야 한다 --- #### 엔그램(n-gram) - 연이어 사용된 n개의 단어 - 두 단어 연속: 바이그램(bigram) 또는 2-gram - 세 단어 연속: 트라이그램(trigram) 또는 3-gram .center[ <img src="../Image/etc/05_4_table1.png" width="70%" /> ] -- - **텍스트를 엔그램으로 토큰화하면** - 단어 앞뒤에 연이어 사용된 단어를 함께 살펴봄: 얼마나 자주 '연이어' 사용된 단어쌍인가? - 단어가 연결될 때 생기는 의미와 맥락을 이해할 수 있음 - 대다수의 텍스트에 사용된 평범한 단어쌍이 아니라 분명한 의미를 드러내는 단어쌍 발견 --- #### 엔그램으로 토큰화하기 **샘플 텍스트로 엔그램 토큰화해보기** <br-back-10> - `tidytext::unnest_tokens()` - `token = "ngrams"` - `n`: 기준 단어 수 ```r text <- tibble(value = "대한민국은 민주공화국이다. 대한민국의 주권은 국민에게 있고, 모든 권력은 국민으로부터 나온다.") text ``` ``` ## # A tibble: 1 x 1 ## value ## <chr> ## 1 대한민국은 민주공화국이다. 대한민국의 주권은 국민에게 있고, 모든 권력은 국민으로부터 나온다. ``` --- .pull-left[ ```r # 바이그램 토큰화 text %>% unnest_tokens(input = value, output = word, token = "ngrams", * n = 2) ``` ``` ## # A tibble: 9 x 1 ## word ## <chr> ## 1 대한민국은 민주공화국이다 ## 2 민주공화국이다 대한민국의 ## 3 대한민국의 주권은 ## 4 주권은 국민에게 ## 5 국민에게 있고 ## 6 있고 모든 ## 7 모든 권력은 ## 8 권력은 국민으로부터 ## 9 국민으로부터 나온다 ``` ] .pull-right[ ```r # 트라이그램 토큰화 text %>% unnest_tokens(input = value, output = word, token = "ngrams", * n = 3) ``` ``` ## # A tibble: 8 x 1 ## word ## <chr> ## 1 대한민국은 민주공화국이다 대한민국의 ## 2 민주공화국이다 대한민국의 주권은 ## 3 대한민국의 주권은 국민에게 ## 4 주권은 국민에게 있고 ## 5 국민에게 있고 모든 ## 6 있고 모든 권력은 ## 7 모든 권력은 국민으로부터 ## 8 권력은 국민으로부터 나온다 ``` ] --- - 단어 기준 토큰화 = 유니그램(unigram) 토큰화 .pull-left[ ```r # 단어 기준 토큰화 text %>% unnest_tokens(input = value, output = word, * token = "words") ``` ``` ## # A tibble: 10 x 1 ## word ## <chr> ## 1 대한민국은 ## 2 민주공화국이다 ## 3 대한민국의 ## 4 주권은 ## 5 국민에게 ## 6 있고 ## 7 모든 ## 8 권력은 ## 9 국민으로부터 ## 10 나온다 ``` ] .pull-right[ ```r # 유니그램 토큰화 text %>% unnest_tokens(input = value, output = word, * token = "ngrams", * n = 1) ``` ``` ## # A tibble: 10 x 1 ## word ## <chr> ## 1 대한민국은 ## 2 민주공화국이다 ## 3 대한민국의 ## 4 주권은 ## 5 국민에게 ## 6 있고 ## 7 모든 ## 8 권력은 ## 9 국민으로부터 ## 10 나온다 ``` ] --- #### 기사 댓글로 바이그램 만들기 ##### (1) 명사, 동사, 형용사 추출하기 - `comment_pos` 이용: 댓글을 형태소로 토큰화 후 품사별로 행 분리 - 명사, 동사, 형용사를 추출해 결합한 후 두 글자 이상만 남김 ```r comment_new <- comment_pos %>% separate_rows(word, sep = "[+]") %>% filter(str_detect(word, "/n|/pv|/pa")) %>% mutate(word = ifelse(str_detect(word, "/pv|/pa"), str_replace(word, "/.*$", "다"), str_remove(word, "/.*$"))) %>% filter(str_count(word) >= 2) %>% arrange(id) ``` --- <svg viewBox="0 0 576 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;position:relative;display:inline-block;top:.1em;fill:#FF7333;"> [ comment ] <path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg> **바이그램으로 토큰화할 때는 형태소 추출 먼저** - 텍스트 원문을 바이그램으로 토큰화하면 원형은 같지만 표현만 다른 단어들이 개별 단어로 취급됨 - ex) '하다', '했다', '하며', '하므로' - 표현이 아니라 의미 중심으로 분석해야 하므로 형태소를 먼저 추출한 다음 바이그램으로 토큰화해야 함 --- ##### (2) 유의어 처리하기 ```r comment_new <- comment_new %>% mutate(word = ifelse(str_detect(word, "감독") & !str_detect(word, "감독상"), "봉준호", word), word = ifelse(word == "오르다", "올리다", word), word = ifelse(str_detect(word, "축하"), "축하", word)) ``` --- ##### (3) 한 댓글이 하나의 행이 되도록 결합하기 ```r comment_new %>% select(word) ``` ``` ## # A tibble: 26,860 x 1 ## word ## <chr> ## 1 우리 ## 2 좋다 ## 3 생기다 ## 4 기쁘다 ## 5 행복한 ## 6 행복 ## 7 축하 ## 8 행복 ## 9 기쁘다 ## 10 기쁘다 ## # … with 26,850 more rows ``` --- ##### (3) 한 댓글이 하나의 행이 되도록 결합하기 ```r line_comment <- comment_new %>% group_by(id) %>% summarise(sentence = paste(word, collapse = " ")) line_comment ``` ``` ## # A tibble: 4,007 x 2 ## id sentence ## * <int> <chr> ## 1 1 우리 좋다 생기다 기쁘다 행복한 행복 축하 행복 기쁘다 ## 2 2 기쁘다 시국 기쁘다 감사하다 축하 진심 ## 3 3 우리나라 봉준호 불다 크다 영감 봉준호 공동각본쓴 한진 작가님 축하 축하 드리다… ## 4 4 봉준호 봉준호 우리나라 대한민국 자랑 세계 어디 우리 한국인 힘내다 삽시 ## 5 5 노벨상 탄느낌이네요 축하 ## 6 6 기생충 받다 박수 치다 감독상 기대다 봉준호 봉준호 ## 7 7 대한민국 영화사 쓰다 계시다 ## 8 8 아카데미상 받다 태극기 휘날리다 광해 명량 전부문 휩쓸어야겠 ## 9 9 다시한번 보이다 영화관 ## 10 10 대한민국 봉준호 대단 한국의 문화 자긍심 가지 ## # … with 3,997 more rows ``` --- ##### (4) 바이그램으로 토큰화하기 ```r bigram_comment <- line_comment %>% unnest_tokens(input = sentence, output = bigram, token = "ngrams", n = 2) bigram_comment ``` ``` ## # A tibble: 23,348 x 2 ## id bigram ## <int> <chr> ## 1 1 우리 좋다 ## 2 1 좋다 생기다 ## 3 1 생기다 기쁘다 ## 4 1 기쁘다 행복한 ## 5 1 행복한 행복 ## 6 1 행복 축하 ## 7 1 축하 행복 ## 8 1 행복 기쁘다 ## 9 2 기쁘다 시국 ## 10 2 시국 기쁘다 ## # … with 23,338 more rows ``` --- #### 연이어 사용된 단어쌍 빈도 구하기 ##### 1. 바이그램 분리하기 ```r # 바이그램 분리하기 bigram_seprated <- bigram_comment %>% separate(bigram, c("word1", "word2"), sep = " ") bigram_seprated ``` ``` ## # A tibble: 23,348 x 3 ## id word1 word2 ## <int> <chr> <chr> ## 1 1 우리 좋다 ## 2 1 좋다 생기다 ## 3 1 생기다 기쁘다 ## 4 1 기쁘다 행복한 ## 5 1 행복한 행복 ## 6 1 행복 축하 ## 7 1 축하 행복 ## 8 1 행복 기쁘다 ## 9 2 기쁘다 시국 ## 10 2 시국 기쁘다 ## # … with 23,338 more rows ``` --- ##### 2. 단어쌍 빈도 구하기 .pull-left[ ```r # 단어쌍 빈도 구하기 pair_bigram <- bigram_seprated %>% count(word1, word2, sort = T) %>% na.omit() pair_bigram ``` ] .pull-right[ ``` ## # A tibble: 19,030 x 3 ## word1 word2 n ## <chr> <chr> <int> ## 1 봉준호 봉준호 155 ## 2 블랙리스트 올리다 64 ## 3 진심 축하 64 ## 4 봉준호 축하 57 ## 5 봉준호 송강호 34 ## 6 영화 만들다 31 ## 7 축하 봉준호 31 ## 8 대단 축하 27 ## 9 봉준호 블랙리스트 27 ## 10 대박 축하 26 ## # … with 19,020 more rows ``` ] <br> <svg viewBox="0 0 352 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M176 80c-52.94 0-96 43.06-96 96 0 8.84 7.16 16 16 16s16-7.16 16-16c0-35.3 28.72-64 64-64 8.84 0 16-7.16 16-16s-7.16-16-16-16zM96.06 459.17c0 3.15.93 6.22 2.68 8.84l24.51 36.84c2.97 4.46 7.97 7.14 13.32 7.14h78.85c5.36 0 10.36-2.68 13.32-7.14l24.51-36.84c1.74-2.62 2.67-5.7 2.68-8.84l.05-43.18H96.02l.04 43.18zM176 0C73.72 0 0 82.97 0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16v.06h48v-.12c-.01-4.77-.72-9.51-2.15-14.07-5.59-17.81-22.82-64.77-62.17-109.67-20.54-23.43-31.52-53.15-31.61-84.14-.2-73.64 59.67-128 127.95-128 70.58 0 128 57.42 128 128 0 30.97-11.24 60.85-31.65 84.14-39.11 44.61-56.42 91.47-62.1 109.46a47.507 47.507 0 0 0-2.22 14.3v.1h48v-.05c9.68-33.37 35.78-73.18 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0z"></path></svg> `na.omit()`: 결측치 행 제거: 한 단어로 된 문장은 바이그램으로 토큰화하면 `NA`가 됨 <br> ex) '축하합니다', '멋집니다' --- ##### 3. 단어쌍 살펴보기 <br10> .pull-left[ ```r # 동시 출현 단어쌍 *pair %>% filter(item1 == "대한민국") ``` ``` ## # A tibble: 1,010 x 3 ## item1 item2 n ## <chr> <chr> <dbl> ## 1 대한민국 봉준호 70 ## 2 대한민국 축하 54 ## 3 대한민국 자랑 44 ## 4 대한민국 영화 30 ## 5 대한민국 기생충 27 ## 6 대한민국 국민 22 ## 7 대한민국 세계 16 ## 8 대한민국 아카데미 16 ## 9 대한민국 위상 15 ## 10 대한민국 좋다 14 ## # … with 1,000 more rows ``` ] .pull-right[ ```r # 바이그램 단어쌍 *pair_bigram %>% filter(word1 == "대한민국") ``` ``` ## # A tibble: 109 x 3 ## word1 word2 n ## <chr> <chr> <int> ## 1 대한민국 국민 21 ## 2 대한민국 자랑 15 ## 3 대한민국 영화 11 ## 4 대한민국 국격 8 ## 5 대한민국 위상 7 ## 6 대한민국 만세 6 ## 7 대한민국 봉준호 5 ## 8 대한민국 문화 4 ## 9 대한민국 영광 4 ## 10 대한민국 기생충 3 ## # … with 99 more rows ``` ] --- #### 엔그램으로 네트워크 그래프 만들기 ```r # 네트워크 그래프 데이터 만들기 graph_bigram <- pair_bigram %>% filter(n >= 8) %>% as_tbl_graph() # 네트워크 그래프 만들기 set.seed(1234) word_network(graph_bigram) ``` <img src="../Image/05/05_4_1.png" width="40%" /> --- <img src="../Image/05/05_4_1.png" width="80%" /> --- ##### 유의어 통일하고 네트워크 그래프 다시 만들기 - `bigram_seprated`의 유의어 통일, 같은 단어 연속 단어쌍 제거 - 단어쌍 빈도 구하고 결측치 제거 ```r # 유의어 처리 bigram_seprated <- bigram_seprated %>% mutate(word1 = ifelse(str_detect(word1, "대단"), "대단", word1), word2 = ifelse(str_detect(word2, "대단"), "대단", word2), word1 = ifelse(str_detect(word1, "자랑"), "자랑", word1), word2 = ifelse(str_detect(word2, "자랑"), "자랑", word2), word1 = ifelse(str_detect(word1, "짝짝짝"), "짝짝짝", word1), word2 = ifelse(str_detect(word2, "짝짝짝"), "짝짝짝", word2)) %>% # 같은 단어 연속 제거 filter(word1 != word2) # 단어쌍 빈도 구하기 pair_bigram <- bigram_seprated %>% count(word1, word2, sort = T) %>% na.omit() ``` --- ```r # 네트워크 그래프 데이터 만들기 set.seed(1234) graph_bigram <- pair_bigram %>% filter(n >= 8) %>% as_tbl_graph(directed = F) %>% mutate(centrality = centrality_degree(), # 중심성 group = as.factor(group_infomap())) # 커뮤니티 ``` --- ```r # 네트워크 그래프 만들기 set.seed(1234) ggraph(graph_bigram, layout = "fr") + # 레이아웃 geom_edge_link(color = "gray50", # 엣지 색깔 alpha = 0.5) + # 엣지 명암 geom_node_point(aes(size = centrality, # 노드 크기 color = group), # 노드 색깔 show.legend = F) + # 범례 삭제 scale_size(range = c(4, 8)) + # 노드 크기 범위 geom_node_text(aes(label = name), # 텍스트 표시 repel = T, # 노드밖 표시 size = 5, # 텍스트 크기 family = "nanumgothic") + # 폰트 theme_graph() # 배경 삭제 ``` --- <img src="../Image/05/05_4_2.png" width="80%" /> --- - 자주 연이어 사용된 단어쌍 중심으로 네트워크 형성 - 단어의 맥락과 의미를 구체적으로 이해할 수 있음 - 개별 단어의 빈도는 낮지만 자주 연이어 사용되고 함께 사용할 때 분명한 의미 지니는 단어쌍 발견 - ex) '이미경-부회장', '조국-가족' <img src="../Image/05/05_4_2.png" width="60%" /> --- ##### 파이 계수, 바이그램 네트워크 그래프의 차이점 <br10> - **파이 계수**를 이용한 네트워크 그래프 - 관련성이 큰 단어쌍 중심으로 네트워크 형성 - 빈도가 낮아도 관련성이 큰 단어 주로 표현 - 관련성이 작은 노드들이 연결되지 않음 - 단어 군집이 명확하게 드러남 but 단어들의 전반적인 관계를 파악하기 어려움 -- <br10> - **바이그램**을 이용한 네트워크 그래프 - 연이어 자주 사용된 단어쌍 중심으로 표현 - 관련성이 큰 동시에 자주 사용된 단어 주로 표현 - 노드가 대부분 연결됨 - 단어 군집이 덜 명확 but 단어들의 전반적인 관계 파악할 수 있음 --- ##### 파이 계수, 바이그램 네트워크 그래프의 차이점 <br10> - **파이 계수**를 이용한 네트워크 그래프 - .orange[**관련성이 큰 단어쌍**] 중심으로 네트워크 형성 - 빈도가 낮아도 관련성이 큰 단어 주로 표현 - 관련성이 작은 노드들이 연결되지 않음 - 단어 군집이 명확하게 드러남 but 단어들의 전반적인 관계를 파악하기 어려움 <br10> - **바이그램**을 이용한 네트워크 그래프 - .orange[**연이어 자주 사용된 단어쌍**] 중심으로 표현 - 관련성이 큰 동시에 자주 사용된 단어 주로 표현 - 노드가 대부분 연결됨 - 단어 군집이 덜 명확 but 단어들의 전반적인 관계 파악할 수 있음 --- ##### 어떤 방법으로 네트워크 그래프를 만드는 게 좋을까 <br10> - 각 방법의 특징 다르므로 분석 목적에 맞게 선택 - 세 가지 방법 모두 사용해 분석 결과 비교하면 텍스트를 다각도로 이해할 수 있음 <br> <img src="../Image/05/05_2_6.png" height="30%" width="35%"/> <img src="../Image/05/05_3_2.png" height="30%" width="30%"/> <img src="../Image/05/05_4_2.png" height="30%" width="30%"/> --- - **동시 출현 빈도**: 자주 사용된 단어 중심으로 단어들의 관계 표현 <br10> <img src="../Image/05/05_2_6.png" width="75%" /> --- - **파이 계수** - 관련성이 큰 단어쌍 중심으로 표현 - 단어 군집을 잘 드러내고 싶을 때 <img src="../Image/05/05_3_2.png" width="70%" /> --- - **엔그램** - 연이어 사용될 때 의미를 지니는 단어쌍 중심으로 표현 - 단어들이 전반적으로 어떤 관계를 형성하고 있는지 표현할 때 <img src="../Image/05/05_4_2.png" width="70%" /> --- class: title1 정리하기 --- ### 정리하기 ##### 1. 동시 출현 단어 분석 - Co-occurrence analysis ```r # 품사 기준 토큰화 comment_pos <- news_comment %>% unnest_tokens(input = reply, output = word, token = SimplePos22, drop = F) # 명사, 동사, 형용사 추출 comment <- comment_pos %>% separate_rows(word, sep = "[+]") %>% filter(str_detect(word, "/n|/pv|/pa")) %>% mutate(word = ifelse(str_detect(word, "/pv|/pa"), str_replace(word, "/.*$", "다"), str_remove(word, "/.*$"))) %>% filter(str_count(word) >= 2) %>% arrange(id) ``` --- ### 정리하기 ##### 1. 동시 출현 단어 분석 - Co-occurrence analysis ```r # 단어 동시 출현 빈도 구하기 pair <- comment %>% pairwise_count(item = word, feature = id, sort = T) ``` --- ### 정리하기 ##### 2. 단어 간 상관 분석 - Phi coefficient ```r # 파이 계수 구하기 word_cors <- comment %>% add_count(word) %>% filter(n >= 20) %>% pairwise_cor(item = word, feature = id, sort = T) ``` --- ### 정리하기 ##### 3. 연이어 사용된 단어쌍 분석 - n-gram ```r # 텍스트를 한 행으로 구성 line_comment <- comment %>% group_by(id) %>% summarise(sentence = paste(word, collapse = " ")) # 바이그램 토큰화 bigram_comment <- line_comment %>% unnest_tokens(input = sentence, output = bigram, token = "ngrams", n = 2) # 바이그램 분리 bigram_seprated <- bigram_comment %>% separate(bigram, c("word1", "word2"), sep = " ") ``` --- ### 정리하기 ##### 3. 연이어 사용된 단어쌍 분석 - n-gram ```r # 단어쌍 빈도 구하기 pair_bigram <- bigram_seprated %>% count(word1, word2, sort = T) %>% na.omit() ``` --- ### 정리하기 ##### 4. 네트워크 그래프 만들기 ```r # 네트워크 그래프 데이터 만들기 set.seed(1234) graph_comment <- pair_bigram %>% filter(n >= 8) %>% as_tbl_graph(directed = F) %>% mutate(centrality = centrality_degree(), group = as.factor(group_infomap())) # 네트워크 그래프 만들기 set.seed(1234) ggraph(graph_comment) + geom_edge_link() + geom_node_point(aes(size = centrality, color = group)) + geom_node_text(aes(label = name)) ``` --- ### 분석 도전 **`"news_comment_BTS.csv"`에는 2020년 9월 21일 방탄소년단이 '빌보드 핫 100 차트' 1위에 오른 소식을 다룬 기사에 달린 댓글이 들어있습니다. `"news_comment_BTS.csv"`를 이용해 문제를 해결해 보세요.** - Q1. `"news_comment_BTS.csv"`를 불러온 다음 행 번호를 나타낸 변수를 추가하고 분석에 적합하게<br> 전처리하세요. - Q2. 댓글에서 명사, 동사, 형용사를 추출하고 '/로 시작하는 모든 문자'를 '다'로 바꾸세요. - Q3. 다음 코드를 이용해 유의어를 통일한 다음 한 댓글이 하나의 행이 되도록 단어를 결합하세요. ```r # 유의어 통일하기 comment <- comment %>% mutate(word = case_when(str_detect(word, "축하") ~ "축하", str_detect(word, "방탄") ~ "자랑", str_detect(word, "대단") ~ "대단", str_detect(word, "자랑") ~ "자랑", T ~ word)) ``` --- ### 분석 도전 **`"news_comment_BTS.csv"`에는 2020년 9월 21일 방탄소년단이 '빌보드 핫 100 차트' 1위에 오른 소식을 다룬 기사에 달린 댓글이 들어있습니다. `"news_comment_BTS.csv"`를 이용해 문제를 해결해 보세요.** - Q4. 댓글을 바이그램으로 토큰화한 다음 바이그램 단어쌍을 분리하세요. <br10> - Q5. 단어쌍 빈도를 구한 다음 네트워크 그래프 데이터를 만드세요. - 난수를 고정한 다음 네트워크 그래프 데이터를 만드세요. - 빈도가 3 이상인 단어쌍만 사용하세요. - 연결 중심성과 커뮤니티를 나타낸 변수를 추가하세요. <br10> - Q6. 바이그램을 이용해 네트워크 그래프를 만드세요. - 난수를 고정한 다음 네트워크 그래프를 만드세요. - 레이아웃을 `"fr"`로 설정하세요. - 연결 중심성에 따라 노드 크기를 정하고, 커뮤니티별로 노드 색깔이 다르게 설정하세요. - 노드의 범례를 삭제하세요. - 텍스트가 노드 밖에 표시되게 설정하고, 텍스트의 크기를 5로 설정하세요. --- Q1. `"news_comment_BTS.csv"`를 불러온 다음 행 번호를 나타낸 변수를 추가하고 분석에 적합하게<br> 전처리하세요. ```r library(readr) library(dplyr) raw_news_comment <- read_csv("news_comment_BTS.csv") glimpse(raw_news_comment) ``` ``` ## Rows: 1,200 ## Columns: 5 ## $ reg_time <dttm> 2020-09-01 22:58:09, 2020-09-01 09:56:46… ## $ reply <chr> "국보소년단<U+0001F49C>", "아줌마가 들어도 좋더라", "팩트체… ## $ press <chr> "한국경제", "한국경제", "한국경제", "한국경제", "한국경제", "… ## $ title <chr> "[속보]BTS '다이너마이트', 한국 가수 최초로 빌보드 싱글 1위", … ## $ url <chr> "https://news.naver.com/main/read.nhn?mod… ``` --- <br-back-20> ```r library(stringr) library(textclean) news_comment <- raw_news_comment %>% select(reply) %>% mutate(id = row_number(), reply = str_replace_all(reply, "[^가-힣]", " "), reply = str_squish(reply)) news_comment %>% select(id, reply) ``` ``` ## # A tibble: 1,200 x 2 ## id reply ## <int> <chr> ## 1 1 국보소년단 ## 2 2 아줌마가 들어도 좋더라 ## 3 3 팩트체크 현재 빌보드 위 방탄소년단 위 위 위 위 위 위 위 위 위… ## 4 4 방탄소년단이 한국사람이라 너무 자랑스러워요 우리오래오래 함께하자… ## 5 5 대단한 월드 클래스는 다르네 좋은 소식 응원해요… ## 6 6 정국오빠 생일과 더불어 빌보드 위기사라니 축제구나… ## 7 7 정말 축하하고 응원하지만 집에서 여러 계정으로 스트리밍 돌리고 사재기하고… ## 8 8 기자는 자고 일어났지만 팬들은 못자고 발표 기다림… ## 9 9 자랑스럽다 축하합니다 ## 10 10 늘 응원하고 사랑합니다 ## # … with 1,190 more rows ``` --- Q2. 댓글에서 명사, 동사, 형용사를 추출하고 '/로 시작하는 모든 문자'를 '다'로 바꾸세요. ```r # 품사 기준 토큰화 library(tidytext) library(KoNLP) comment_pos <- news_comment %>% unnest_tokens(input = reply, output = word, token = SimplePos22, drop = F) ``` --- ```r # 한 행이 한 품사를 구성하도록 분리 library(tidyr) comment_pos <- comment_pos %>% separate_rows(word, sep = "[+]") comment_pos %>% select(word, reply) ``` ``` ## # A tibble: 20,851 x 2 ## word reply ## <chr> <chr> ## 1 국보소년/nc 국보소년단 ## 2 단/ma 국보소년단 ## 3 아줌마/nc 아줌마가 들어도 좋더라 ## 4 가/jc 아줌마가 들어도 좋더라 ## 5 들/pv 아줌마가 들어도 좋더라 ## 6 어도/ec 아줌마가 들어도 좋더라 ## 7 좋/pa 아줌마가 들어도 좋더라 ## 8 더/ep 아줌마가 들어도 좋더라 ## 9 어/ec 아줌마가 들어도 좋더라 ## 10 라/nc 아줌마가 들어도 좋더라 ## # … with 20,841 more rows ``` --- ```r # 명사, 동사, 형용사 추출 comment <- comment_pos %>% separate_rows(word, sep = "[+]") %>% filter(str_detect(word, "/n|/pv|/pa")) %>% mutate(word = ifelse(str_detect(word, "/pv|/pa"), str_replace(word, "/.*$", "다"), str_remove(word, "/.*$"))) %>% filter(str_count(word) >= 2) %>% arrange(id) ``` --- ```r comment %>% select(word, reply) ``` ``` ## # A tibble: 7,539 x 2 ## word reply ## <chr> <chr> ## 1 국보소년 국보소년단 ## 2 아줌마 아줌마가 들어도 좋더라 ## 3 들다 아줌마가 들어도 좋더라 ## 4 좋다 아줌마가 들어도 좋더라 ## 5 팩트체크 팩트체크 현재 빌보드 위 방탄소년단 위 위 위 위 위 위 위 위 … ## 6 빌보드 팩트체크 현재 빌보드 위 방탄소년단 위 위 위 위 위 위 위 위 … ## 7 방탄소년단… 팩트체크 현재 빌보드 위 방탄소년단 위 위 위 위 위 위 위 위 … ## 8 방탄소년단… 방탄소년단이 한국사람이라 너무 자랑스러워요 우리오래오래 함께하자… ## 9 한국사람 방탄소년단이 한국사람이라 너무 자랑스러워요 우리오래오래 함께하자… ## 10 자랑 방탄소년단이 한국사람이라 너무 자랑스러워요 우리오래오래 함께하자… ## # … with 7,529 more rows ``` --- Q3. 다음 코드를 이용해 유의어를 통일한 다음 한 댓글이 하나의 행이 되도록 단어를 결합하세요. ```r # 유의어 통일하기 comment <- comment %>% mutate(word = case_when(str_detect(word, "축하") ~ "축하", str_detect(word, "방탄") ~ "자랑", str_detect(word, "대단") ~ "대단", str_detect(word, "자랑") ~ "자랑", T ~ word)) ``` --- ```r # 단어를 댓글별 한 행으로 결합 line_comment <- comment %>% group_by(id) %>% summarise(sentence = paste(word, collapse = " ")) line_comment ``` ``` ## # A tibble: 1,155 x 2 ## id sentence ## * <int> <chr> ## 1 1 국보소년 ## 2 2 아줌마 들다 좋다 ## 3 3 팩트체크 빌보드 자랑 ## 4 4 자랑 한국사람 자랑 우리오래오래 함께하다 ## 5 5 대단 월드 클래스 다르다 좋다 소식 응원해 ## 6 6 정국오빠 생일 더불다 빌보드 위기사 축제구 ## 7 7 축하 응원하지 계정 스트리밍 돌리다 사재기 팬덤 테러하 개념보고 놀라다… ## 8 8 기자 자다 일어나다 패다 못자 발표 ## 9 9 자랑 축하 ## 10 10 응원 사랑합 ## # … with 1,145 more rows ``` --- Q4. 댓글을 바이그램으로 토큰화한 다음 바이그램 단어쌍을 분리하세요. ```r # 바이그램 토큰화 bigram_comment <- line_comment %>% unnest_tokens(input = sentence, output = bigram, token = "ngrams", n = 2) bigram_comment ``` ``` ## # A tibble: 6,541 x 2 ## id bigram ## <int> <chr> ## 1 1 <NA> ## 2 2 아줌마 들다 ## 3 2 들다 좋다 ## 4 3 팩트체크 빌보드 ## 5 3 빌보드 자랑 ## 6 4 자랑 한국사람 ## 7 4 한국사람 자랑 ## 8 4 자랑 우리오래오래 ## 9 4 우리오래오래 함께하다 ## 10 5 대단 월드 ## # … with 6,531 more rows ``` --- ```r # 바이그램 단어쌍 분리 bigram_seprated <- bigram_comment %>% separate(bigram, c("word1", "word2"), sep = " ") bigram_seprated ``` ``` ## # A tibble: 6,541 x 3 ## id word1 word2 ## <int> <chr> <chr> ## 1 1 <NA> <NA> ## 2 2 아줌마 들다 ## 3 2 들다 좋다 ## 4 3 팩트체크 빌보드 ## 5 3 빌보드 자랑 ## 6 4 자랑 한국사람 ## 7 4 한국사람 자랑 ## 8 4 자랑 우리오래오래 ## 9 4 우리오래오래 함께하다 ## 10 5 대단 월드 ## # … with 6,531 more rows ``` --- - Q5. 단어쌍 빈도를 구한 다음 네트워크 그래프 데이터를 만드세요. - 난수를 고정한 다음 네트워크 그래프 데이터를 만드세요. - 빈도가 3 이상인 단어쌍만 사용하세요. - 연결 중심성과 커뮤니티를 나타낸 변수를 추가하세요. <br10> .pull-left[ ```r # 단어쌍 빈도 구하기 pair_bigram <- bigram_seprated %>% count(word1, word2, sort = T) %>% na.omit() pair_bigram ``` ] .pull-right[ ``` ## # A tibble: 5,455 x 3 ## word1 word2 n ## <chr> <chr> <int> ## 1 축하 하다 43 ## 2 자랑 축하 40 ## 3 자랑 자랑 38 ## 4 축하 자랑 35 ## 5 대단 자랑 24 ## 6 진짜 자랑 24 ## 7 진짜 대단 23 ## 8 자랑 진짜 17 ## 9 빌보드 축하 14 ## 10 군대 면제 13 ## # … with 5,445 more rows ``` ] --- ```r # 네트워크 그래프 데이터 만들기 library(tidygraph) set.seed(1234) graph_bigram <- pair_bigram %>% filter(n >= 3) %>% as_tbl_graph(directed = F) %>% mutate(centrality = centrality_degree(), group = as.factor(group_infomap())) graph_bigram ``` ``` ## # A tbl_graph: 90 nodes and 130 edges ## # ## # An undirected multigraph with 6 components ## # ## # Node Data: 90 x 3 (active) ## name centrality group ## <chr> <dbl> <fct> ## 1 축하 18 1 ## 2 자랑 45 1 ## 3 대단 9 1 ## 4 진짜 12 1 ## 5 빌보드 16 2 ## 6 군대 3 4 ## # … with 84 more rows ## # ## # Edge Data: 130 x 3 ## from to n ## <int> <int> <int> ## 1 1 61 43 ## 2 1 2 40 ## 3 2 2 38 ## # … with 127 more rows ``` --- - Q6. 바이그램을 이용해 네트워크 그래프를 만드세요. - 난수를 고정한 다음 네트워크 그래프를 만드세요. - 레이아웃을 `"fr"`로 설정하세요. - 연결 중심성에 따라 노드 크기를 정하고, 커뮤니티별로 노드 색깔이 다르게 설정하세요. - 노드의 범례를 삭제하세요. - 텍스트가 노드 밖에 표시되게 설정하고, 텍스트의 크기를 5로 설정하세요. ```r library(ggraph) set.seed(1234) ggraph(graph_bigram, layout = "fr") + geom_edge_link() + geom_node_point(aes(size = centrality, color = group), show.legend = F) + geom_node_text(aes(label = name), repel = T, size = 5) + theme_graph() ``` --- <img src="https://raw.githubusercontent.com/youngwoos/Doit_textmining/main/Quiz/Quiz_Part05_files/figure-markdown_github/unnamed-chunk-15-1.png" width="90%" /> --- ```r # 그래프 꾸미기 library(showtext) font_add_google(name = "Nanum Gothic", family = "nanumgothic") set.seed(1234) ggraph(graph_bigram, layout = "fr") + # 레이아웃 geom_edge_link(color = "gray50", # 엣지 색깔 alpha = 0.5) + # 엣지 명암 geom_node_point(aes(size = centrality, # 노드 크기 color = group), # 노드 색깔 show.legend = F) + # 범례 삭제 scale_size(range = c(4, 8)) + # 노드 크기 범위 geom_node_text(aes(label = name), # 텍스트 표시 repel = T, # 노드밖 표시 size = 5, # 텍스트 크기 family = "nanumgothic") + # 폰트 theme_graph() # 배경 삭제 ``` --- <img src="https://raw.githubusercontent.com/youngwoos/Doit_textmining/main/Quiz/Quiz_Part05_files/figure-markdown_github/unnamed-chunk-17-1.png" width="90%" /> --- class: title0 끝