Processing math: 100%
+ - 0:00:00
Notes for current slide
Notes for next slide

Do it! 쉽게 배우는 R 텍스트 마이닝

1 / 131

05 의미망 분석:
어떤 맥락에서 단어를 썼을까?

3 / 131

We'll make

4 / 131

We'll make

5 / 131

and

6 / 131


목차

05-1 동시 출현 단어 분석: Co-occurrence analysis(link)

05-2 동시 출현 네트워크: Co-occurrence network(link)

05-3 단어 간 상관 분석: Phi coefficient(link)

05-4 연이어 사용된 단어쌍 분석: n-gram(link)

7 / 131

05-1 동시 출현 단어 분석:
Co-occurrence analysis

8 / 131
동시 출현 단어 분석(Co-occurrence analysis)
  • 단어 간의 관계를 살펴보는 분석 방법
  • '손-장갑', '머리-모자' 처럼 관계가 있는 단어 파악
  • 단어의 관계를 표현한 의미망(semantic network) 만드는데 활용
9 / 131

기본적인 전처리

# 기생충 기사 댓글 불러오기
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())
10 / 131

토큰화하기

  • 단어가 사용된 맥락을 살펴봐야 하므로 명사, 형용사, 동사 함께 추출
1. 형태소 분석기를 이용해 품사 기준으로 토큰화하기
  • SimplePos22() : 문장의 단어를 22개의 품사로 구분
library(tidytext)
library(KoNLP)
comment_pos <- news_comment %>%
unnest_tokens(input = reply,
output = word,
token = SimplePos22,
drop = F)
comment_pos %>%
select(reply, word)
11 / 131
## # 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
12 / 131
품사 분리하여 행 구성하기
  • 원하는 품사를 추출하기 쉽도록 한 행을 한 품사로 구성하기
  • tidyr::separate_rows():
    • 정규 표현식에 따라 텍스트를 여러 행으로 나누기
    • sep = "[+]": "+"가 등장할 때마다 행을 나눔
# 품사별로 행 분리
library(tidyr)
comment_pos <- comment_pos %>%
separate_rows(word, sep = "[+]")
comment_pos %>%
select(word, reply)
13 / 131
## # 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
14 / 131
3. 품사 추출하기
(1) 명사 추출하기
  • "/n"이 붙어있는 단어 추출
  • 태그 제거: '/로 시작하는 모든 문자' 제거
# 명사 추출하기
noun <- comment_pos %>%
filter(str_detect(word, "/n")) %>%
mutate(word = str_remove(word, "/.*$"))
noun %>%
select(word, reply)
15 / 131
## # 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
16 / 131
# 명사 빈도 구하기
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
17 / 131
(2) 동사, 형용사 추출하기
  • 동사 "/pv", 형용사:"/pa" 붙어있는 단어 추출
  • 단어 뒤에 태그 대신 '다'를 붙여 이해하기 편하게 수정하기
    • ex) "받" → "받다", "멋지" → "멋지다"
# 동사, 형용사 추출하기
pvpa <- comment_pos %>%
filter(str_detect(word, "/pv|/pa")) %>% # "/pv", "/pa" 추출
mutate(word = str_replace(word, "/.*$", "다")) # "/"로 시작 문자를 "다"로 바꾸기
pvpa %>%
select(word, reply)
18 / 131
## # 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
19 / 131
(3) 추출한 데이터 결합하기
  • 추출한 단어 결합하기
  • 이해할 수 있는 두 글자 이상 단어만 남기기
# 품사 결합
comment <- bind_rows(noun, pvpa) %>%
filter(str_count(word) >= 2) %>%
arrange(id)
comment %>%
select(word, reply)
20 / 131
## # 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
21 / 131

[ comment ] 명사, 동사, 형용사를 한 번에 추출하기

  • 명사, 동사, 형용사를 추출해 결합한 후 두 글자 이상만 남기기
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)
22 / 131

단어 동시 출현 빈도 구하기

  • widyr::pairwise_count()
    • item: 단어
    • feature: 텍스트 구분 기준
    • sort = T: 빈도가 높은 순으로 출력 결과 정렬
