본문 바로가기

R/텍스트 마이닝

03. 비교분석 - 실습

"speeches_presidents.csv" : 역대 대통령의 대선 출마 선언문

 

Q1. speeches_presidents.csv 를 불러와 이명박 전 대통령과 노무현 전 대통령의 연설문을 추출하고

     분석에 적합하게 전처리 하여라.

 

Q2. 연설문에서 명사를 추출한 다음 연설문별 단어 빈도를 구해라.

 

Q3. 로그 오즈비를 이용해 두 연설문에서 상대적으로 중요한 단어를 10개씩 추출하세요.

 

Q4. 두 연설문에서 상대적으로 중요한 단어를 나타낸 막대그래프를 만드세요.


연설문 불러오기

두 연설문 데이터를 불러와 tibble 구조로 변환한 다음 어떤 연설문인지 알 수 있게 대통령 이름을 부여하겠다.

 

dplyr 패키지의 mutate() 함수 알아보기

: 데이터 프레임 자료형에 새로운 파생 column을 만드는 함수

 

library(dplyr)
Lee<-raw_speeches %>%
        filter(president=="이명박") %>%   # 이명박 전 대통령
        mutate(president = "lee")   # 대통령 이름 변수 추가

Roh<-raw_speeches %>%
        filter(president=="노무현") %>%  # 노무현 전 대통령
        mutate(president = "roh")   # 대통령 이름 변수 추가

 

데이터 합치기

bind_row() 를 이용해 두 데이터를 행(세로) 방향으로 결합한 다음,

출력한 결과를 보기 편하게 select() 로 변수 순서를 바꾼다.

코드의 출력 결과,  bind_speeches 의 윗부분은 이명박 전 대통령의 연설문, 아랫부분은 노무현 전 대통령의 연설문

  •   select 함수를 사용하여 변수 순서가 바뀌었다.

 

bind_speeches <- bind_rows(Lee, Roh) %>%
   select(president, value)

 

1. 기본적인 전처리 및 토큰화

연설문에서 한글 이외의 문자와 연속된 공백을 제거하고 형태소 분석기를 이용해 명사 기준으로 토큰화

1장과 2장에서는 문자열 벡터를 이용했기 때문 str_replace_all()  str_squish() 를 변수에 바로 적용했지만

여기서는 tibble 구조의 bind_speeches에 들어있는 value 변수를 변환해야 하므로 mutate () 를 이용해야 한다.

 

unnest_tokens() 을 이용해 텍스트를 토큰화 - unnest_tokens() 의 파라미터

  • input : 토큰화할 함수
  • output : 토큰을 담을 변수명
  • token : 텍스트를 나누는 기준 ( sentences : 문장 기준 / words : 띄어쓰기 기준 / characters : 글자 기준 )

- 기본적인 전처리

# 기본적인 전처리
library(stringr)
speeches <- bind_speeches %>%
  mutate(value = str_replace_all(value, "[^가-힣]", " "),
          value = str_squish(value))

 

- 토큰화

unnest_tokens() 의 token에 extractNoun 을 입력하면 다루기 쉬운 tibble 구조로 명사를 출력한다.

 

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

speeches <- speeches %>%
  unnest_tokens(input = value,
                output = word,  # 띄어쓰기 기준으로 토큰화
                token = extractNoun)   # 명사 출력

 

2. 하위 집단별 단어 빈도 구하기 - count ()

speeches 의 president 변수는 "lee" 과 "roh" 으로 구분되어 있다.

두 연설문에 각 어떤 단어가 자주 사용되었는지 알아봐야 하므로 count() 를 이용해

"Lee" 과 "Roh" 에 포함된 단어 빈도를 따로 구해야 한다.

 

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

 

 

자주 사용된 단어 추출하기 - slice_max()

dplyr 패키지의 slice_max() 를 이용하면 텍스트에 자주 사용된 단어를 간편하게 추출할 수 있다.

 

연설문에 가장 많이 사용된 단어 추출하기

slice_max() 를 이용해 각 연설문에 가장 많이 사용된 단어를 10개씩 추출하자.

frequency 를 president 별로 분리한 다음 slice_max() 를 이용해 단어 빈도가 높은 10개의 행을 추출

 

