Semantic Network Analysis

Text Mining

Description for Semantic Network Analysis

Yeongeun Jeon , Jung In Seo
2023-11-30

의미망 분석 (Semantic Network Analysis)



패키지 설치

pacman::p_load("readr",
               "dplyr", "tidyr",
               "stringr",
               "tidytext",
               "textclean",
               "KoNLP",
               "widyr",
               "ggplot2",
               "tidygraph", "ggraph",
               "showtext")

1. 동시 출현 단어 분석


1-1. 데이터 불러오기

# 데이터 불러오기
raw_news_comment <- read_csv(".../news_comment_parasite.csv")

raw_news_comment
# A tibble: 4,150 × 5
   reg_time            reply                         press title url  
   <dttm>              <chr>                         <chr> <chr> <chr>
 1 2020-02-10 16:59:02 "정말 우리 집에 좋은 일이 생… MBC   '기…  http…
 2 2020-02-10 13:32:24 "와 너무 기쁘다! 이 시국에 …  SBS   [영…  http…
 3 2020-02-10 12:30:09 "우리나라의 영화감독분들 그…  한겨… ‘기…  http…
 4 2020-02-10 13:08:22 "봉준호 감독과 우리나라 대한… 한겨… ‘기…  http…
 5 2020-02-10 16:25:41 "노벨상 탄느낌이네요\n축하축… 한겨… ‘기…  http…
 6 2020-02-10 12:31:45 "기생충 상 받을때 박수 쳤어…  한겨… ‘기…  http…
 7 2020-02-10 12:31:33 "대한민국 영화사를 새로 쓰고… 한겨… ‘기…  http…
 8 2020-02-11 09:20:52 "저런게 아카데미상 받으면  '… 한겨… ‘기…  http…
 9 2020-02-10 20:53:27 "다시한번 보여주세요 영화관…  한겨… ‘기…  http…
10 2020-02-10 20:22:41 "대한민국 BTS와함께  봉준호…  한겨… ‘기…  http…
# ℹ 4,140 more rows

1-2. 전처리

# 전처리
news_comment <- raw_news_comment %>%
  select(reply) %>%                                       # 변수 reply 선택 -> 댓글만 추출
  mutate(reply = str_replace_all(reply, "[^가-힣]", " "), # 한글을 제외한 모든 문자는 공백으로 변경
         reply = str_squish(reply),                       # 연속된 공백 제거
         id = row_number())                               # 변수 id 추가 -> 행 번호(row_number)를 값으로 입력

news_comment
# A tibble: 4,150 × 2
   reply                                                            id
   <chr>                                                         <int>
 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일인…     1
 2 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다 축하…     2
 3 우리나라의 영화감독분들 그리고 앞으로 그 꿈을 그리는 분들에…      3
 4 봉준호 감독과 우리나라 대한민국 모두 자랑스럽다 세계 어디를 …     4
 5 노벨상 탄느낌이네요 축하축하 합니다                               5
 6 기생충 상 받을때 박수 쳤어요 감독상도 기대해요 봉준호 감독 …      6
 7 대한민국 영화사를 새로 쓰고 계시네요                              7
 8 저런게 아카데미상 받으면 태극기 휘날리며 광해 명량 은 전부문…     8
 9 다시한번 보여주세요 영화관에서 보고싶은디                         9
10 대한민국 와함께 봉준호감독님까지 대단하고 한국의 문화에 자긍…    10
# ℹ 4,140 more rows

1-3. 토큰화

Caution! 명사의 의미는 문장에 함께 사용한 형용사와 동사에 따라 달라진다. 동시 출현 단어 분석은 단어가 사용된 맥락을 살펴보는 게 중요하므로 명사뿐만 아니라 형용사와 동사도 함께 추출해야 한다.


1-3-1. 형태소 분석기를 이용한 품사 구분

출처 : https://github.com/haven-jeon/KoNLP/blob/master/etcs/KoNLP-API.md


# 토큰화
comment_pos <- news_comment %>%                # 전처리를 수행한 결과가 저장되어 있는 객체 in 1-2
  unnest_tokens(input = reply,                 # 토큰화를 수행할 텍스트가 포함된 변수명
                output = word,                 # 출력 변수명
                token = SimplePos22,           # 22개의 품사 기준으로 토큰화
                drop = F)                      # 원문 제거 X

comment_pos %>% 
  select(word, reply)                          # 변수 word와 reply 선택
# A tibble: 39,956 × 2
   word          reply                                               
   <chr>         <chr>                                               
 1 정말/ma       정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
 2 우리/np       정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
 3 집/nc+에/jc   정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
 4 좋/pa+은/et   정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
 5 일/nc+이/jc   정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
 6 생기/pv+어/ec 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
 7 기쁘/pa+고/ec 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
 8 행복한/nc     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
 9 것/nb+처럼/jc 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
10 나/np+의/jc   정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 …
# ℹ 39,946 more rows

Result! 변수 word를 살펴보면 단어 뒤에 "/nc""/pv" 같이 품사를 나타내는 태그가 붙여져 있다. 태그를 이용하면 원하는 품사의 단어를 추출할 수 있다.


1-3-2. 품사 분리

# 한 행이 한 품사로 구성되게 품사를 분리
comment_pos <- comment_pos %>%                 # 품사를 구분한 결과가 저장되어 있는 객체 in 1-3-1
  separate_rows(word, sep = "[+]")             # 변수 word를 "+" 기준으로 행 분리 

comment_pos %>% 
  select(word, reply)                          # 변수 word와 reply 선택
# A tibble: 70,553 × 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   정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
# ℹ 70,543 more rows

Result! 위의 코드에서는 sep = "[+]"를 입력함으로써 "+"가 등장할 때마다 행을 분리하여 한 행에 한 품사만 존재하도록 한다.


1-3-3. 품사 추출

① 명사 추출

# 1. 명사 추출
noun <- comment_pos %>%                        # 품사를 분리한 결과가 저장되어 있는 객체 in 1-3-2
  filter(str_detect(word, "/n")) %>%           # 변수 word에서 "/n"를 포함하는 행만 추출
  mutate(word = str_remove(word, "/.*$"))      # "/"로 시작하는 모든 문자 제거 -> 태그를 제거하기 위해 

noun %>%
  select(word, reply)                          # 변수 word와 reply 선택
# A tibble: 27,457 × 2
   word   reply                                                      
   <chr>  <chr>                                                      
 1 우리   정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
 2 집     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
 3 일     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
 4 행복한 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
 5 것     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
 6 나     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
 7 일     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
 8 양     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
 9 행복   정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
10 행복   정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의 일…
# ℹ 27,447 more rows
noun %>%
  count(word,                                  # 단어별 빈도 계산
        sort = T)                              # 빈도를 내림차순으로 정렬
# A tibble: 8,069 × 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
# ℹ 8,059 more rows

Result! 함수 count()를 이용하여 댓글에 어떤 명사가 많이 사용되었는지 알 수 있다. 기생충의 아카데미상 수상 소식을 다룬 기사에 달린 댓글에서는 “영화”가 463번으로 가장 많이 사용되었으며, 그 다음으로 “기생충”과 “봉준호”가 많이 사용되었다.


② 동사와 형용사 추출

# 2. 동사와 형용사 추출
pvpa <- comment_pos %>%                           # 품사를 분리한 결과가 저장되어 있는 객체 in 1-3-2
  filter(str_detect(word, "/pv|/pa")) %>%         # 변수 word에서 "/pv"와 "/pa"를 포함하는 행만 추출
  mutate(word = str_replace(word, "/.*$", "다"))  # "/"로 시작하는 모든 문자를 "다"로 변경 -> 태그를 "다"로 변경

pvpa %>%
  select(word, reply)                             # 변수 word와 reply 선택