install.packages("widyr")
library(widyr)
pair <- comment %>%
pairwise_count(item = word,
feature = id,
sort = T)
pair
23 / 131
## # 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
24 / 131
## # 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) "영화-기생충", "기생충-영화"
25 / 131
특정 단어와 자주 함께 사용된 단어 살펴보기
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
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
26 / 131

05-2 동시 출현 네트워크:
Co-occurrence network

27 / 131
동시 출현 네트워크(co-occurrence network)
  • 동시 출현 빈도를 이용해 단어의 관계를 네트워크 형태로 표현
  • 단어들이 어떤 맥락에서 함께 사용되었는지 이해할 수 있다
28 / 131
동시 출현 네트워크(co-occurrence network)
  • 동시 출현 빈도를 이용해 단어의 관계를 네트워크 형태로 표현
  • 단어들이 어떤 맥락에서 함께 사용되었는지 이해할 수 있다

네트워크 그래프 데이터 만들기

  • tidygraph::as_tbl_graph()
  • 동시 출현 빈도 데이터를 '네트워크 그래프 데이터'로 변환하기
    • 단어를 나타내는 노드(node, 꼭짓점)
    • 단어를 연결하는 엣지(edge, 선)

[ comment ] 네트워크가 너무 복잡하지 않도록 25회 이상 사용된 단어 추출해 생성

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
29 / 131

네트워크 그래프 만들기

  • ggraph::ggraph()
install.packages("ggraph")
library(ggraph)
ggraph(graph_comment) +
geom_edge_link() + # 엣지
geom_node_point() + # 노드
geom_node_text(aes(label = name)) # 텍스트

30 / 131

31 / 131

[ comment ] 그래프를 큰 화면에 출력하는 방법

  • Plots 창의 Zoom 아이콘 클릭 이미지 출력 창을 별도로 열어 큰 화면에서 보기
    • 윈도우: windows()
    • macOS: x11()
32 / 131
그래프 다듬기
# 한글 폰트 설정
library(showtext)
font_add_google(name = "Nanum Gothic", family = "nanumgothic")
showtext_auto()
33 / 131
엣지와 노드의 색깔, 크기, 텍스트 위치 수정
  • ggraph(layout = "fr"): 네트워크 형태 결정
    • 난수를 이용해 매번 형태 달라짐 → set.seed()로 난수 고정
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() # 배경 삭제

[ comment ] 노드 텍스트 폰트 geom_node_text()family로 별도 설정. theme()으로 적용 안됨.

34 / 131

35 / 131
네트워크 그래프 함수 만들기
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)
word_network(graph_comment)
36 / 131

유의어 처리하기

  • 유의어(synonyms): 표현은 다르지만 의미가 비슷한 단어
    • ex) "감독", "봉감독", "봉준호감독"
  • 유의어 통일하기: 네트워크 구조가 간결해지고 단어의 관계가 좀 더 분명하게 드러남
37 / 131
# 유의어 처리하기
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)

38 / 131

39 / 131

연결 중심성과 커뮤니티 표현하기

  • 네트워크 그래프는 단어 노드가 많아 어떤 단어 노드 중심으로 해석할지 판단 어려움
  • 연결 중심성과 커뮤니티를 표현하면 단어의 관계를 더 분명하게 파악할 수 있다
40 / 131

연결 중심성과 커뮤니티 표현하기

  • 네트워크 그래프는 단어 노드가 많아 어떤 단어 노드 중심으로 해석할지 판단 어려움
  • 연결 중심성과 커뮤니티를 표현하면 단어의 관계를 더 분명하게 파악할 수 있다
연결 중심성(degree centrality)
  • 노드가 다른 노드들과 얼마나 밀접하게 연결되는지 나타낸 값
  • 연결 중심성으로 노드 크기를 조정하면 어떤 단어를 눈여겨봐야 할지 판단하기 쉬워진다

41 / 131

연결 중심성과 커뮤니티 표현하기

  • 네트워크 그래프는 단어 노드가 많아 어떤 단어 노드 중심으로 해석할지 판단 어려움
  • 연결 중심성과 커뮤니티를 표현하면 단어의 관계를 더 분명하게 파악할 수 있다
