두 텍스트를 비교 분석하기 위해 합치는 과정이 필요합니다. 각 텍스트를 불러와서 합친 후 여러가지 방법으로 텍스트를 비교하는 방법을 알아보도록 하겠습니다.
목차
Toggle1. 단어 빈도 비교하기
- 데이터 불러오기
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)
- 집단별 단어 빈도 구하기
- 기본적인 전처리 및 토큰화
# 기본적인 전처리
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
- 하위 집단별 단어 빈도 구하기 – 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)
- 자주 사용된 단어 추출하기 – 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)
- 막대 그래프 만들기
- 변수의 항목별로 그래프만들기 – facet_wrap()
library(ggplot2)
ggplot(top10, aes(x = reorder(word, n),
y = n,
fill = president)) +
geom_col() +
coord_flip() +
facet_wrap(~ president)
- 그래프별로 y축 설정하기
ggplot(top10, aes(x = reorder(word, n),
y = n,
fill = president)) +
geom_col() +
coord_flip() +
facet_wrap(~ president, # president별 그래프 생성
scales = "free_y") # y축 통일하지 않음
- 특정 단어 제거하고 막대 그래프 만들기
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")
- 축 정렬하기
- 그래프별로 축 정렬하기 – 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")
- 변수 항목 제거하기 – 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 조건에서 발생할 확률에 비해 얼마나 더 큰지를 나타낸 값.
- 오즈비를 구하면 단어가 두 텍스트 중 어디에 등장할 확률이 높은지, 상대적인 중요도를 알 수 있음.
- 단어의 비중을 나타낸 변수 추가하기
→ 오즈비 = 각 단어의 빈도 / 모든 단어 빈도의 합
frequency_wide <- frequency_wide %>%
mutate(ratio_moon = ((moon + 1)/(sum(moon + 1))), # moon에서 단어의 비중
ratio_park = ((park + 1)/(sum(park + 1)))) # park에서 단어의 비중
frequency_wide
- 오즈비 변수 추가하기
→ 한 텍스트의 단어 비중을 다른 텍스트의 단어 비중으로 나누면 오즈비가 됨.
frequency_wide <- frequency_wide %>%
mutate(odds_ratio = ratio_moon/ratio_park)
# 내림차순
frequency_wide %>%
arrange(-odds_ratio)
# 오름차순
frequency_wide %>%
arrange(odds_ratio)
- 오즈비 간단히 구하기
# 전체
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))
- 막대 그래프 만들기
- 비중이 큰 텍스트를 나나탠 변수 추가하기
top10 <- top10 %>%
mutate(president = ifelse(odds_ratio > 1, "moon", "park"),
n = ifelse(odds_ratio > 1, moon, park))
top10
- 막대 그래프 만들기
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()
- 그래프별로 축 설정하기
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")) # 폰트
- 주요 단어가 사용된 문장 살펴보기
- 원문을 문장 기준으로 토큰화하기
speeches_sentence <- bind_speeches %>%
as_tibble() %>%
unnest_tokens(input = value,
output = sentence,
token = "sentences")
head(speeches_sentence)
tail(speeches_sentence)
- 주요 단어가 사용된 문장 추출하기
speeches_sentence %>%
filter(president == "moon" & str_detect(sentence, "복지국가"))
speeches_sentence %>%
filter(president == "park" & str_detect(sentence, "행복"))
- 중요도가 비슷한 단어 살펴보기
frequency_wide %>%
arrange(abs(1 - odds_ratio)) %>%
head(10)
- 중요도가 비슷하면서 빈도가 높은 단어 추출하기
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
- 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 텍스트 마이닝
“이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.”