# A tibble: 5,317 × 2
   word       reply                                                   
   <chr>      <chr>                                                   
 1 좋다       정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 2 생기다     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 3 기쁘다     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 4 축하드리다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 5 기쁘다     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 6 기쁘다     와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다…
 7 기쁘다     와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다…
 8 축하드리다 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다…
 9 불다       우리나라의 영화감독분들 그리고 앞으로 그 꿈을 그리는 분…
10 크다       우리나라의 영화감독분들 그리고 앞으로 그 꿈을 그리는 분…
# ℹ 5,307 more rows
pvpa %>%
  count(word,                                     # 단어별 빈도 계산
        sort = T)                                 # 빈도를 내림차순으로 정렬
# A tibble: 697 × 2
   word           n
   <chr>      <int>
 1 축하드리다   298
 2 받다         215
 3 좋다         170
 4 아니다       156
 5 되다         144
 6 없다         136
 7 같다         130
 8 멋지다       121
 9 만들다       119
10 알다          94
# ℹ 687 more rows

Result! “축하드리다”가 가장 많이 사용되었으며, 그 다음으로 “받다”와 “좋다”가 많이 사용되었다.


# 3. 추출한 데이터셋 결합 
comment <- bind_rows(noun, pvpa) %>%              # bind_rows : 행 기준으로 "noun"과 "pvpa" 결합
  filter(str_count(word) >= 2) %>%                # 두 글자 이상 단어만 추출
  arrange(id)                                     # 변수 id를 기준으로 오름차순 정렬

comment %>%                                       # 명사, 동사, 그리고 형용사인 단어만 포함된 데이터셋
  select(word, reply)                             # 변수 word와 reply 선택
# A tibble: 26,860 × 2
   word       reply                                                   
   <chr>      <chr>                                                   
 1 우리       정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 2 행복한     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 3 행복       정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 4 행복       정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 5 좋다       정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 6 생기다     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 7 기쁘다     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 8 축하드리다 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
 9 기쁘다     정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나의…
10 시국       와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하다…
# ℹ 26,850 more rows

# ※ 명사, 동사, 그리고 형용사를 한 번에 추출 
comment_new <- comment_pos %>%                             # 품사를 분리한 결과가 저장되어 있는 객체 in 1-3-2
  filter(str_detect(word, "/n|/pv|/pa")) %>%               # 변수 word에서 "/n" (명사), "/pv" (동사)와 "/pa" (형용사)를 포함하는 행만 추출
  mutate(word = ifelse(str_detect(word, "/pv|/pa"),          
                       str_replace(word, "/.*$", "다"),    # "/pv" (동사)와 "/pa" (형용사)인 경우 "/"로 시작하는 모든 문자를 "다"로 변경 -> 태그를 "다"로 변경
                       str_remove(word, "/.*$"))) %>%      # "/n" (명사)인 경우 "/"로 시작하는 모든 문자 제거 -> 태그 제거
  filter(str_count(word) >= 2) %>%                         # 두 글자 이상의 단어만 추출
  arrange(id)                                              # 변수 id를 기준으로 오름차순 정렬

comment_new
# A tibble: 26,860 × 3
   reply                                                     id word  
   <chr>                                                  <int> <chr> 
 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 우리  
 2 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 좋다  
 3 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 생기다
 4 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘다
 5 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복한
 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복  
 7 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 축하… 
 8 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복  
 9 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘다
10 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하…     2 기쁘다
# ℹ 26,850 more rows

Caution! 이 방법을 사용하면 결과는 같지만 단어 정렬 순서가 달라진다. 품사별로 따로 처리해 결합하는 방법을 사용하면 변수 id별로 위쪽에는 명사, 아래쪽에는 동사와 형용사를 나열한다. 반면, 이 방법은 문장에 사용된 순서에 따라 단어를 나열한다.


1-4. 단어 동시 출현 빈도 계산

pair <- comment %>%                    # 명사, 동사, 그리고 형용사인 단어만 추출한 결과가 저장되어 있는 객체 in 1-3-3
  pairwise_count(item = word,          # 단어가 입력되어 있는 변수명                    
                 feature = id,         # 텍스트 구분 기준
                 sort = T)             # 빈도를 내림차순으로 정렬
pair
# A tibble: 245,920 × 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
# ℹ 245,910 more rows

Result! 출력 결과를 보면 두 단어가 함께 몇 번씩 사용되었는지 알 수 있다. 예를 들어, 첫 번째 행을 보면 “영화”와 “기생충”이 111번 함께 사용되었고, 전체 댓글에서 가장 많이 사용된 단어쌍임을 알 수 있다.
Caution! 함수 pairwise_count()는 한 단어를 기준으로 함께 사용한 모든 단어의 빈도를 계산하기 때문에 출력 결과에 “영화-기생충”, “기생충-영화”와 같이 동일한 단어쌍이 2번 등장한다.

# 특정 단어와 자주 함께 사용된 단어 살펴보기
pair %>% 
  filter(item1 == "영화")            # "영화"와 함께 자주 사용한 단어 추출
# A tibble: 2,313 × 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
# ℹ 2,303 more rows
pair %>% 
  filter(item1 == "봉준호")          # "봉준호"와 함께 자주 사용한 단어 추출
# A tibble: 1,579 × 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
# ℹ 1,569 more rows

Result! 함수 filter()를 이용하면 특정 단어와 자주 함께 사용된 단어가 무엇인지 알 수 있다. “영화”는 “기생충”과 가장 많이 함께 사용되었으며, “봉준호”는 “감독”과 가장 많이 함께 사용되었다.


2. 동시 출현 네트워크


2-1. 네트워크 그래프 데이터로 변환

graph_comment <- pair %>%            # 동시 출현 빈도가 저장되어 있는 객체 in 1-4
  filter(n >= 25) %>%                # 25회 이상 함께 사용된 단어쌍만 추출 -> 네트워크의 복잡성 방지
  as_tbl_graph()                     # 네트워크 그래프 데이터로 변환

graph_comment
# A tbl_graph: 30 nodes and 108 edges
#
# A directed simple graph with 2 components
#
# A tibble: 30 × 1
  name  
  <chr> 
1 영화  
2 기생충
3 감독  
4 봉준호
5 감독님
6 만들다
# ℹ 24 more rows
#
# A tibble: 108 × 3
   from    to     n
  <int> <int> <dbl>
1     1     2   111
2     2     1   111
3     3     4    86
# ℹ 105 more rows

Result! 단어를 나타내는 노드(Node, 꼭짓점) 30개와 단어를 연결하는 엣지(Edge, 선) 108개로 구성되어 있음을 알 수 있다 (A tbl_graph: 30 nodes and 108 edges). 그래프를 만들 때 이 값들을 활용한다.
Caution! 분석 결과를 확인하기 전에는 빈도가 몇 이상인 단어를 추출하는 게 적당한지 알 수 없다. 빈도를 조절하며 그래프를 여러 번 만들어 보면서 적당한 값을 찾아야 한다.


2-2. 네트워크 그래프

ggraph(graph_comment) +              # 네트워크 그래프 데이터 in 2-1
  geom_edge_link() +                 # 단어들을 엣지(선)로 연결
  geom_node_point() +                # 단어를 노드로 구성
  geom_node_text(aes(label = name))  # 노드에 단어 표시

Caution! 네트워크 그래프는 큰 이미지로 봐야 전체 구성을 파악할 수 있다. Plots 창의 Zoom 아이콘을 클릭해 큰 화면에서 그래프를 살펴볼 수 있다. 또는 이미지 출력 창을 별도로 열면 큰 화면에서 그래프를 살펴볼 수 있다. 윈도우에서는 함수 windows(), macOS에서는 함수 x11()을 실행해 창을 크게 만든 다음 그래프를 출력할 수도 있다.