커뮤니티(community)
  • 단어 간의 관계가 가까워 빈번하게 연결된 노드 집단
  • 노드를 커뮤니티별로 구분 지어 서로 다른 색으로 표현하면 네트워크 구조를 이해하기 쉬워진다

42 / 131
1. 네트워크 그래프 데이터에 연결 중심성, 커뮤니티 변수 추가하기

  • 네트워크 그래프 데이터 만들기: as_tbl_graph()
    • directed = F: 방향성 없도록 설정
    • group_infomap()은 방향성 없는 네트워크 그래프 데이터에서만 커뮤니티를 찾아줌
43 / 131
1. 네트워크 그래프 데이터에 연결 중심성, 커뮤니티 변수 추가하기

  • 네트워크 그래프 데이터 만들기: as_tbl_graph()
    • directed = F: 방향성 없도록 설정
    • group_infomap()은 방향성 없는 네트워크 그래프 데이터에서만 커뮤니티를 찾아줌
  • 연결 중심성 변수 추가하기: centrality_degree()
  • 커뮤니티 변수 추가하기: group_infomap()
    • 커뮤니티가 정수형 숫자이므로 노드가 그라데이션으로 표현됨
    • as.factor(): factor 타입으로 변환해 노드 그룹별로 다른 색으로 표현
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
44 / 131
## # 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
45 / 131

2. 네트워크 그래프에 연결 중심성, 커뮤니티 표현하기

  • geom_node_point(aes())
    • size = centrality: 연결 중심성에 따라 노드 크기 설정
    • color = group: 커뮤니티 별로 노드 색깔 다르게
    • geom_node_point(show.legend = F): 범례 제거
    • scale_size(range = c(5, 15)): 노드 크기 5~15 범위 유지
    • 너무 크거나 작으면 알아보기 불편
46 / 131
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() # 배경 삭제
47 / 131

48 / 131

3. 네트워크의 주요 단어 살펴보기

주요 단어의 커뮤니티 살펴보기
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>
49 / 131

같은 커뮤니티로 분류된 단어 살펴보기

graph_comment %>%
filter(group == 4) %>%
arrange(-centrality) %>%
data.frame()
## name centrality group
## 1 봉준호 62 4
## 2 받다 10 4
## 3 자랑 6 4
## 4 만들다 4 4
50 / 131

연결 중심성이 높은 주요 단어 살펴보기

graph_comment %>%
arrange(-centrality)
## # 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
51 / 131

연결 중심성이 높은 주요 단어 살펴보기

  • 2번 커뮤니티로 분류된 단어
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
52 / 131

4. 주요 단어가 사용된 원문 살펴보기

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
53 / 131
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
54 / 131
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


[ comment ] tidygraph 패키지의 연결 중심성 지표, 커뮤니티 탐지 알고리즘: tidygraph.data-imaginist.com

55 / 131

05-3 단어 간 상관 분석:
Phi coefficient

56 / 131
동시 출현 빈도의 한계
  • 대부분의 단어와 자주 함께 사용되는 단어쌍 다수
    • ex) "영화"-"기생충"
  • 다른 단어에 비해 상대적으로 자주 함께 사용된 단어가 무엇인지 살펴봐야 한다
57 / 131
동시 출현 빈도의 한계
  • 대부분의 단어와 자주 함께 사용되는 단어쌍 다수
    • ex) "영화"-"기생충"
  • 다른 단어에 비해 상대적으로 자주 함께 사용된 단어가 무엇인지 살펴봐야 한다
파이 계수(phi coefficient)
  • 두 단어가 함께 사용되는 경우가 각각 사용되는 경우에 비해 얼마나 많은지 나타낸 지표
  • 상대적으로 관련성이 큰 단어 파악하는데 활용
    • 어떤 단어와 자주 함께 사용되지만 다른 단어와는 자주 함께 사용되지 않는 단어
