R 단어 비교 분석하는 방법

두 텍스트를 비교 분석하기 위해 합치는 과정이 필요합니다. 각 텍스트를 불러와서 합친 후 여러가지 방법으로 텍스트를 비교하는 방법을 알아보도록 하겠습니다.

1. 단어 빈도 비교하기

  • 데이터 불러오기
library(dplyr)

# 문재인 대통령 연설문 불러오기
raw_moon <- readLines("speech_moon.txt", encoding = "UTF-8")
moon <- raw_moon %>%
  as_tibble() %>%
  mutate(president = "moon")

# 박근혜 대통령 연설문 불러오기
raw_park <- readLines("speech_park.txt", encoding = "UTF-8")
park <- raw_park %>%
  as_tibble() %>%
  mutate(president = "park")
  • 데이터 합치기
bind_speeches <- bind_rows(moon, park) %>%
  select(president, value)
  • 집단별 단어 빈도 구하기
  1. 기본적인 전처리 및 토큰화
# 기본적인 전처리
library(stringr)
speeches <- bind_speeches %>%
  mutate(value = str_replace_all(value, "[^가-힣]", " "),
         value = str_squish(value))

speeches

# 토큰화
library(tidytext)
library(KoNLP)

speeches <- speeches %>%
  unnest_tokens(input = value,
                output = word,
                token = extractNoun)
speeches
  1. 하위 집단별 단어 빈도 구하기 – count()
  • 샘플 텍스트로 작동 원리 알아보기
df <- tibble(class = c("a", "a", "a", "b", "b", "b"),
             sex = c("female", "male", "female", "male", "male", "female"))
df

df %>% count(class, sex)
  • 두 텍스트의 단어 빈도 구하기
frequency <- speeches %>%
  count(president, word) %>%   # 연설문 및 단어별 빈도
  filter(str_count(word) > 1)  # 두 글자 이상 추출

head(frequency)
  1. 자주 사용된 단어 추출하기 – slice_max()
  • 샘플 데이터로 작동 원리 알아보기
    • slice_max() : 값이 큰 상위 n개의 행을 추출해 내림차순으로 정렬하는 함수
    • slice_min() : 값이 작은 상위 n개의 행을 추출해 올림차순으로 정렬하는 함수
df <- tibble(x = c(1:100))
df

df %>% slice_max(x, n = 3)

# 다른 방법들
df %>% 
	arrange(desc(x)) %>% 
	head(5)

df %>% 
	top(x, n=5) %>% 
	arrange(desc(x))

  • 텍스트에 가장 많이 사용된 단어 추출하기
top10 <- frequency %>%
  group_by(president) %>%  # president별로 분리
  slice_max(n, n = 10)     # 상위 10개 추출

top10

top10 %>%
  filter(president == "park") %>%
	print(n = Inf)
  • 빈도 동점 단어 제외하고 추출하기 – slice_max(with_ties = F)
df <- tibble(x = c("A", "B", "C", "D"), y = c(4, 3, 2, 2))

df %>% 
  slice_max(y, n = 3)

df %>% 
  slice_max(y, n = 3, with_ties = F)
top10 <- frequency %>%
  group_by(president) %>%
  slice_max(n, n = 10, with_ties = F)
  • 막대 그래프 만들기
  1. 변수의 항목별로 그래프만들기 – facet_wrap()
library(ggplot2)
ggplot(top10, aes(x = reorder(word, n),
                  y = n,
                  fill = president)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~ president)
  1. 그래프별로 y축 설정하기
ggplot(top10, aes(x = reorder(word, n),
                  y = n,
                  fill = president)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~ president,         # president별 그래프 생성
              scales = "free_y")  # y축 통일하지 않음
  1. 특정 단어 제거하고 막대 그래프 만들기
top10 <- frequency %>%
  filter(word != "국민") %>%
  group_by(president) %>%
  slice_max(n, n = 10, with_ties = F)

top10