# 그래프 다듬기 
# 나눔바른 고딕 폰트 불러오기
font_add_google(name = "Nanum Gothic",      # 구글 폰트에서 사용하고자 하는 폰트 이름 
                family = "nanumgothic")     # R에서 사용할 폰트 이름 -> 사용자 지정
showtext_auto()

Caution! 함수의 옵션을 이용해 엣지와 노드의 색깔, 크기, 텍스트 위치 등을 수정할 수 있다. 함수 ggraph()의 옵션 layout은 네트워크의 형태를 정하는 기능을 한다. 하지만, 옵션 layout을 지정하면 난수를 이용해 그래프를 만들기 때문에 set.seed로 난수를 고정하여 항상 같은 모양의 그래프를 만들도록 해야 한다. 옵션 layout에 따라 네트워크가 어떻게 달라지는지 궁금하다면 여기를 참고한다.
게다가, 노드 텍스트의 폰트는 함수 geom_node_text()의 옵션 family를 이용해 별도로 설정해야 한다. 함수 theme()으로 그래프 전체의 폰트를 바꾸더라도 노드 텍스트에는 적용되지 않는다.

set.seed(1234)                              # Seed 고정 -> 항상 동일한 모양의 그래프 출력
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()                             # 배경 삭제

# 동일한 코드 반복을 방지하기 위해 네트워크 그래프를 만드는 함수 생성
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()                             # 배경 삭제
}
# 예시
set.seed(1234)                                # Seed 고정 -> 항상 동일한 모양의 그래프 출력
word_network(graph_comment)

Result! 함수 as_tbl_graph()로 만든 네트워크 그래프 데이터를 함수 word_network()에 적용하면 네트워크 그래프를 출력한다.


2-3. 유의어 처리

# 1. 유의어 처리
comment <- comment %>%                                  # 명사, 동사, 그리고 형용사인 단어만 추출한 결과가 저장되어 있는 객체 in 1-3-3
  mutate(# "감독상"을 제외하고 "감독"을 포함하는 모든 단어는 "봉준호"로 통일
         word = ifelse(str_detect(word, "감독") & !str_detect(word, "감독상"),   
                       "봉준호", word), 
         # "오르다"는 "올리다"로 변경
         word = ifelse(word == "오르다", "올리다", word),
         # "축하"를 포함하는 모든 단어는 "축하"로 통일
         word = ifelse(str_detect(word, "축하"), "축하", word))

comment
# A tibble: 26,860 × 3
   reply                                                     id word  
   <chr>                                                  <int> <chr> 
 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 우리  
 2 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복한
 3 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복  
 4 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복  
 5 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 좋다  
 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 생기다
 7 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘다
 8 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 축하  
 9 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘다
10 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하…     2 시국  
# ℹ 26,850 more rows

# 2. 단어 동시 출현 빈도 계산
pair <- comment %>%                    # 유의어를 처리한 객체 
  pairwise_count(item = word,          # 단어가 입력되어 있는 변수명                    
                 feature = id,         # 텍스트 구분 기준
                 sort = T)             # 빈도를 내림차순으로 정렬
pair
# A tibble: 239,504 × 3
   item1      item2          n
   <chr>      <chr>      <dbl>
 1 봉준호     축하         198
 2 축하       봉준호       198
 3 영화       봉준호       119
 4 봉준호     영화         119
 5 블랙리스트 봉준호       114
 6 봉준호     블랙리스트   114
 7 영화       기생충       111
 8 기생충     영화         111
 9 기생충     봉준호        94
10 봉준호     기생충        94
# ℹ 239,494 more rows
# A tbl_graph: 36 nodes and 152 edges # # A directed simple graph with 1 component # # A tibble: 36 × 1 name <chr> 1 봉준호 2 축하 3 영화 4 블랙리스트 5 기생충 6 대한민국 # ℹ 30 more rows # # A tibble: 152 × 3 from to n <int> <int> <dbl> 1 1 2 198 2 2 1 198 3 3 1 119 # ℹ 149 more rows
# 4. 네트워크 그래프 만들기
set.seed(1234)                        # Seed 고정 -> 항상 동일한 모양의 그래프 출력
word_network(graph_comment)

Result! 네트워크 구조가 유의어를 처리하기 전보다 간결해졌음을 알 수 있다.


2-4. 연결 중심성과 커뮤니티



2-4-1. 연결 중심성과 커뮤니티 변수 추가

set.seed(1234)                                    # Seed 고정 -> For group_infomap()
graph_comment <- pair %>%                         # 단어 동시 출현 빈도의 결과가 저장되어 있는 객체 in 2-3
  filter(n >= 25) %>%                             # 25회 이상 함께 사용된 단어만 추출 -> 네트워크의 복잡성 방지
  as_tbl_graph(directed = F) %>%                  # 네트워크 그래프 데이터로 변환 / directed = F : 그래프에 방향 표시 X
  mutate(centrality = centrality_degree(),        # centrality_degree : 연결 중심성 계산
         group = as.factor(group_infomap()))      # group_infomap : 커뮤니티를 찾음 / as.factor : Factor로 변환 -> 그룹에 따라 다른 색으로 표현하기 위해

graph_comment
# A tbl_graph: 36 nodes and 152 edges
#
# An undirected multigraph with 1 component
#
# A tibble: 36 × 3
  name       centrality group
  <chr>           <dbl> <fct>
1 봉준호             62 4    
2 축하               34 2    
3 영화               26 3    
4 블랙리스트          6 6    
5 기생충             26 1    
6 대한민국           10 3    
# ℹ 30 more rows
#
# A tibble: 152 × 3
   from    to     n
  <int> <int> <dbl>
1     1     2   198
2     1     2   198
3     1     3   119
# ℹ 149 more rows

Caution! 함수 group_infomap()는 난수를 이용하여 그룹을 생성하기 때문에 set.seed를 사용해 항상 동일한 결과를 출력하도록 한다. 게다가, Package "tidygraph"에는 함수 centrality_degree()와 함수 group_infomap()외에 연결 중심성과 커뮤니티를 구하는 다양한 함수가 있다. (참고. Introducing tidygraph)


2-4-2. 연결 중심성과 커뮤니티 표현

set.seed(1234)                                    # Seed 고정 -> 항상 동일한 모양의 그래프 출력
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") +        # 폰트 -> 2-2에서 정의한 나눔바른 고딕 폰트
  theme_graph()                                   # 배경 삭제

Result! 노드의 크기가 클수록 다른 노드들과 밀접하게 연결되어 있다는 것을 의미하며, 같은 색깔로 분류된 노드들은 관계가 가깝다는 것을 의미한다. 예를 들어, 노드 크기가 가장 큰 “봉준호”는 다른 단어들과 가장 밀접하게 연결되어 있기 때문에 연결 중심성이 크며, 중요한 단어라는 것을 알 수 있다.


2-4-3. 네트워크의 주요 단어 확인

graph_comment %>%                                 # 연결 중심성과 커뮤니티 결과가 저장되어 있는 객체 in 2-4-1
  filter(name == "봉준호")                        # "봉준호"만 추출
# A tbl_graph: 1 nodes and 0 edges
#
# An unrooted tree
#
# A tibble: 1 × 3
  name   centrality group
  <chr>       <dbl> <fct>
1 봉준호         62 4    
#
# A tibble: 0 × 3
# ℹ 3 variables: from <int>, to <int>, n <dbl>

Result! 노드 크기가 가장 큰 “봉준호”는 4번 커뮤니티(group)로 분류되어 있다.