# 연설문에 가장 많이 사용된 단어 추출하기

top10 <- frequency %>%
  group_by(president) %>%  # president별로 분리
  slice_max(n, n = 10)     # 상위 10개 추출

top10

       

     

 

출력 결과에서  A tibble: 25 x 3 을 보면 top10 이 25행으로 구성됨을 알 수 있다.

       두 연설문에서 빈도가 높은 단어를 10개씩 추출했는데 20행이 아니라 25행인 이유는

       빈도가 동점인 행이 전부 추출됐기 때문이다.

 

 

빈도 동점 단어 제외하고 추출하기 - slice_max(with_ties = FALSE)

+ 교재에 with_ties = F 라고 나와있지만 with_ties = FALSE 로 오류해결

 

빈도가 동점인 행을 추출하지 않도록 제외하면 원하는 만큼 단어를 추출할 수 있다.

slice_max()  with_ties = FALSE 를 입력하면 동점이 있을 때 n 에 입력한 만큼만 행을 추출한다.

 

연설문에서 단어 빈도 동점을 제외하고 많이 사용된 단어를 추출

출력 결과에서  A tibble: 20 x 3 을 보면 top10 이 20행으로 구성됨을 알 수 있다.

 

# 빈도 동점 단어 제외하고 추출하기 - slice_max(with_ties = FALSE)

top10 <- frequency %>%
  group_by(president) %>%
  slice_max(n, n = 10, with_ties = FALSE)

 

long form 을 wide form 으로 변환하기

앞에서 만든 frequency 는 president 가 "lee" 인 행과 "roh" 인 행이 세로로 길게 나열된 형태이다.

이렇게 세로로 나열된 형태를 long form 이라고 한다.

상대적으로 많이 사용된 단어를 알아보려면 long form 데이터를 가로로 넓은 형태의 wide form 으로 변형하고, 

단어의 비중을 나타낸 변수를 추가해야 한다.

 

long form 을 wide form 으로 변형하기 - pivot_wider ()

tidyr 패키지의 pivot_wider () 을 이용해 long form 데이터를 가로로 넓은 형태의 wide form 데이터로 변형하겠다.

 

wide form 

  • 단어가 두 연설문에 몇 번씩 사용되었는지 비교하기 쉽다.
  • 변수를 이용해 연산하기 수월하다.

pivot_wider()에는 다음과 같은 파라미터를 입력한다

- names_form : 변수명으로 만들 값이 들어있는 변수.

                   여기서는 president 에 들어있는 "lee","roh" 을 변수명으로 만들어야 하므로 president 를 입력

 

- values_form : 변수에 채워 넣을 값이 들어 있는 변수. 여기서는 변수에 단어 빈도를 채워 넣어야 하므로 n을 입력

 

출력 결과를 보면, 한 단어가 한 행으로 구성되므로 단어가 두 연설문에 몇 번 사용되었는지 쉽게 비교할 수 있다.

 

NA를 0 으로 바꾸기

앞 코드 결과에서 결측치 NA 가 존재.

NA 이면 연산할 수 없으므로 0 으로 변환해야 한다.

pivot_wider()  values_fill 에 list(n=0) 을 입력하면 Na = 0 으로 변환한다.

 

 

연설문 단어 빈도를 wide  form 으로 변환하기

long form 데이터를 wide form 으로 변환하는 방법을 익혔으니,

연설문의 단어 빈도가 들어있는 frequency 를 wide form 으로 변환하자.

 

# 연설문 단어 빈도를 wide form 으로 변환하기
install.packages("tidyr")
library(tidyr)
frequency_wide <- frequency %>%
  pivot_wider(names_from = president,
              values_from = n,
              values_fill = list(n = 0))

 

 

 

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

 

로그 오즈비를 활용해 단어를 비교하는 방법을 알아보자.

어떤 값에 로그를 취하면 1보다 큰 값은 양수, 1보다 자은 값은 음수가 된다.

앞에서 구한 odds_ratio에 로그를 취하면 "lee"에서 비중이 커서 odds_ratio가 1보다 큰 단어는 양수가 된다.