ggplot(top10, aes(x = reorder(word, n),
                  y = n,
                  fill = president)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~ president, scales = "free_y")
  1. 축 정렬하기
  • 그래프별로 축 정렬하기 – render_within()
    • render_within() : 축 순서를 변수의 항목별로 따로 구하는 기능
    • x : 축
    • by : 정렬 기준
    • within : 그래프를 나누는 기준
    • 축은 word, 정렬 기준은 단어 빈도 n, 그래프를 나누는 기준은 president
ggplot(top10, aes(x = reorder_within(word, n, president),
                  y = n,
                  fill = president)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~ president, scales = "free_y")
  1. 변수 항목 제거하기 – scale_x_reordered()
ggplot(top10, aes(x = reorder_within(word, n, president),
                  y = n,
                  fill = president)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~ president, scales = "free_y") +
  scale_x_reordered() +
  labs(x = NULL) +                                    # x축 삭제
  theme(text = element_text(family = "nanumgothic"))  # 폰트

2. 오즈비-상대적으로 중요한 R 단어 비교하기

  • 텍스트를 비교할 때는 특정 텍스트에는 많이 사용되지만 다른 텍스트에는 적게 사용된 단어, 즉 ‘상대적으로 많이 사용된 단어’를 분석해야 한다.
  • long form을 wide form으로 변환하기 – pivot_wider()
    • 결과 → ‘moon 국민 21’ 이런 형태로 되어 있으면 단어가 다른 범주(moon, park)에서 몇번 사용했는지 파악이 어려움.
    df_long <- frequency %>% group_by(president) %>% slice_max(n, n = 10) %>% filter(word %in% c("국민", "우리", "정치", "행복")) df_long # 결과 moon 국민 21 ... park 국민 72 ...
    • names_from : 변수명으로 만들 값이 들어 있는 변수, 여기서는 president 입력.
    • values_from : 변수에 채워 넣을 값이 들어 있는 변수. 단어 빈도가 중요하여 n 입력.
install.packages("tidyr")
library(tidyr)

df_wide <- df_long %>%
  pivot_wider(names_from = president,
              values_from = n)

df_wide

#결과
국민   21    72
우리   17    10
  • NA를 0으로 바꾸기
df_wide <- df_long %>%
  pivot_wider(names_from = president,
              values_from = n,
              values_fill = list(n = 0))

df_wide
  • 연설문 단어 빈도를 wide form으로 변환하기
frequency_wide <- frequency %>%
  pivot_wider(names_from = president,
              values_from = n,
              values_fill = list(n = 0))

frequency_wide
  • 오즈비 구하기
    • 오즈비는 어떤 사건이 A 조건에서 발생할 확률이 B 조건에서 발생할 확률에 비해 얼마나 더 큰지를 나타낸 값.
    • 오즈비를 구하면 단어가 두 텍스트 중 어디에 등장할 확률이 높은지, 상대적인 중요도를 알 수 있음.
  1. 단어의 비중을 나타낸 변수 추가하기

→ 오즈비 = 각 단어의 빈도 / 모든 단어 빈도의 합

frequency_wide <- frequency_wide %>%
  mutate(ratio_moon = ((moon + 1)/(sum(moon + 1))),  # moon에서 단어의 비중
         ratio_park = ((park + 1)/(sum(park + 1))))  # park에서 단어의 비중

frequency_wide
  1. 오즈비 변수 추가하기

→ 한 텍스트의 단어 비중을 다른 텍스트의 단어 비중으로 나누면 오즈비가 됨.

frequency_wide <- frequency_wide %>%
  mutate(odds_ratio = ratio_moon/ratio_park)

# 내림차순
frequency_wide %>%
  arrange(-odds_ratio)

# 오름차순
frequency_wide %>%
  arrange(odds_ratio)
  1. 오즈비 간단히 구하기
# 전체
frequency_wide <- frequency_wide %>%
  mutate(ratio_moon  = ((moon + 1)/(sum(moon + 1))),
         ratio_park  = ((park + 1)/(sum(park + 1))),
         odds_ratio = ratio_moon/ratio_park)