# "봉준호"와 같은 커뮤니티로 분류된 단어 살펴보기
graph_comment %>%                                 # 연결 중심성과 커뮤니티 결과가 저장되어 있는 객체 in 2-4-1
  filter(group == 4) %>%                          # 4번 커뮤니티 단어들만 추출
  arrange(-centrality) %>%                        # 연결 중심성을 기준으로 내림차순 정렬
  data.frame()                                    # Data Frame 형태로 변환
    name centrality group
1 봉준호         62     4
2   받다         10     4
3   자랑          6     4
4 만들다          4     4

# 연결 중심성이 높은 주요 단어 살펴보기
graph_comment %>%                                 # 연결 중심성과 커뮤니티 결과가 저장되어 있는 객체 in 2-4-1
  arrange(-centrality)                            # 연결 중심성을 기준으로 내림차순 정렬
# A tbl_graph: 36 nodes and 152 edges
#
# An undirected multigraph with 1 component
#
# A tibble: 36 × 3
  name     centrality group
  <chr>         <dbl> <fct>
1 봉준호           62 4    
2 축하             34 2    
3 영화             26 3    
4 기생충           26 1    
5 작품상           14 5    
6 대한민국         10 3    
# ℹ 30 more rows
#
# A tibble: 152 × 3
   from    to     n
  <int> <int> <dbl>
1     1     2   198
2     1     2   198
3     1     3   119
# ℹ 149 more rows

Result! 연결 중심성이 높은 단어순으로 출력하면 어떤 단어를 눈여겨봐야 할지 알 수 있다. 출력 결과를 보면 “봉준호” 다음으로 “축하”의 연결 중심성이 높으며, 2번 커뮤니티(group)로 분류되었음을 알 수 있다.

# "축하"와 같은 커뮤니티로 분류된 단어 살펴보기
graph_comment %>%                                 # 연결 중심성과 커뮤니티 결과가 저장되어 있는 객체 in 2-4-1
  filter(group == 2) %>%                          # 2번 커뮤니티 단어들만 추출
  arrange(-centrality) %>%                        # 연결 중심성을 기준으로 내림차순 정렬
  data.frame()                                    # 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

Result! 2번 커뮤니티로 분류된 단어를 살펴보면 주로 봉준호 감독의 수상을 축하하는 내용임을 알 수 있다.


2-5. 주요 단어가 사용된 원문 추출

news_comment %>%                                  # 전처리를 수행한 결과가 저장되어 있는 객체 in 1-2
  filter(str_detect(reply, "봉준호") &            # "봉준호"와 "대박"이 함께 사용된 댓글 추출
           str_detect(reply, "대박")) %>%
  select(reply)                                   # 변수 reply 선택 -> 댓글만 추출
# A tibble: 19 × 1
   reply                                                              
   <chr>                                                              
 1 대박 대박 진짜 대박 봉준호 감독님과 우리 배우들 너무 다랑스러워요  
 2 내가 죽기전에 아카데미에서 한국어를 들을줄이야 봉준호대박 기생충대…
 3 대박 관왕이라니 축하합니다 봉준호를 배출한 충무로 그리고 문화강국 …
 4 우와 대박 진자 대단하다 봉준호                                     
 5 봉준호 경사났네 대박중에 대에박 축하합니다                         
 6 봉준호 작품상 탔다 대박                                            
 7 봉준호 군대 면제시켜도될듯 대박 여윽시 위대한 한국에는 위대한 봉준…
 8 아니 다른상을 받은것도 충분히 대단하고 굉장하지만 최고의 영예인 작…
 9 봉준호 군대 면제시켜도될듯 대박 여윽시 위대한 한국에는 위대한 봉준…
10 봉준호감독님대박 축하합니다                                        
11 와 봉준호 대박 축하드려요                                          
12 대박이다 감격의 한해입니다 봉준호 감독님 정말 축하드립니다         
13 좌파영화인 봉준호가 좌파영화로 아카데미 작품상 대박 배 아프겠다 안…
14 각본상 외국여영화상 수상 대박입니다 축하하고 잠시후에 봉준호 감독… 
15 한국 역사상 최초인 오스카상 관왕 진짜 대박 대한민국 위상과 국격을 …
16 미쳣다 감독상은 진짜 예상못햇는데 마틴 스콜쎄지 퀜틴타란티노 스티… 
17 봉준호감독 짱 가 대박났네                                          
18 와 대박 소름돋아 으악 봉준호 감독님 너무너무 축하드려요            
19 와 진짜 대박이다 봉준호 언젠가 정말 세계적으로 인정 받는 날이 올줄…
news_comment %>%                                  # 전처리를 수행한 결과가 저장되어 있는 객체 in 1-2
  filter(str_detect(reply, "박근혜") &            # "박근혜"와 "블랙리스트"가 함께 사용된 댓글 추출
           str_detect(reply, "블랙리스트")) %>%
  select(reply)                                   # 변수 reply 선택 -> 댓글만 추출
# A tibble: 63 × 1
   reply                                                              
   <chr>                                                              
 1 일베와 자한당이 싫어하는 봉준호 감독이 아카데미에서 상받으니 쪽바… 
 2 박근혜 블랙리스트 로 낙인찍은 봉준호 감독님이 아시아 최초로 오스카…
 3 우리나라에서만 좌파다 빨갱이다 라고 비하함 박근혜 때 이런 세계적 … 
 4 박근혜 최순실 블랙리스트에 오른 훌륭하신 감독님 축하합니다         
 5 박근혜정부가 얼마나 썩고 무능했냐면 각종 영화제에서 최고상 수상을 …
 6 넷상 보수들 만큼 이중적인 새 끼들 없음 봉준호 송강호 보고 종북좌빨…
 7 박근혜 자한당 독재시절 봉준호 송강호를 블랙리스트 올려놓고 활동 방…
 8 대단합니다 김연아 방탄 봉준호 스포츠 음악 영화 못하는게 없어요 좌… 
 9 송강호 봉준호 박근혜 이명박 시절 블랙리스트 이제 어떻게 깔려구     
10 이명박근혜정권당시 좌파감독이라고 블랙리스트까지 올랏던 봉준호 역… 
# ℹ 53 more rows
news_comment %>%                                  # 전처리를 수행한 결과가 저장되어 있는 객체 in 1-2
  filter(str_detect(reply, "기생충") &            # "기생충"과 "조국"이 함께 사용된 댓글 추출
           str_detect(reply, "조국")) %>%
  select(reply)                                   # 변수 reply 선택 -> 댓글만 추출
# A tibble: 64 × 1
   reply                                                              
   <chr>                                                              
 1 조국이가 받아야 한다 기생충 스토리 제공                            
 2 한번도경험하지 못한 조국가족사기단기생충 개봉박두                  
 3 와 조국 가족 사기단 부제 기생충 최고                               
 4 문재인과 조국 기생충 리얼                                          
 5 기생충은 좌좀 조국 가족을 패러디한 영화라서 우파들도 열광하고 있는…
 6 조국 가족이 기생충 영화를 꼭 봐야되는데                            
 7 좌파 인생영화인데 좌파 기생충들에게 이 상을 받쳐라 조국 서울대 문… 
 8 기생충 조국 봉준호 만세                                            
 9 봉준호감독님 글로벌 영화계 큰상수상을 진심으로 축하합니다 다만 기… 
10 기생충보면서 조국생각난사람 나쁜일라나 봉준호 감독님이 현 시대를 … 
# ℹ 54 more rows

3. 단어 간 상관분석


3-1. 파이 계수


3-1-1. 의미



3-1-2. 파이 계수 계산