58 / 131
파이 계수의 의미
  • X, Y 두 단어가 있을 때, 여러 텍스트에서 두 단어의 사용 여부를 놓고 가능한 모든 경우
    • X, Y 모두 있음( a )
    • X, Y 모두 없음( d )
    • X만 있음( b )
    • Y만 있음( c )

59 / 131
파이 계수의 의미
  • X, Y 두 단어가 있을 때, 여러 텍스트에서 두 단어의 사용 여부를 놓고 가능한 모든 경우
    • X, Y 모두 있음( a )
    • X, Y 모두 없음( d )
    • X만 있음( b )
    • Y만 있음( c )


ϕ=adbc(a+b)(c+d)(a+c)(b+d)

60 / 131
파이 계수의 의미
  • -1 ~ +1
    • +1에 가까울수록 두 단어가 자주 함께 사용되어 관련성이 크다는 의미
    • -1에 가까울수록 함께 사용되는 경우가 드물어 관련성이 작다는 의미
61 / 131

파이 계수 구하기

  • widyr::pairwise_cor()
    • item: 단어
    • feature: 텍스트 구분 기준
    • sort = T: 파이 계수 높은순 정렬
word_cors <- comment %>%
add_count(word) %>%
filter(n >= 20) %>%
pairwise_cor(item = word,
feature = id,
sort = T)
word_cors

[ comment ] add_count() 원자료에 빈도 나타낸 변수 추가

62 / 131

파이 계수 구하기

  • widyr::pairwise_cor()
    • item: 단어
    • feature: 텍스트 구분 기준
    • sort = T: 파이 계수 높은순 정렬
word_cors <- comment %>%
add_count(word) %>%
filter(n >= 20) %>%
pairwise_cor(item = word,
feature = id,
sort = T)
word_cors

[ comment ] add_count() 원자료에 빈도 나타낸 변수 추가

## # 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
63 / 131

특정 단어와 관련성이 큰 단어 살펴보기

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
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
64 / 131

파이 계수로 막대 그래프 만들기

1. 관심 단어별로 파이 계수가 큰 단어 추출하기
# 관심 단어 목록 생성
target <- c("대한민국", "역사", "수상소감", "조국", "박근혜", "블랙리스트")
top_cors <- word_cors %>%
filter(item1 %in% target) %>%
group_by(item1) %>%
slice_max(correlation, n = 8)
65 / 131
2. 막대 그래프 만들기
# 그래프 순서 정하기
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"))
66 / 131

67 / 131

파이 계수로 네트워크 그래프 만들기

1. 네트워크 그래프 데이터 만들기. 연결 중심성과 커뮤니티 추가하기
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()))
68 / 131
2. 네트워크 그래프 만들기
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()
69 / 131

70 / 131
동시 출현 빈도, 파이 계수로 만든 네트워크 그래프의 차이점
  • 동시 출현 빈도를 이용한 네트워크 그래프
    • 여러 단어와 자주 함께 사용된 단어쌍 중심으로 네트워크 형성
    • 노드 대부분이 서로 연결되어 구조가 복잡하고 군집이 잘 드러나지 않음
    • 자주 사용된 단어를 파악할 때 활용
71 / 131
동시 출현 빈도, 파이 계수로 만든 네트워크 그래프의 차이점
  • 동시 출현 빈도를 이용한 네트워크 그래프
    • 여러 단어와 자주 함께 사용된 단어쌍 중심으로 네트워크 형성
    • 노드 대부분이 서로 연결되어 구조가 복잡하고 군집이 잘 드러나지 않음
    • 자주 사용된 단어를 파악할 때 활용


  • 파이 계수를 이용한 네트워크 그래프
    • 다른 단어에 비해 상대적으로 자주 함께 사용된 단어쌍 중심으로 네트워크 형성
    • 관련성이 큰 단어끼리만 연결되어 단어 군집이 명확하게 드러남
    • 밀접하게 관련된 단어쌍 파악할 때 활용