반대로 "roh" 에서 비중이 커서 odds_ratio가 1보다 작은 단어는 음수가 된다.

이처럼 단어 빈도로 로그 오즈비를 구하면 단어가 두 텍스트 중 어디에서 비중이 큰지에 따라 서로 다른 부호를 갖는다.

 

'단어 빈도 로그 오즈비'는 단어 빈도 오즈비에 로그를 취해 구한다.

수식으로 나타내면 다음과 같다

  • n : 각 단어의 빈도
  • total : 전체 단어 빈도

로그 오즈비로 막대 그래프를 만들면 단어가 어느 텍스트에서 중요한가에 따라 반대되는 축 방향으로 표현되어 

텍스트의 차이를 분명하게 드러낼 수 있다.

연설문 분석에 적용하면 "lee" 에서 중요한 단어는 막대가 오른쪽, "roh" 에서 중요한 단어는 막대가 왼쪽

 

Q. 각 단어 빈도수에 1 을 더하는 이유 ?

   어떤 단어가 한 연설문에서 전혀 사용되지 않아 빈도가 0 이면 오즈비가 0 이 되므로

   단어의 비중이 어떤 연설문에서 더 큰지 알 수 없게 된다.

   이런 문제를 피하려면 빈도가 0 보다 큰 값이 되도록 모든 값에 1을 더하면 된다.

 

# 로그 오즈비 변수 추가
frequency_wide <- frequency_wide %>%
  mutate(log_odds_ratio = log(((lee + 1) / (sum(lee + 1))) /
                              ((roh + 1) / (sum(roh + 1)))))

 

 

로그 오즈비를 이용해 중요한 단어 비교하기

로그 오즈비를 이용해 각 연설문에서 상대적으로 중요한 단어를 10개씩 추출하겠다.

우선 group_by()  ifelse() 를 이용해 log_odds_ratio 가 0보다 크면 "lee" 그렇지 않으면 "roh" 을 부여한다.

president 변수를 만들어 항목별로 분리한다.

그런 다음 slice_max()  abs() 를 이용해 log_odds_ratio의 절대값 기준으로 상위 10개 단어를 추출한다.

이렇게 하면 log_odds_ratio 가 "lee" 에서 가장 큰 단어 10개, "roh" 에서 가장 작은 단어 10개를 추출한다.

 

# 로그 오즈비를 이용해 중요한 단어 비교하기
top10 <- frequency_wide %>%
  group_by(president = ifelse(log_odds_ratio > 0, "lee", "roh")) %>%
  slice_max(abs(log_odds_ratio), n = 10, with_ties = FALSE)
 
top10 %>% 
  arrange(-log_odds_ratio) %>% 
  select(word, log_odds_ratio, president)

 

 

막대 그래프 만들기

top10 을 이용해 막대 그래프를 만들자.

각 단어가 어느 연설문에서 중요한지에 따라 서로 다른 축 방향으로 표현되어 텍스트의 차이가 잘 드러난다.

 

  • coord_flip() : x 축과 y 축 바꾸기
  • reorder ( 정렬할 데이터, 정렬기준 ) 
  • fill : 채우는 색깔
  • geom_col( ) : 평균값을 막대 길이로 표현할 때 사용
  • geom_bar( ) : 값의 개수 즉, 빈도를 막대 길이로 표현할 때 사용한다.

 

library(ggplot2)
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"))

 

 

 

 

 


 

"inaugural_address.csv" : 역대 대통령의 취임사

 

 

Q1. 다음 코드를 실행해 "inaugural_address.csv" 를 불러온 다음 분석에 적합하게 전처리 하고

     연설문에서 명사를 추출하시오.

raw_speeches <- read_csv("inaugural_address.csv")

 

Q2. TF_IDF 를 이용해 각 연설문에서 상대적으로 중요한 단어 10개를 추출하시오.

 

Q3. 각 연설문에서 상대적으로 중요한 단어를 나타낸 막대그래프를 만드시오.


TF_IDF - 여러 텍스트의 단어 비교하기

오즈비는 두 조건의 확률을 이용해 계산하므로 세 개 이상의 텍스트를 비교할 때 사용하기에는 적절하지 않다.