word_cors <- comment %>%                         # 형태소로 토큰화하고 유의어 처리를 수행한 결과가 저장되어 있는 객체 in 2-3
  add_count(word) %>%                            # 단어 빈도 추가
  filter(n >= 20) %>%                            # 20회 이상 사용된 단어만 추출
  # 파이 계수 계산
  pairwise_cor(item = word,                      # 단어가 입력되어 있는 변수명
               feature = id,                     # 텍스트 구분 기준
               sort = T)                         # 파이 계수를 내림순으로 정렬

word_cors
# A tibble: 26,732 × 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
# ℹ 26,722 more rows

Result! 변수 correlation이 파이 계수 \(\phi\)를 의미하며, 이를 보고 두 단어의 상관성이 얼마나 큰지 알 수 있다. 출력 결과를 보면 “올리다”와 “블랙리스트”의 파이 계수가 0.478로 가장 높다는 것을 알 수 있으며, 이는 댓글에서 “올리다”가 나타날 때 “블랙리스트”가 나타날 확률이 높다는 것을 의미한다.
Caution! 함수 count()를 이용하면 빈도만 남고 원자료가 제거되지만, 함수 add_count()를 이용하면 원자료에 빈도를 나타낸 변수를 추가한다.


Caution! 함수 filter()를 이용해 특정 단어와 상관성이 큰 단어를 살펴볼 수 있다.

# "대한민국"과 상관관계가 높은 단어
word_cors %>% 
  filter(item1 == "대한민국")                   
# A tibble: 163 × 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
# ℹ 153 more rows

Result! 댓글에서 “대한민국”이 나타날 때 “국민”이 나타날 확률이 높다는 것을 의미한다.

# "역사"와 상관관계가 높은 단어
word_cors %>% 
  filter(item1 == "역사")
# A tibble: 163 × 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
# ℹ 153 more rows

Result! 댓글에서 “역사”가 나타날 때 “쓰다”가 나타날 확률이 높다는 것을 의미한다.


3-1-3. 파이 계수에 대한 시각화

## 관심 단어 목록
target <- c("대한민국", "역사", "수상소감", "조국", "박근혜", "블랙리스트")

# 1. 관심 단어별로 파이 계수가 큰 단어 추출하기
top_cors <- word_cors %>%                                           # 파이 계수가 저장되어 있는 객체 in 3-1-2
  filter(item1 %in% target) %>%                                     # 변수 item1에서 관심 단어에 해당하는 행만 추출
  group_by(item1) %>%                                               # 변수 item1에 대해 그룹화 -> 관심 단어별로 파이 계수가 높은 8개 단어를 추출하기 위해
  slice_max(correlation, n = 8)                                     # 파이 계수가 높은 8개 단어 추출


top_cors$item1 <- factor(top_cors$item1,                            # Factor 변환  
                         levels = target)                           # levels에 입력한 순서대로 그래프 출력

# 2. 막대 그래프 만들기
ggplot(top_cors, aes(x = reorder_within(item2, correlation, item1), # reorder_within : 관심 단어(item1)별로 파이 계수(correlation)가 높은 순으로 item 2 정렬 
                     y = correlation,
                     fill = item1)) +                               # 관심 단어에 따라 막대 색깔 다르게 표현
  geom_col(show.legend = F) +                                       # 막대 그래프
  facet_wrap(~ item1, scales = "free") +                            # 변수 item1의 항목별로 그래프 작성 -> 관심 단어 각각의 막대 그래프 작성
  coord_flip() +                                                    # 막대를 가로로 회전
  scale_x_reordered() +                                             # 불필요한 단어 제거
  labs(x = NULL) +                                                  # x축 제목 제거 -> 막대를 가로로 회전했기 때문에 y축 제목이 제거됨
  theme(text = element_text(family = "nanumgothic"))                # 폰트 -> 2-2에서 정의한 나눔바른 고딕 폰트


3-1-4. 파이 계수를 이용하여 네트워크 그래프 만들기

# 1. 파이 계수로 네트워크 그래프 데이터 생성
set.seed(1234)                                    # Seed 고정 -> For group_infomap()
graph_cors <- word_cors %>%                       # 파이 계수가 저장되어 있는 객체 in 3-1-2
  filter(correlation >= 0.15) %>%                 # 파이 계수가 0.15 이상인 단어 쌍만 추출 -> 네트워크 복잡성 방지
  as_tbl_graph(directed = F) %>%                  # 네트워크 그래프 데이터로 변환 / directed = F : 그래프에 방향 표시 X
  mutate(centrality = centrality_degree(),        # centrality_degree : 연결 중심성 계산
         group = as.factor(group_infomap()))      # group_infomap : 커뮤니티를 찾음 / as.factor : Factor로 변환 -> 그룹에 따라 다른 색으로 표현하기 위해


set.seed(1234)                                    # Seed 고정 -> 항상 동일한 모양의 그래프 출력
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") +        # 폰트 -> 2-2에서 정의한 나눔바른 고딕 폰트
  theme_graph()                                   # 배경 삭제

Result! 출력한 그래프를 보면 상관성이 큰 단어쌍을 한눈에 알 수 있다. 예를 들어, “올리다”와 “블랙리스트” 사이에는 엣지가 굵기 때문에 파이 계수가 높다는 것을 알 수 있으며, 특히, 노드 크기가 큰 “블랙리스트”는 다른 단어와도 밀접하게 연결되어 있기 때문에 연결 중심성이 크다는 것을 알 수 있다.
Caution! 파이 계수가 얼마 이상인 단어쌍을 추출해야 하는지는 정답이 없다. 추출 기준 값을 바꾸어 가며 여러 번 그래프를 만들어 본 다음 적당한 값을 찾아야 한다.


3-2. 동시 출현 빈도와의 차이점


4. 엔그램


4-1. 엔그램 토큰화


Caution! Package "tidytext"의 함수 unnest_tokens()를 이용하면 텍스트를 엔그램으로 토큰화할 수 있다. 옵션 token"ngrams"를 입력하고 고려할 연이은 단어 개수를 n에 입력하면 된다. 2를 입력하면 바이그램, 3을 입력하면 트라이그램이 된다. 게다가, 앞에서 단어를 기준으로 토큰화하는 것은 n=1인 유니그램(Unigram)으로 토큰화하는 것과 같다.

# 엔그램 토큰화 간단 예제
text <- tibble(value = "대한민국은 민주공화국이다. 대한민국의 주권은 국민에게 있고, 모든 권력은 국민으로부터 나온다.")

# 바이그램 토큰화
text %>%
  unnest_tokens(input = value,                  # 토큰화를 수행할 텍스트가 포함된 변수명 
                output = word,                  # 출력 변수명
                token = "ngrams",               # 엔그램 토큰화
                n = 2)                          # 바이그램
# A tibble: 9 × 1
  word                     
  <chr>                    
1 대한민국은 민주공화국이다
2 민주공화국이다 대한민국의
3 대한민국의 주권은        
4 주권은 국민에게          
5 국민에게 있고            
6 있고 모든                
7 모든 권력은              
8 권력은 국민으로부터      
9 국민으로부터 나온다      
# 트라이그램 토큰화
text %>%
  unnest_tokens(input = value,                  # 토큰화를 수행할 텍스트가 포함된 변수명 
                output = word,                  # 출력 변수명
                token = "ngrams",               # 엔그램 토큰화
                n = 3)                          # 트라이그램
# A tibble: 8 × 1
  word                                
  <chr>                               
1 대한민국은 민주공화국이다 대한민국의
2 민주공화국이다 대한민국의 주권은    
3 대한민국의 주권은 국민에게          
4 주권은 국민에게 있고                
5 국민에게 있고 모든                  
6 있고 모든 권력은                    
7 모든 권력은 국민으로부터            
8 권력은 국민으로부터 나온다          
# 단어 기준 토큰화
text %>%
  unnest_tokens(input = value,                  # 토큰화를 수행할 텍스트가 포함된 변수명 
                output = word,                  # 출력 변수명
                token = "words")                # 단어 기준으로 토큰화