72 / 131
동시 출현 빈도, 파이 계수로 만든 네트워크 그래프의 차이점
  • 동시 출현 빈도를 이용한 네트워크 그래프
    • 여러 단어와 자주 함께 사용된 단어쌍 중심으로 네트워크 형성
    • 노드 대부분이 서로 연결되어 구조가 복잡하고 군집이 잘 드러나지 않음
    • 자주 사용된 단어를 파악할 때 활용


  • 파이 계수를 이용한 네트워크 그래프
    • 다른 단어에 비해 상대적으로 자주 함께 사용된 단어쌍 중심으로 네트워크 형성
    • 관련성이 큰 단어끼리만 연결되어 단어 군집이 명확하게 드러남
    • 밀접하게 관련된 단어쌍 파악할 때 활용
73 / 131

05-4
연이어 사용된 단어쌍 분석: n-gram

74 / 131
  • 같은 단어도 함께 사용된 단어에 따라 의미가 달라짐
  • 어떤 단어는 다른 단어와 연결되어 새로운 의미를 만들어냄
    • ex)
      • '사과를 먹다', '사과를 하다'
      • '감을 잡다', '귀가 얇다'
75 / 131
  • 같은 단어도 함께 사용된 단어에 따라 의미가 달라짐
  • 어떤 단어는 다른 단어와 연결되어 새로운 의미를 만들어냄
    • ex)
      • '사과를 먹다', '사과를 하다'
      • '감을 잡다', '귀가 얇다'
  • 동시 출현 빈도와 파이 계수의 한계: 단어가 함께 사용된 횟수만 고려
    • 단어가 연결될 때 생기는 의미 무시
    • 이해하기 어려운 단어쌍 등장
76 / 131
  • 같은 단어도 함께 사용된 단어에 따라 의미가 달라짐
  • 어떤 단어는 다른 단어와 연결되어 새로운 의미를 만들어냄
    • ex)
      • '사과를 먹다', '사과를 하다'
      • '감을 잡다', '귀가 얇다'
  • 동시 출현 빈도와 파이 계수의 한계: 단어가 함께 사용된 횟수만 고려
    • 단어가 연결될 때 생기는 의미 무시
    • 이해하기 어려운 단어쌍 등장


  • 단어가 연결될 때 생기는 의미를 고려하려면 '자주 연이어 사용된 단어'를 살펴봐야 한다
77 / 131

엔그램(n-gram)

  • 연이어 사용된 n개의 단어
    • 두 단어 연속: 바이그램(bigram) 또는 2-gram
    • 세 단어 연속: 트라이그램(trigram) 또는 3-gram

78 / 131

엔그램(n-gram)

  • 연이어 사용된 n개의 단어
    • 두 단어 연속: 바이그램(bigram) 또는 2-gram
    • 세 단어 연속: 트라이그램(trigram) 또는 3-gram

  • 텍스트를 엔그램으로 토큰화하면
    • 단어 앞뒤에 연이어 사용된 단어를 함께 살펴봄: 얼마나 자주 '연이어' 사용된 단어쌍인가?
    • 단어가 연결될 때 생기는 의미와 맥락을 이해할 수 있음
    • 대다수의 텍스트에 사용된 평범한 단어쌍이 아니라 분명한 의미를 드러내는 단어쌍 발견
79 / 131

엔그램으로 토큰화하기

샘플 텍스트로 엔그램 토큰화해보기

  • tidytext::unnest_tokens()
    • token = "ngrams"
    • n: 기준 단어 수
text <- tibble(value = "대한민국은 민주공화국이다. 대한민국의 주권은 국민에게 있고, 모든 권력은 국민으로부터 나온다.")
text
## # A tibble: 1 x 1
## value
## <chr>
## 1 대한민국은 민주공화국이다. 대한민국의 주권은 국민에게 있고, 모든 권력은 국민으로부터 나온다.
80 / 131
# 바이그램 토큰화
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 국민으로부터 나온다
# 트라이그램 토큰화
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 권력은 국민으로부터 나온다
81 / 131
  • 단어 기준 토큰화 = 유니그램(unigram) 토큰화
# 단어 기준 토큰화
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 나온다
# 유니그램 토큰화
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 나온다
82 / 131

기사 댓글로 바이그램 만들기