텍스트를 둘씩 짝지어 따로 비교할 수도 있지만, 비교할 텍스트가 많으면

계산 절차가 번거롭고 결과를 해석하기 어렵기 때문에 효율적이지 않다.

 

중요한 단어란 무엇일까?

셋 이상의 텍스트를 비교하는 가장 쉬운 방법은 각 텍스트에서 자주 사용된 단어를 알아보는 것이다. 

하지만 앞에서 살펴보았듯이 어디서나 자주 사용되는 흔한 단어는 중요하다고 보기 어렵다.

예를 들어 대부분의 자기소개서에는 '저는' 이라는 단어가 많이 나오지만 이 단어를 중요하다고 할 순 없다.

중요한 단어는 흔하지 않으면서도 특정 텍스트에서는 자주 사용된 단어라고 할 수 있다.

이런 단어는 텍스트가 다른 텍스트와 구별되는 특징, 즉 개성을 드러낸다.

예를들어, 어떤 자기소개서에 '스카이다이빙' 이라는 흔하지 않은 단어가 여러 번 사용되었다면 

이 단어는 글쓴이의 개성을 잘 드러내므로 중요하다고 볼 수 있다.

 

TF-IDF 의 의미 알아보기

TF-IDF ( Term Frequency - Inverse Document Frequency )

: 어떤 단어가 흔하지 않으면서도 특정 텍스트에서 자주 사용된 정도를 나타낸 지표  ( 단어 빈도- 역문서 빈도 )

 

TF-IDF 를 이용하면 텍스트의 개성을 드러내는 주요 단어를 찾을 수 있다.

계산 과정을 살펴보면서 TF-IDF 의 의미를 알아보자.

 

TF

TF ( Term Frequency ) : 단어가 특정 텍스트에 사용된 횟수 , ' 단어빈도' 를 의미

 

DF 와 IDF 

DF ( Document Frequency ) : 단어가 사용된 텍스트 수 , '문서 빈도' 를 의미

                                      : DF 가 클수록 여러 문서에 흔하게 사용된 일반적인 단어라고 할 수 있다.

IDF ( Inverse Document Frequency ) : 전체 문서수(N) 에서 DF가 차지하는 비중을 구하고,

                                                   그 값의 역수에 로그를 취한 값 ( '역문서 빈도' )

IDF 는 DF의 역수이므로 DF가 클수록 작아지고, DF가 작을수록 커진다.

  • IDF 가 클수록 드물게 사용되는 특이한 단어
  • IDF 가 작을수록 흔하게 사용되는 일반적인 단어

TF - IDF

TF - IDF : TF (단어빈도) 와 IDF (역 문서빈도) 를 곱한 값

  • TF - IDF 는 어떤 단어가 분석 대상이 되는 텍스트 내에서 많이 사용될수록 커지고 (TF) 
  • 동시에 해당 단어가 사용된 텍스트가 드믈수록 커지는 (IDF) 의 특성을 지님

 

즉, 흔하지 않은 단어인데 특정 텍스트에서 자주 사용될수록 큰 값을 지님

그러므로 각 텍스트에서 TF-IDF 가 큰 단어를 보면 다른 텍스트와 구별되는 특징을 알 수 있다.

 

TF - IDF 구하기

TF-IDF 를 이용하여 텍스트를 분석해보자.   

 

1. 단어 빈도 구하기

역대 대통령의 취임사를 담은 inaugural_address.csv 를 불러와 기본적인 전처리를 한 다음

명사를 추출해 단어빈도를 구하겠다.

CSV 파일을 불러올 때는 readr 패키지 read_csv() 을 이용한다.

read_csv()는 데이터를 다루기 편한 tibble 구조로 만들어주고, 데이터를 불러오는 속도도 read.csv() 보다 빠르다.

 

raw_speeches <- read_csv("C://WORK/R/inaugural_address.csv")

  •  inaugural_address.csv 데이터에는 4명의 대통령의 취임사가 들어있다.

필요한 라이브러리들

install.packages("readr")
library(readr)
library(dplyr)
library(stringr)
library(tidytext)
library(KoNLP)
library(ggplot2)

 

- 기본적인 전처리

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

 

- 토큰화

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