# A tibble: 10 × 1
   word          
   <chr>         
 1 대한민국은    
 2 민주공화국이다
 3 대한민국의    
 4 주권은        
 5 국민에게      
 6 있고          
 7 모든          
 8 권력은        
 9 국민으로부터  
10 나온다        
# 유니그램 토큰화
text %>%
  unnest_tokens(input = value,                  # 토큰화를 수행할 텍스트가 포함된 변수명 
                output = word,                  # 출력 변수명
                token = "ngrams",               # 엔그램 토큰화
                n = 1)                          # 유니그램
# A tibble: 10 × 1
   word          
   <chr>         
 1 대한민국은    
 2 민주공화국이다
 3 대한민국의    
 4 주권은        
 5 국민에게      
 6 있고          
 7 모든          
 8 권력은        
 9 국민으로부터  
10 나온다        

# 분석 데이터 news_comment_parasite에 대한 엔그램 토큰화
## 1. 명사, 동사, 그리고 형용사 추출
comment_new <- comment_pos %>%                            # 품사를 분리한 결과가 저장되어 있는 객체 in 1-3-2
  separate_rows(word, sep = "[+]") %>%                    # "+"를 기준으로 행 분리 
  filter(str_detect(word, "/n|/pv|/pa")) %>%              # 변수 word가"/n" (명사), "/pv" (동사)와 "/pa" (형용사)인 행만 추출
  mutate(word = ifelse(str_detect(word, "/pv|/pa"),       
                       str_replace(word, "/.*$", "다"),   # "/pv" (동사)와 "/pa" (형용사)인 경우 "/"로 시작하는 모든 문자를 "다"로 변경 -> 태그를 "다"로 변경
                       str_remove(word, "/.*$"))) %>%     # "/n" (명사)인 경우 "/"로 시작하는 모든 문자 제거 -> 태그 제거
  filter(str_count(word) >= 2) %>%                        # 두 글자 이상의 단어만 추출
  arrange(id)                                             # 변수 id를 기준으로 오름차순 정렬


comment_new
# A tibble: 26,860 × 3
   reply                                                     id word  
   <chr>                                                  <int> <chr> 
 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 우리  
 2 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 좋다  
 3 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 생기다
 4 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘다
 5 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복한
 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복  
 7 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 축하… 
 8 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복  
 9 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘다
10 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하…     2 기쁘다
# ℹ 26,850 more rows

Result! 형태소로 토큰화하고 품사별로 행을 분리한 comment_pos (참고. 1-3-2. 품사 분리)에서 명사, 동사, 그리고 형용사를 추출한 후 두 글자 이상만 남긴다.
Caution! 텍스트 원문을 엔그램으로 바로 토큰화하면 “하다”, “했다”, “하며”, “하므로”처럼 원형이 같지만 표현만 다른 단어들이 전부 개별 단어로 취급된다. 단어의 표현이 아니라 의미 중심으로 분석해야 하므로 원문에서 형태소를 추출한 다음 엔그램으로 토큰화해야 한다.

## 2. 유의처 처리 : 표현은 다르지만 의미가 비슷한 단어를 한 단어로 통일
comment_new <- comment_new %>%                         # 명사, 동사, 그리고 형용사인 단어만 추출한 결과가 저장되어 있는 객체 
  mutate(
    # "감독상"을 제외하고 "감독"을 포함하는 모든 단어는 "봉준호"로 통일
    word = ifelse(str_detect(word, "감독") & !str_detect(word, "감독상"),   
                  "봉준호", word), 
    # "오르다"는 "올리다"로 변경
    word = ifelse(word == "오르다", "올리다", word),
    # "축하"를 포함하는 모든 단어는 "축하"로 통일
    word = ifelse(str_detect(word, "축하"), "축하", word))

comment_new %>%
  select(id, word)                                     # 변수 id와 word만 선택
# A tibble: 26,860 × 2
      id word  
   <int> <chr> 
 1     1 우리  
 2     1 좋다  
 3     1 생기다
 4     1 기쁘다
 5     1 행복한
 6     1 행복  
 7     1 축하  
 8     1 행복  
 9     1 기쁘다
10     2 기쁘다
# ℹ 26,850 more rows

Result! 첫 번째 행부터 아홉 번째 행까지 첫 번째 댓글에서 사용된 단어들을 나타내고 있다(id = 1). 분석을 위해 한 행에 하나의 댓글에서 사용된 모든 단어들을 나열하도록 변환한다. 즉, 첫 번째 행에 첫 번째 댓글에서 사용된 모든 단어들을 나열하고, 두 번째 행에서는 두 번째 댓글에서 사용된 모든 단어들을 나열한다.

## 3. 한 행에 하나의 댓글에서 사용된 모든 단어들을 나열
line_comment <- comment_new %>%                       
  group_by(id) %>%                                    # 변수 id에 대해 그룹화 
  summarise(sentence = paste(word, collapse = " "))   # 변수 id의 항목별로 변수 word에 적힌 단어들을 띄어쓰기 " "로 결합

line_comment
# A tibble: 4,007 × 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 대한민국 봉준호 대단 한국의 문화 자긍심 가지                 
# ℹ 3,997 more rows

Result! 첫 번째 행은 첫 번째 댓글에서 사용된 단어들을 나타내고, 두 번째 행은 두 번째 댓글에서 사용된 단어들을 나타낸다.

## 4. 바이그램 토큰화
bigram_comment <- line_comment %>%             
  unnest_tokens(input = sentence,               # 토큰화를 수행할 텍스트가 포함된 변수명 
                output = bigram,                # 출력 변수명
                token = "ngrams",               # 엔그램 토큰화
                n = 2)                          # 바이그램

bigram_comment
# A tibble: 23,348 × 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 시국 기쁘다  
# ℹ 23,338 more rows

4-2. 연이어 사용된 단어쌍 빈도 계산

# 1. 바이그램 분리하기
bigram_seprated <- bigram_comment %>%                  # 바이그램 토큰화를 수행한 결과가 저장되어 있는 객체 in 4-1
  separate(bigram, c("word1", "word2"), sep = " ")     # 변수 bigram에 적힌 두 단어를 변수 word1과 word2으로 분리 

bigram_seprated
# A tibble: 23,348 × 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 시국   기쁘다
# ℹ 23,338 more rows
# 2. 단어쌍 빈도 계산
pair_bigram <- bigram_seprated %>%                    
  count(word1, word2,                                  # 단어쌍의 빈도 계산
        sort = T) %>%                                  # 빈도를 내림차순으로 정렬 
  na.omit()                                            # 결측치가 있는 행 제거

pair_bigram
# A tibble: 19,030 × 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
# ℹ 19,020 more rows
# 동시 출현 단어쌍
pair %>%                                               # 단어 동시 출현 빈도의 결과가 저장되어 있는 객체 in 2-3
  filter(item1 == "대한민국")

# 바이그램 단어쌍
pair_bigram %>%                                        # 바이그램 단어쌍 빈도가 계산되어 있는 객체
  filter(word1 == "대한민국")


Result! pair_bigram는 두 단어가 얼마나 자주 연이어 등장하는 지를 나타낸다. 예를 들어, “대한민국”과 “국민”은 21번 연이어 등장한다.
Caution! 동시 출현 단어쌍은 일반적으로 자주 사용된 단어로 구성되며 단어쌍의 종류가 많고 빈도도 높다. 반면, 바이그램 단어쌍은 의미가 연결되는 단어로 구성되며 단어쌍의 종류가 적고 빈도도 낮다.

# 동시 출현 단어쌍
pair %>%                                               # 단어 동시 출현 빈도의 결과가 저장되어 있는 객체 in 2-3
  filter(item1 == "아카데미")