(1) 명사, 동사, 형용사 추출하기
  • comment_pos 이용: 댓글을 형태소로 토큰화 후 품사별로 행 분리
  • 명사, 동사, 형용사를 추출해 결합한 후 두 글자 이상만 남김
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)
83 / 131

[ comment ] 바이그램으로 토큰화할 때는 형태소 추출 먼저

  • 텍스트 원문을 바이그램으로 토큰화하면 원형은 같지만 표현만 다른 단어들이 개별 단어로 취급됨
    • ex) '하다', '했다', '하며', '하므로'
  • 표현이 아니라 의미 중심으로 분석해야 하므로 형태소를 먼저 추출한 다음 바이그램으로 토큰화해야 함
84 / 131
(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))
85 / 131
(3) 한 댓글이 하나의 행이 되도록 결합하기
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
86 / 131
(3) 한 댓글이 하나의 행이 되도록 결합하기
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
87 / 131
(4) 바이그램으로 토큰화하기
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
88 / 131

연이어 사용된 단어쌍 빈도 구하기

1. 바이그램 분리하기
# 바이그램 분리하기
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
89 / 131
2. 단어쌍 빈도 구하기
# 단어쌍 빈도 구하기
pair_bigram <- bigram_seprated %>%
count(word1, word2, sort = T) %>%
na.omit()
pair_bigram
## # 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


[ comment ] na.omit(): 결측치 행 제거: 한 단어로 된 문장은 바이그램으로 토큰화하면 NA가 됨
    ex) '축하합니다', '멋집니다'

90 / 131
3. 단어쌍 살펴보기
# 동시 출현 단어쌍
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
# 바이그램 단어쌍
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
91 / 131

엔그램으로 네트워크 그래프 만들기

# 네트워크 그래프 데이터 만들기
graph_bigram <- pair_bigram %>%
filter(n >= 8) %>%
as_tbl_graph()
# 네트워크 그래프 만들기
set.seed(1234)
word_network(graph_bigram)

92 / 131

93 / 131
유의어 통일하고 네트워크 그래프 다시 만들기
  • bigram_seprated의 유의어 통일, 같은 단어 연속 단어쌍 제거
  • 단어쌍 빈도 구하고 결측치 제거
# 유의어 처리
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()
94 / 131
# 네트워크 그래프 데이터 만들기
set.seed(1234)
graph_bigram <- pair_bigram %>%
filter(n >= 8) %>%
as_tbl_graph(directed = F) %>%
mutate(centrality = centrality_degree(), # 중심성
group = as.factor(group_infomap())) # 커뮤니티
95 / 131
# 네트워크 그래프 만들기
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() # 배경 삭제
96 / 131

97 / 131
  • 자주 연이어 사용된 단어쌍 중심으로 네트워크 형성
  • 단어의 맥락과 의미를 구체적으로 이해할 수 있음
  • 개별 단어의 빈도는 낮지만 자주 연이어 사용되고 함께 사용할 때 분명한 의미 지니는 단어쌍 발견
    • ex) '이미경-부회장', '조국-가족'

98 / 131
파이 계수, 바이그램 네트워크 그래프의 차이점
  • 파이 계수를 이용한 네트워크 그래프
    • 관련성이 큰 단어쌍 중심으로 네트워크 형성
    • 빈도가 낮아도 관련성이 큰 단어 주로 표현
    • 관련성이 작은 노드들이 연결되지 않음
      • 단어 군집이 명확하게 드러남 but 단어들의 전반적인 관계를 파악하기 어려움
99 / 131
파이 계수, 바이그램 네트워크 그래프의 차이점
  • 파이 계수를 이용한 네트워크 그래프
    • 관련성이 큰 단어쌍 중심으로 네트워크 형성
    • 빈도가 낮아도 관련성이 큰 단어 주로 표현
    • 관련성이 작은 노드들이 연결되지 않음
      • 단어 군집이 명확하게 드러남 but 단어들의 전반적인 관계를 파악하기 어려움
  • 바이그램을 이용한 네트워크 그래프
    • 연이어 자주 사용된 단어쌍 중심으로 표현
    • 관련성이 큰 동시에 자주 사용된 단어 주로 표현
    • 노드가 대부분 연결됨
    • 단어 군집이 덜 명확 but 단어들의 전반적인 관계 파악할 수 있음