- 단어 빈도 구하기

  • count() 를 이용하면 하위 집단별 빈도를 구할 수 있다. 

: count() 에 집단을 구성하는 두 변수를 순서대로 입력하면 데이터를 첫 번째 변수의 항목별로 나눈 뒤 ,

  다시 두 번째 변수의 항목별로 나누어 하위 집단별 빈도를 구한다.

 

# 단어 빈도 구하기
frequency <- speeches %>%
  count(president, word) %>%
   filter(str_count(word) > 1)  # 두 글자 이상의 단어

 

2. TF-IDF 구하기

tidytext 패키지 bind_tf_idf() 를 이용하면 TF_IDF 를 구할 수 있다.

bind_tf_idf () 의 파라미터

  • term : 단어
  • document : 텍스트 구분 기준
  • n : 단어 빈도

frequency 를 bind_tf_idf() 에 적용해 TF-IDF 를 구한 다음, tf_idf 가 높은 순으로 정렬

 

출력 결과를 보면 tf, idf, tf_idf 가 추가되었음을 알 수 있다. 

 

# TF-IDF
frequency <- frequency %>%
  bind_tf_idf(term = word,           # 단어
              document = president,  # 텍스트 구분 변수
              n = n) %>%             # 단어 빈도
  arrange(-tf_idf)   # 내림차순 정렬

 

TF-IDF 가 높은 단어 살펴보기 

TF_IDF 를 이용하면 텍스트의 특징을 드러내는 중요한 단어가 무엇인지 파악할 수 있다.

tf_idf 가 높은 단어를 살펴보면 각 대통령이 다른 대통령과 달리 무엇을 강조했는지 알 수 있다.

 

# 상대적으로 중요한 단어 10개씩 추출
frequency %>% filter(president== "문재인")
frequency %>% filter(president== "박근혜")
frequency %>% filter(president== "이명박")
frequency %>% filter(president== "노무현")

 

 

막대 그래프 만들기

각 연설문에서 TF-IDF 가 높은 단어를 추출해 막대 그래프를 만들자.

출력한 그래프를 보면 역대 대통령의 개성을 드러내는 단어를 파악할 수 있다.

 

- factor () : 변수 항목의 levels를 정하면 원하는 순서로 그래프를 나열할 수 있다.

- reorder_within() : 축 순서를 변수의 항목별로 따로 구할 수 있다.

                           ( 전체 빈도를 기준으로 구하는 것이 아니라 변수의 항목별로 구할 수 있다. )

- scale_x_reordered() : 변수 항목 제거

                               ( 예 : reorder_within() 적용 후에 '일자리_moon' 으로 항목이름이 생김 )

                                ( scale_x_reordered() 로 항목 이름 제거 )

- facet_wrap() 의 scales 에 "free" 을 입력하면 x 축과 y축 모두 그래프 별로 정한다.

 

top10 <- frequency %>%
  group_by(president) %>%
  slice_max(tf_idf, n = 10, with_ties = FALSE)

# 그래프 순서 정하기
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) +  # x축과 y축의 크기를 그래프 별로 정함
  scale_x_reordered() +   # 변수 항목 이름 제거
  labs(x = NULL) +  # x 축 이름 삭제
  theme(text = element_text(family = "nanumgothic")) # 폰트

 

 

 

 

 


+ TF-IDF 의 한계와 대안

모든 문서에 사용된 단어는 IDF가 0이 이므로 TF-IDF 도 0이 된다.

따라서 TF-IDF 를 활용하면 어떤 단어가 특정 문서에서 특출나게 많이 사용되더라도

모든 문서에 사용되면 발견할 수 없는 한계가 있다.

'weighted log odds' 를 활용하며 이런 한계를 극복할 수 있다.

'weighted log odds' 는 어떤 단어 등장 확률을 가중치로 이용하기 떄문에, 어떤 단어가 모든 문서에 사용되더라도 특정 문서에 많이 사용되면 발견할 수 있다.

또한, 오즈비와 달리 셋 이상의 문서를 비교할 때도 사용할 수 있는 장점이 있다.

tidylo 패키지를 이용하면 weighted lod odds 를 쉽게 구할 수 있다.