# 오즈비
frequency_wide <- frequency_wide %>%
  mutate(odds_ratio = ((moon + 1)/(sum(moon + 1)))/
                      ((park + 1)/(sum(park + 1))))
  • 상대적으로 중요한 단어 추출하기
  • 오즈비가 가장 높거나 가장 낮은 단어 추출하기 → filter(rank())
top10 <- frequency_wide %>%
  filter(rank(odds_ratio) <= 10 | rank(-odds_ratio) <= 10)

top10 %>%
  arrange(-odds_ratio) %>%
	print(n=Inf)
  • rank()로 순위 구하기
df <- tibble(x=c(2,5,10))

df %>% mutate(y=rank(x))

df %>% mutate(y=rank(-x))
  • 막대 그래프 만들기
  1. 비중이 큰 텍스트를 나나탠 변수 추가하기
top10 <- top10 %>%
  mutate(president = ifelse(odds_ratio > 1, "moon", "park"),
         n = ifelse(odds_ratio > 1, moon, park))

top10
  1. 막대 그래프 만들기
ggplot(top10, aes(x = reorder_within(word, n, president),
                  y = n,
                  fill = president)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~ president, scales = "free_y") +
  scale_x_reordered()
  1. 그래프별로 축 설정하기
ggplot(top10, aes(x = reorder_within(word, n, president),
                  y = n,
                  fill = president)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~ president, scales = "free") +
  scale_x_reordered() +
  labs(x = NULL) +                                    # x축 삭제
  theme(text = element_text(family = "nanumgothic"))  # 폰트
  • 주요 단어가 사용된 문장 살펴보기
  1. 원문을 문장 기준으로 토큰화하기
speeches_sentence <- bind_speeches %>%
  as_tibble() %>%
  unnest_tokens(input = value,
                output = sentence,
                token = "sentences")

head(speeches_sentence)
tail(speeches_sentence)
  1. 주요 단어가 사용된 문장 추출하기
speeches_sentence %>%
  filter(president == "moon" & str_detect(sentence, "복지국가"))

speeches_sentence %>%
  filter(president == "park" & str_detect(sentence, "행복"))
  1. 중요도가 비슷한 단어 살펴보기
frequency_wide %>%
  arrange(abs(1 - odds_ratio)) %>%
  head(10)
  1. 중요도가 비슷하면서 빈도가 높은 단어 추출하기
frequency_wide %>%
  filter(moon >= 5 & park >= 5) %>%
  arrange(abs(1 - odds_ratio)) %>%
  head(10)

3. 로그 오즈비로 단어 비교하기

  • 로그 오즈비(log odds ratio) → 1보다 큰 단어는 양수, 1보다 작은 단어는 음수
frequency_wide <- frequency_wide %>%
  mutate(log_odds_ratio = log(odds_ratio))
# moon에서 비중이 큰 단어
frequency_wide %>%
  arrange(-log_odds_ratio)

# park에서 비중이 큰 단어
frequency_wide %>%
  arrange(log_odds_ratio)
  • 로그 오즈비 간단히 구하기
frequency_wide <- frequency_wide %>%
  mutate(log_odds_ratio = log(((moon + 1) / (sum(moon + 1))) /
                              ((park + 1) / (sum(park + 1)))))
  • 로그 오즈비를 이용해 중요한 단어 비교하기
top10 <- frequency_wide %>%
  group_by(president = ifelse(log_odds_ratio > 0, "moon", "park")) %>%
  slice_max(abs(log_odds_ratio), n = 10, with_ties = F)

top10 %>% 
  arrange(-log_odds_ratio) %>% 
  select(word, log_odds_ratio, president)
  • 막대 그래프 만들기
ggplot(top10, aes(x = reorder(word, log_odds_ratio),
                  y = log_odds_ratio,
                  fill = president)) +
  geom_col() +
  coord_flip() +
  labs(x = NULL) +
  theme(text = element_text(family = "nanumgothic"))