100 / 131
파이 계수, 바이그램 네트워크 그래프의 차이점
  • 파이 계수를 이용한 네트워크 그래프
    • 관련성이 큰 단어쌍 중심으로 네트워크 형성
    • 빈도가 낮아도 관련성이 큰 단어 주로 표현
    • 관련성이 작은 노드들이 연결되지 않음
      • 단어 군집이 명확하게 드러남 but 단어들의 전반적인 관계를 파악하기 어려움
  • 바이그램을 이용한 네트워크 그래프
    • 연이어 자주 사용된 단어쌍 중심으로 표현
    • 관련성이 큰 동시에 자주 사용된 단어 주로 표현
    • 노드가 대부분 연결됨
    • 단어 군집이 덜 명확 but 단어들의 전반적인 관계 파악할 수 있음
101 / 131
어떤 방법으로 네트워크 그래프를 만드는 게 좋을까

  • 각 방법의 특징 다르므로 분석 목적에 맞게 선택
  • 세 가지 방법 모두 사용해 분석 결과 비교하면 텍스트를 다각도로 이해할 수 있음


102 / 131
  • 동시 출현 빈도: 자주 사용된 단어 중심으로 단어들의 관계 표현

103 / 131
  • 파이 계수
    • 관련성이 큰 단어쌍 중심으로 표현
    • 단어 군집을 잘 드러내고 싶을 때

104 / 131
  • 엔그램
    • 연이어 사용될 때 의미를 지니는 단어쌍 중심으로 표현
    • 단어들이 전반적으로 어떤 관계를 형성하고 있는지 표현할 때

105 / 131

정리하기

106 / 131

정리하기

1. 동시 출현 단어 분석 - Co-occurrence analysis
# 품사 기준 토큰화
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)
107 / 131

정리하기

1. 동시 출현 단어 분석 - Co-occurrence analysis
# 단어 동시 출현 빈도 구하기
pair <- comment %>%
pairwise_count(item = word,
feature = id,
sort = T)
108 / 131

정리하기

2. 단어 간 상관 분석 - Phi coefficient
# 파이 계수 구하기
word_cors <- comment %>%
add_count(word) %>%
filter(n >= 20) %>%
pairwise_cor(item = word,
feature = id,
sort = T)
109 / 131

정리하기

3. 연이어 사용된 단어쌍 분석 - n-gram
# 텍스트를 한 행으로 구성
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 = " ")
110 / 131

정리하기

3. 연이어 사용된 단어쌍 분석 - n-gram
# 단어쌍 빈도 구하기
pair_bigram <- bigram_seprated %>%
count(word1, word2, sort = T) %>%
na.omit()
111 / 131

정리하기

4. 네트워크 그래프 만들기
# 네트워크 그래프 데이터 만들기
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))
112 / 131

분석 도전

"news_comment_BTS.csv"에는 2020년 9월 21일 방탄소년단이 '빌보드 핫 100 차트' 1위에 오른 소식을 다룬 기사에 달린 댓글이 들어있습니다. "news_comment_BTS.csv"를 이용해 문제를 해결해 보세요.

  • Q1. "news_comment_BTS.csv"를 불러온 다음 행 번호를 나타낸 변수를 추가하고 분석에 적합하게
           전처리하세요.

  • Q2. 댓글에서 명사, 동사, 형용사를 추출하고 '/로 시작하는 모든 문자'를 '다'로 바꾸세요.

  • Q3. 다음 코드를 이용해 유의어를 통일한 다음 한 댓글이 하나의 행이 되도록 단어를 결합하세요.

# 유의어 통일하기
comment <- comment %>%
mutate(word = case_when(str_detect(word, "축하") ~ "축하",
str_detect(word, "방탄") ~ "자랑",
str_detect(word, "대단") ~ "대단",
str_detect(word, "자랑") ~ "자랑",
T ~ word))
113 / 131

분석 도전