# A tibble: 1,243 × 3
   item1    item2      n
   <chr>    <chr>  <dbl>
 1 아카데미 봉준호    59
 2 아카데미 기생충    47
 3 아카데미 작품상    44
 4 아카데미 영화      42
 5 아카데미 축하      30
 6 아카데미 받다      23
 7 아카데미 한국      23
 8 아카데미 아니다    23
 9 아카데미 수상      22
10 아카데미 미국      22
# ℹ 1,233 more rows
# 바이그램 단어쌍
pair_bigram %>%                                        # 바이그램 단어쌍 빈도가 계산되어 있는 객체
  filter(word1 == "아카데미")
# A tibble: 141 × 3
   word1    word2      n
   <chr>    <chr>  <int>
 1 아카데미 작품상    22
 2 아카데미 시상식    19
 3 아카데미 수상       7
 4 아카데미 감독상     6
 5 아카데미 로컬       6
 6 아카데미 개부문     4
 7 아카데미 받다       4
 8 아카데미 빨갱이     4
 9 아카데미 역사       4
10 아카데미 왕이       4
# ℹ 131 more rows

Result! “아카데미”와 “작품상”은 22번 연이어 등장한다.


4-3. 네트워크 그래프

# 1. 네트워크 그래프 데이터 
graph_bigram <- pair_bigram %>%                        # 바이그램 단어쌍 빈도가 계산되어 있는 객체 in 4-2
  filter(n >= 8) %>%                                   # 8회 이상 함께 사용된 단어쌍만 추출
  as_tbl_graph()                                       # 네트워크 그래프 데이터로 변환

# 2. 네트워크 그래프 
set.seed(1234)                                         # Seed 고정 -> 항상 동일한 모양의 그래프 출력
word_network(graph_bigram)                             # 함수 word_network in 2-2

Result! 출력 그래프를 보면 “대단”, “대단하다”처럼 의미가 비슷한 단어가 개별 노드로 되어 있어 네트워크가 복잡하고 이해하기 어렵다. 단어의 관계가 분명하게 드러나도록 유의어를 통일할 필요가 있다.


4-3-1. 유의어 처리

  1. 바이그램 단어쌍이 저장되어 있는 bigram_seprated (참고. 4-2. 연이어 사용된 단어쌍 빈도 계산)에서 비슷한 단어를 통일한다.
  2. 같은 단어가 연속 사용된 단어쌍을 해석하는 것은 의미가 없으므로 제거한다.
  3. 단어쌍 빈도를 구하고 함수 na.omit()를 이용해 결측치를 제거한다.
# 유의어 처리
bigram_seprated <- bigram_seprated %>%                                      # 바이그램 단어쌍이 저장되어 있는 객체 in 4-2
  mutate(word1 = ifelse(str_detect(word1, "대단"), "대단", word1),          # 변수 word1에서 "대단"을 포함하는 모든 단어는 "대단"으로 변경 
         word2 = ifelse(str_detect(word2, "대단"), "대단", word2),          # 변수 word2에서 "대단"을 포함하는 모든 단어는 "대단"으로 변경 
         word1 = ifelse(str_detect(word1, "자랑"), "자랑", word1),          # 변수 word1에서 "자랑"을 포함하는 모든 단어는 "자랑"으로 변경
         word2 = ifelse(str_detect(word2, "자랑"), "자랑", word2),          # 변수 word2에서 "자랑"을 포함하는 모든 단어는 "자랑"으로 변경
         word1 = ifelse(str_detect(word1, "짝짝짝"), "짝짝짝", word1),      # 변수 word1에서 "짝짝짝"을 포함하는 모든 단어는 "짝짝짝"으로 변경
         word2 = ifelse(str_detect(word2, "짝짝짝"), "짝짝짝", word2)) %>%  # 변수 word2에서 "짝짝짝"을 포함하는 모든 단어는 "짝짝짝"으로 변경
  filter(word1 != word2)                                                    # 같은 단어가 연속 사용된 단어쌍 제거

bigram_seprated
# A tibble: 22,584 × 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 시국   기쁘다
# ℹ 22,574 more rows
# 단어쌍 빈도 계산
pair_bigram <- bigram_seprated %>%
  count(word1, word2,                        # 단어쌍의 빈도 계산
        sort = T) %>%                        # 빈도를 내림차순으로 정렬 
  na.omit()                                  # 결측치가 있는 행 제거

pair_bigram
# A tibble: 18,836 × 3
   word1      word2          n
   <chr>      <chr>      <int>
 1 블랙리스트 올리다        64
 2 진심       축하          64
 3 봉준호     축하          57
 4 대단       축하          42
 5 봉준호     송강호        34
 6 영화       만들다        31
 7 축하       봉준호        31
 8 축하       자랑          28
 9 봉준호     블랙리스트    27
10 대박       축하          26
# ℹ 18,826 more rows

Caution! Package "dplyr"의 함수 mutate_at()case_when()을 이용하면 여러 변수의 유의어를 처리하는 코드를 다음과 같이 작성할 수 있다.

# ※ 유의어 한 번에 처리
bigram_seprated_new <- bigram_seprated %>%                                  # 바이그램 단어쌍이 저장되어 있는 객체 in 4-2
  mutate_at(vars("word1", "word2"),                                         
            ~ case_when(   
              str_detect(., "대단") ~ "대단",                               # 변수 word1과 word2에 대해 "대단"을 포함하는 모든 단어는 "대단"으로 변경 
              str_detect(., "자랑") ~ "자랑",                               # 변수 word1과 word2에 대해 "자랑"을 포함하는 모든 단어는 "자랑"으로 변경 
              str_detect(., "짝짝짝") ~ "짝짝짝",                           # 변수 word1과 word2에 대해 "짝짝짝"을 포함하는 모든 단어는 "짝짝짝"으로 변경 
              T ~ .)) %>%                                                   # 변수 word1과 word2에 대해 "대단", "자랑", 그리고 "짝짝짝"을 포함하지 않는 단어는 그대로
  filter(word1 != word2)                                                    # 같은 단어가 연속 사용된 단어쌍 제거

bigram_seprated_new
# A tibble: 22,584 × 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 시국   기쁘다
# ℹ 22,574 more rows

4-3-2. 네트워크 그래프

  1. 바이그램 단어쌍의 빈도가 저장되어 있는 pair_bigram (참고. 4-2. 연이어 사용된 단어쌍 빈도 계산)를 이용해 네트워크 그래프 데이터를 다시 만든다.
    • 네트워크가 너무 복잡하지 않도록 8회 이상 사용된 단어쌍만 추출해 네트워크 그래프 데이터를 만든 다음 연결 중심성과 커뮤니티를 추가한다.
  2. 함수 ggraph()를 이용해 네트워크 그래프를 만든다.
# 1. 네트워크 그래프 데이터 
set.seed(1234)                                # Seed 고정 -> For group_infomap()
graph_bigram <- pair_bigram %>%      
  filter(n >= 8) %>%                          # 8회 이상 함께 사용된 단어쌍만 추출
  as_tbl_graph(directed = F) %>%              # 네트워크 그래프 데이터로 변환 / directed = F : 그래프에 방향 표시 X
  # 연결 중심성과 커뮤니티 변수 추가
  mutate(centrality = centrality_degree(),    # centrality_degree : 연결 중심성 계산
         group = as.factor(group_infomap()))  # group_infomap : 커뮤니티를 찾음 / as.factor : Factor로 변환 -> 그룹에 따라 다른 색으로 표현하기 위해

# 2. 네트워크 그래프 
set.seed(1234)                                # Seed 고정 -> 항상 동일한 모양의 그래프 출력
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") +    # 폰트 -> 2-2에서 정의한 나눔바른 고딕 폰트
  theme_graph()                               # 배경 삭제