4.TF-IDF-여러 텍스트의 단어 비교하기

  • 중요한 단어는 흔하지 않으면서도 특정 텍스트에서는 자주 사용된 단어
  • TF-IDF 의미
    • Term Frequency – Inverse Document Frequency
    • TF(Term Frequency) : 단어가 특정 텍스트에 사용된 횟수, 단어 빈도를 의미
    • DF(Document Frequency) : 단어가 사용된 텍스트 수 ‘문서 빈도’, DF가 클수록 여러 문서에 흔하게 사용된 일반적인 단어임.
    • IDF(Inverse Document Frequency) : 전체 문서 수(N)에서 DF가 차지하는 비중을 구하고, 그 값의 역수에 로그를 취한 값. ‘역문서 빈도’
    • IDF는 DF의 역수이므로 DF가 클수록 작아지고, 반대로 DF가 작을수록 커집니다. 따라서 IDF가 클수록 드물게 사용되는 특이한 단어, IDF가 작을수록 흔하게 사용되는 일반적인 단어
    • TF-IDF는 TF(단어 빈도)와 IDF(역 문서 빈도)를 곱한 값. TF-IDF는 어떤 단어가 분석 대상이 되는 텍스트 내에서 많이 사용될수록 커지고(TF), 동시에 해당 단어가 사용된 텍스트가 드물수록 커지는 (IDF) 특성을 지님. ‘흔하지 않은 단어인데 특정 텍스트에서 자주 사용될수록’ 큰 값
# 데이터 불러오기
install.packages("readr")
library(readr)

raw_speeches <- read_csv("speeches_presidents.csv")
raw_speeches
# 기본적인 전처리
speeches <- raw_speeches %>%
  mutate(value = str_replace_all(value, "[^가-힣]", " "),
         value = str_squish(value))

# 토큰화
speeches <- speeches %>%
  unnest_tokens(input = value,
                output = word,
                token = extractNoun)

# 단어 빈도 구하기
frequency <- speeches %>%
  count(president, word) %>%
  filter(str_count(word) > 1)

frequency
  1. TF-IDF 구하기
  • bind_tf_idf()
    • term : 단어
    • document : 텍스트 구분 기준
    • n : 단어 빈도
frequency <- frequency %>%
  bind_tf_idf(term = word,           # 단어
              document = president,  # 텍스트 구분 변수
              n = n) %>%             # 단어 빈도
  arrange(-tf_idf)

frequency
  • TF-IDF가 높은 단어 살펴보기
frequency %>% filter(president == "문재인")

frequency %>% filter(president == "박근혜")

frequency %>% filter(president == "이명박")

frequency %>% filter(president == "노무현")
  • TF-IDF가 낮은 단어 살펴보기
frequency %>%
  filter(president == "문재인") %>%
  arrange(tf_idf)

frequency %>%
  filter(president == "박근혜") %>%
  arrange(tf_idf)
  • 막대 그래프 만들기
# 주요 단어 추출
top10 <- frequency %>%
  group_by(president) %>%
  slice_max(tf_idf, n = 10, with_ties = F)

# 그래프 순서 정하기
top10$president <- factor(top10$president,
                          levels = c("문재인", "박근혜", "이명박", "노무현"))

# 막대 그래프 만들기
ggplot(top10, aes(x = reorder_within(word, tf_idf, president),
                  y = tf_idf,
                  fill = president)) +  
  geom_col(show.legend = F) +
  coord_flip() +
  facet_wrap(~ president, scales = "free", ncol = 2) +
  scale_x_reordered() +
  labs(x = NULL) +
  theme(text = element_text(family = "nanumgothic"))
  • TF-IDF의 한계와 대안
    • 모든 문서에 사용된 단어는 IDF가 0이므로 TF-IDF도 0이 됨. weighted log odds를 활용하면 이러한 한계를 극복할 수 있음.
    • tidylo 패키지 github.com/juliasilge/tidylo

R을 이용한 단어 빈도에 대한 내용은 여기 링크를 참고바랍니다.

형태소 분석기에 대해 궁금하신 분은 여기 링크를 참고바랍니다.

참고 > Do It! 쉽게 배우는 R 텍스트 마이닝

[이지스퍼블리싱]Do it! 쉽게 배우는 R 텍스트 마이닝 - Do it! 시리즈, 이지스퍼블리싱

“이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.”

Back to top