"news_comment_BTS.csv"에는 2020년 9월 21일 방탄소년단이 '빌보드 핫 100 차트' 1위에 오른 소식을 다룬 기사에 달린 댓글이 들어있습니다. "news_comment_BTS.csv"를 이용해 문제를 해결해 보세요.

  • Q4. 댓글을 바이그램으로 토큰화한 다음 바이그램 단어쌍을 분리하세요.
  • Q5. 단어쌍 빈도를 구한 다음 네트워크 그래프 데이터를 만드세요.
    • 난수를 고정한 다음 네트워크 그래프 데이터를 만드세요.
    • 빈도가 3 이상인 단어쌍만 사용하세요.
    • 연결 중심성과 커뮤니티를 나타낸 변수를 추가하세요.
  • Q6. 바이그램을 이용해 네트워크 그래프를 만드세요.
    • 난수를 고정한 다음 네트워크 그래프를 만드세요.
    • 레이아웃을 "fr"로 설정하세요.
    • 연결 중심성에 따라 노드 크기를 정하고, 커뮤니티별로 노드 색깔이 다르게 설정하세요.
    • 노드의 범례를 삭제하세요.
    • 텍스트가 노드 밖에 표시되게 설정하고, 텍스트의 크기를 5로 설정하세요.
114 / 131

Q1. "news_comment_BTS.csv"를 불러온 다음 행 번호를 나타낸 변수를 추가하고 분석에 적합하게
       전처리하세요.

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…
115 / 131

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
116 / 131

Q2. 댓글에서 명사, 동사, 형용사를 추출하고 '/로 시작하는 모든 문자'를 '다'로 바꾸세요.

# 품사 기준 토큰화
library(tidytext)
library(KoNLP)
comment_pos <- news_comment %>%
unnest_tokens(input = reply,
output = word,
token = SimplePos22,
drop = F)
117 / 131
# 한 행이 한 품사를 구성하도록 분리
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
118 / 131
# 명사, 동사, 형용사 추출
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)
119 / 131
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
120 / 131

Q3. 다음 코드를 이용해 유의어를 통일한 다음 한 댓글이 하나의 행이 되도록 단어를 결합하세요.

# 유의어 통일하기
comment <- comment %>%
mutate(word = case_when(str_detect(word, "축하") ~ "축하",
str_detect(word, "방탄") ~ "자랑",
str_detect(word, "대단") ~ "대단",
str_detect(word, "자랑") ~ "자랑",
T ~ word))
121 / 131
# 단어를 댓글별 한 행으로 결합
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
122 / 131

Q4. 댓글을 바이그램으로 토큰화한 다음 바이그램 단어쌍을 분리하세요.

# 바이그램 토큰화
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
123 / 131
# 바이그램 단어쌍 분리
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
124 / 131
  • Q5. 단어쌍 빈도를 구한 다음 네트워크 그래프 데이터를 만드세요.
    • 난수를 고정한 다음 네트워크 그래프 데이터를 만드세요.
    • 빈도가 3 이상인 단어쌍만 사용하세요.
    • 연결 중심성과 커뮤니티를 나타낸 변수를 추가하세요.
# 단어쌍 빈도 구하기
pair_bigram <- bigram_seprated %>%
count(word1, word2, sort = T) %>%
na.omit()
pair_bigram
## # 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
125 / 131
# 네트워크 그래프 데이터 만들기
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
126 / 131
  • Q6. 바이그램을 이용해 네트워크 그래프를 만드세요.
    • 난수를 고정한 다음 네트워크 그래프를 만드세요.
    • 레이아웃을 "fr"로 설정하세요.
    • 연결 중심성에 따라 노드 크기를 정하고, 커뮤니티별로 노드 색깔이 다르게 설정하세요.
    • 노드의 범례를 삭제하세요.
    • 텍스트가 노드 밖에 표시되게 설정하고, 텍스트의 크기를 5로 설정하세요.
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()
127 / 131

128 / 131
# 그래프 꾸미기
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() # 배경 삭제
129 / 131

130 / 131

131 / 131
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
oTile View: Overview of Slides
Esc Back to slideshow