Result! 출력 그래프를 보면 자주 연이어 사용된 단어쌍 중심으로 네트워크를 형성하기 때문에 단어의 맥락과 의미를 구체적으로 이해할 수 있다. “이미경-부회장”, “조국-가족”처럼 개별 단어의 빈도는 낮지만 자주 연이어 사용되고 함께 사용할 때 분명한 의미를 지니는 단어쌍도 발견할 수 있다.


4-4. 파이 계수와 차이점


5. 네트워크 그래프 정리



요약

# 1. 동시 출현 단어 분석
# 1-1. 품사 기준 토큰화
comment_pos <- news_comment %>%                # 전처리를 수행한 결과가 저장되어 있는 객체 in 1-2
  unnest_tokens(input = reply,                 # 토큰화를 수행할 텍스트가 포함된 변수명
                output = word,                 # 출력 변수명
                token = SimplePos22,           # 22개의 품사 기준으로 토큰화
                drop = F)                      # 원문 제거 X

comment_pos
# A tibble: 39,956 × 3
   reply                                                     id word  
   <chr>                                                  <int> <chr> 
 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 정말/…
 2 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 우리/…
 3 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 집/nc…
 4 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 좋/pa…
 5 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 일/nc…
 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 생기/…
 7 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘/…
 8 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복… 
 9 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 것/nb…
10 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 나/np…
# ℹ 39,946 more rows
# 1-2. 명사, 동사, 그리고 형용사 추출
comment <- comment_pos %>%
  separate_rows(word, sep = "[+]") %>%                    # "+"를 기준으로 행 분리 
  filter(str_detect(word, "/n|/pv|/pa")) %>%              # 변수 word가"/n" (명사), "/pv" (동사)와 "/pa" (형용사)인 행만 추출
  mutate(word = ifelse(str_detect(word, "/pv|/pa"),       
                       str_replace(word, "/.*$", "다"),   # "/pv" (동사)와 "/pa" (형용사)인 경우 "/"로 시작하는 모든 문자를 "다"로 변경 -> 태그를 "다"로 변경
                       str_remove(word, "/.*$"))) %>%     # "/n" (명사)인 경우 "/"로 시작하는 모든 문자 제거 -> 태그 제거 
  filter(str_count(word) >= 2) %>%                        # 두 글자 이상의 단어만 추출
  arrange(id)                                             # 변수 id를 기준으로 오름차순 정렬

comment
# A tibble: 26,860 × 3
   reply                                                     id word  
   <chr>                                                  <int> <chr> 
 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 우리  
 2 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 좋다  
 3 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 생기다
 4 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘다
 5 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복한
 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복  
 7 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 축하… 
 8 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 행복  
 9 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼 나…     1 기쁘다
10 와 너무 기쁘다 이 시국에 정말 내 일같이 기쁘고 감사하…     2 기쁘다
# ℹ 26,850 more rows
# 1-3. 단어 동시 출현 빈도 계산
pair <- comment %>%
  pairwise_count(item = word,          # 단어가 입력되어 있는 변수명                    
                 feature = id,         # 텍스트 구분 기준
                 sort = T)             # 빈도를 내림차순 정렬

pair
# A tibble: 245,920 × 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
# ℹ 245,910 more rows
# 2. 단어 간 상관 분석
# 2-1. 파이 계수 계산
word_cors <- comment %>%               # 형태소로 토큰화하고 유의어 처리를 수행한 결과가 저장되어 있는 객체 in 2-3
  add_count(word) %>%                  # 단어 빈도 추가
  filter(n >= 20) %>%                  # 20회 이상 함께 사용된 단어만 추출
  # 파이 계수 계산
  pairwise_cor(item = word,            # 단어가 입력되어 있는 변수명
               feature = id,           # 텍스트 구분 기준
               sort = T)               # 파이 계수를 내림차순으로 정렬

word_cors
# A tibble: 29,412 × 3
   item1      item2      correlation
   <chr>      <chr>            <dbl>
 1 올리다     블랙리스트       0.436
 2 블랙리스트 올리다           0.436
 3 역사       쓰다             0.370
 4 쓰다       역사             0.370
 5 감독님     봉준호           0.335
 6 봉준호     감독님           0.335
 7 박근혜     블랙리스트       0.322
 8 블랙리스트 박근혜           0.322
 9 가족       조국             0.306
10 조국       가족             0.306
# ℹ 29,402 more rows
# 3. 엔그램
# 3-1. 한 행에 하나의 댓글에서 사용된 모든 단어들을 나열
line_comment <- comment %>%
  group_by(id) %>%                                    # 변수 id에 대해 그룹화 
  summarise(sentence = paste(word, collapse = " "))   # 변수 id의 항목별로 변수 word에 적힌 단어들을 띄어쓰기 " "로 결합

line_comment
# A tibble: 4,007 × 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 대한민국 봉준호감독 대단 한국의 문화 자긍심 가지             
# ℹ 3,997 more rows
# 3-2. 바이그램 토큰화
bigram_comment <- line_comment %>%
  unnest_tokens(input = sentence,                      # 토큰화를 수행할 텍스트가 포함된 변수명 
                output = bigram,                       # 출력 변수명
                token = "ngrams",                      # 엔그램 토큰화
                n = 2)                                 # 바이그램

bigram_comment
# A tibble: 23,348 × 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 시국 기쁘다    
# ℹ 23,338 more rows
# 3-3. 바이그램 분리
bigram_seprated <- bigram_comment %>%
  separate(bigram, c("word1", "word2"), sep = " ")     # 변수 bigram에 적힌 두 단어를 변수 word1과 word2으로 분리 

bigram_seprated
# A tibble: 23,348 × 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 시국       기쁘다    
# ℹ 23,338 more rows
# 3-4. 단어쌍 빈도 계산
pair_bigram <- bigram_seprated %>%                    
  count(word1, word2,                        # 단어쌍의 빈도 계산
        sort = T) %>%                        # 빈도를 내림차순으로 정렬 
  na.omit()                                  # 결측치가 있는 행 제거

pair_bigram
# A tibble: 19,455 × 3
   word1      word2          n
   <chr>      <chr>      <int>
 1 봉준호     감독          83
 2 봉준호     감독님        68
 3 진심       축하드리다    51
 4 블랙리스트 올리다        40
 5 영화       만들다        31
 6 한국       영화          25
 7 블랙리스트 오르다        24
 8 역사       쓰다          24
 9 봉준호     송강호        22
10 아카데미   작품상        22
# ℹ 19,445 more rows
# 4. 네트워크 그래프
# 4-1. 네트워크 그래프 데이터 생성
set.seed(1234)                                  # Seed 고정 -> For group_infomap()
graph_bigram <- pair_bigram %>%      
  filter(n >= 8) %>%                            # 8회 이상 함께 사용된 단어쌍만 추출
  as_tbl_graph(directed = F) %>%                # 네트워크 그래프 데이터로 변환 / directed = F : 그래프에 방향 표시 X
  mutate(centrality = centrality_degree(),      # centrality_degree : 연결 중심성 계산
         group = as.factor(group_infomap()))    # group_infomap : 커뮤니티를 찾음 / as.factor : Factor로 변환 -> 그룹에 따라 다른 색으로 표현하기 위해


# 4-2. 네트워크 그래프 
set.seed(1234)                                  # Seed 고정 -> 항상 동일한 모양의 그래프 출력
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") +      # 폰트 -> 2-2에서 정의한 나눔바른 고딕 폰트
  theme_graph()                                 # 배경 삭제

Reuse

Text and figures are licensed under Creative Commons Attribution CC BY 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".