텍스트 마이닝 분석 단어 빈도 및 형태소 분석기, 비교분석

   텍스트 마이닝은  현업에서 은근히 많이 사용하는 기능이다.   즉,  텍스트 데이터를 처리를 엑셀로 하기 어렵고,  응용 해야 할 문제 들이 많이 있다.    아래의 내용은  " 쉽게 배우는 R 텍스트 마이닝"을 Base로  분석 하였다.  

  텍스트 마이닝의 기초적인 부분은 아주 짧고 간단하게 설명 하겠다. 

텍스트 마이닝은  어렵지는 않다.  최소한  내가 본 책은 아주 쉽게 설명이 되어 있다.


만약에 R4.2버전에서  KoNLP가 설치 되어 있지 않았다면 아래의 패이지를 참고해서 설치 한다. 


https://rdmkyg.blogspot.com/2022/06/r-42-windows-rjava-kolnp.html

  

텍스트 전처리 및 데이터 로딩

  텍스트 전처리 하기  

    텍스트를 분석 하기 전에 불필요한 요소를 제거하고 다루기 쉬운 형태로 만드는 과정이다. 

 아래는 문제인 대통령 출마  txt 화일이다. 

https://drive.google.com/file/d/1dLtimNwtunsms01wKm0IU7lGAYouufuo/view?usp=sharing

 위의 링크로 들어가서,  파일 복사 후 "speech_moon.txt"로 만든 후 저장 한다. 


# 1장 텍스트 전처리 

# 라이브러리 불러오기      
library(stringr)
library(dplyr)
library(tidytext)  
library(readxl)
library(httr)
library(ggplot2)
library(ggwordcloud)

# 연설문 불러오기 
  setwd("~/Dropbox/01.R분석/01. Source Data/12.textmining/Data")  
  raw_moon  <- readLines("speech_moon.txt", encoding = "UTF-8")
  head(raw_moon)  
## [1] "정권교체 하겠습니다!"                          
## [2] "  정치교체 하겠습니다!"           
## [3] "  시대교체 하겠습니다!"                                       
## [4] "  "                                                        
## [5] "  ‘불비불명(不飛不鳴)’이라는 고사가 있습니다. 남쪽 언덕 나뭇가지에 앉아, 3년 동안 도 울지도 않는 새. 그러나 그 새는 한번 날면 하늘 끝까지 날고, 한번 울면 천지를 뒤흔듭니다."
## [6] ""

setwd() 파일이 있는 위치의 패스를 지정해 주는 것이고,  readLines는  텍스트 파일을 읽어 오는 것이다. 

아래의 순서에 따라서   문자를 한글로 하고,  연설문 공백을 제거 하는 작업을 할 것이다. 

한글로 된 문자만 전처리(불필요한 문자 제거)

# 연설문에서 불필요한 문자 제거 하기
  moon <-    raw_moon %>% 
       str_replace_all("[^가-힝]", replacement = " ")
   head(moon, 10) 
##  [1] "정권교체 하겠습니다 "                        
##  [2] "  정치교체 하겠습니다 "      
##  [3] "  시대교체 하겠습니다 "               
##  [4] "  "                     
##  [5] "   불비불명       이라는 고사가 있습니다  남쪽 언덕 나뭇가지에 앉아   년 동안 날지도 울지도 않는 새  그러나 그 새는 한번 날면 하늘 끝까지 날고  한번 울면 천지를 뒤흔듭니다 "   
##  [6] ""                                                         
##  [7] "그 동안 정치와 거리를 둬 왔습니다  그러나 암울한 시대가 저를 정치로 불러냈습니다  더 이상 남쪽 나뭇가지에 머무를 수 없었습니다  이제 저는 국민과 함께 높이 날고 크게 울겠습니다  오늘 저는 제  대 대통령선거 출마를 국민 앞에 엄숙히 선언합니다 "
##  [8] ""           
##  [9] ""                                                  
## [10] " 우리나라 대통령 이 되겠습니다 "


연설문 공백 제거 및 문자열 벡터를 tibble로 만들기 

# 연설문에서 공백 제거 
  moon <- moon %>% 
           str_squish()

# 문자열 벡터를 tibble로 만들기 
   moon <- as_tibble(moon)

공백을 제거 하고,  데이터 형식을 tibble 형식으로 만들어야,  텍스트 분석을 할 수 있다. 
이후로 링크되어 나오는 데이터는 위와 같이 텍스트 전처리 되어 나온 것이라고 보면 된다. 

참고로 연속된 공백 제거는 아래와 같이 할 수 있다.    필요시 사용하면 된다. 
# 연속된 공백 제거 하기 
   txt <- "치킨은   맛있다   정말  맛있다.   "
   str_squish(txt)
## [1] "치킨은 맛있다 정말 맛있다."

 데이터 로딩      

  위의 전처리 된 데이터 기반으로 아래의 데이터를 구글드라이버 URL을  다운 받는다.

library(readxl)
  library(httr)

# 문재인 데이터셋 불러오기       
  url = "https://drive.google.com/u/0/uc?id=1sF8rrdkeOjE4raiaPT_b5pPHfTWgOdjo&export=download"
  GET(url, write_disk(tf <- tempfile(fileext = ".xlsx")))
  moon <- read_excel(tf, 1L)  
# 이재명 데이터셋 불러오기 
  url = "https://drive.google.com/u/0/uc?id=1m1So-x-NYQSBosGoRESVA1roQ2krZ67F&export=download"
  GET(url, write_disk(tf <- tempfile(fileext = ".xlsx")))
  lee <- read_excel(tf, 1L)    
# 윤석열 데이터 셋 불러오기 
  url = "https://drive.google.com/u/0/uc?id=1hfbCQ-MESDY_749OKGMeJJvCNSq5Or19&export=download"
  GET(url, write_disk(tf <- tempfile(fileext = ".xlsx")))
  yoon <- read_excel(tf, 1L)    
# tf와 url 데이터 지우기 
 rm(tf,url) 

 데이터는 준비가 되었다.   이제 부터 데이터 분석을 시작 하자. 



단어 토큰화 및 형태소 분석

단어 토큰화

  텍스트는 단락, 문장, 단어, 형태소 등 다양한 단위로 데이터를 나눌수 있는데,  이것을 토큰화라고 한다.   

이번 내용은  보전 내역,  작업 내역에  유용한, 단어 토큰 화만 하겠다. 

문재인 대통령 연설문의  단어 토큰화 하고,  단어 숫자 많은 순으로 소팅 한 다음,  그래프를 그릴 것이다. 

# 단어토큰화 하고 빈도 구하기 
   word_space <- moon %>% 
                   unnest_tokens(input = value,
                                 output =word,
                                 token ="words")

# 단어 숫자 많은 순으로 소팅        
   word_space <- word_space %>% 
     count(word, sort = T)

   
# 두글자 이상 남기기    
  word_space <-  word_space %>% 
     filter(str_count(word) >1)

ggplot2로 어느 문자가 많이 나왔는지, 그래프를 그릴 것이다. 
# ggplot2  그래프 그리기 
  head(word_space,10) %>% 
    ggplot(aes(x = reorder(word, n), y =n)) +
    geom_col() +
    coord_flip() +
    geom_text(aes(label = n ), hjust = -0.3) +  # 그래프 숫자값 표시 
    labs(title ="문재인 대통령 출마 연설문 단어 빈도",
              x = NULL, y =NULL) +
    theme(title = element_text(size = 12)) # 제목크기

문재인 대통령 출마 연설문 단어빈도


# 워드 클라우드 만들기 
 ggplot(word_space, aes(label = word, size =n)) +
   geom_text_wordcloud(seed = 1004) +
   scale_radius(limits = c(3, NA), # 최소, 최대 단어 빈다
                range = c(3, 30) #  최소 최대 글자 크기
   ) +
  scale_color_gradient(low = "#66aaf2",
                       high= "#004EA1") +
  theme_minimal()

워드 클라우드 문재인 대통령 연설문


지금까지는 아무런 의미가 없다.   그냥 있는 데이터 가지고,  텍스트 마이닝 분석을 한것이다.   단어 별로 나누었으니,  명사나 동사도 같이 나왔는데,  연설문에는 동사가 많이 나온다.  위의 내용을 가지고 연설문의 의미를 알 수 없다. 



형태소 분석 

 R의 한글 자연어 분석 패키지인 KoLNP(Korean Natural Language Processing) 패키지는 한국어를  분석 할 수 있는 총 27개의 함수가 들어 있다.  

이번 형태소 분석기에서 분석 하려고 하는 것은 명사이며,  KoLNLP와 tidytext 패키지와 혼용해서 사용을 한다. 

KoLNP 단독으로 사용하게 되면, list 형태로 데이터가 나오기 때문에 데이터를 정리하기가 조금 난해하다.  

형태소 분석을 위해서는 나오는 데이터를 미리 보고,  명사 기준으로 토큰화를 하고,  명사 빈도를  구하더라도,  필요하지 않은 끊어진 동사 및 부사 들이 존재 한다. 

그래서 아래와 같이 stop_word의 단어를 정해 놓고,  불필요한 데이터는 제거 한다. 


# 명사 기준 토큰화
word_noun <-  lee %>% 
  unnest_tokens(input = value,
                output = word, 
                token = extractNoun)

#명사 빈도 구하기 
word_lee <- word_noun %>% 
  count(word, sort =  T) %>%   # 단어 상위 빈도로 조정
  filter(str_count(word) >1 &
           str_count(word) < 5)

# 필요하다고 생각되지 않는 단어 지우기 
url <-"https://drive.google.com/u/0/uc?id=1hFYZ5p0XfwueqDT1UnyQcaz8-hV85d14&export=download"
GET(url, write_disk(tf <- tempfile(fileext = ".xlsx")))
stop_word <- read_excel(tf, 1L)
stop_word <- unique(stop_word$value)
stop_word  
##  [1] "하게"           "들이"           "나가겠습니"     "되겠습니"      
##  [5] "만들겠습니"     "향상시키겠습니" "하겠습니"       "이루겠습니"    
##  [9] "우리"           "국가"           "나라"           "사람"          
## [13] "정치"           "대통령"         "대한민국"       "있습니"        
## [17] "하지"           "없습니"         "보내주셨습니"   "가지"          
## [21] "있었습니"       "하셨습니"       "하였습니"       "해주시"        
## [25] "처리"           "이것"           "이외"           "했습니"        
## [29] "윤석열은"       "어땠습니"       "썼습니"         "보셨습니"      
## [33] "받았습니"       "물었습니"       "만났습니"       "나섰습니"      
## [37] "겪었습니"       "하기도"         "필수적"         "표현"          
## [41] "하나"           "하려"           "마찬가지"       "정권"          
## [45] "우리나라"       "사람들"         "때문"           "시대"          
## [49] "이재명"         "국민"           "갔습니"         "문재인"        
## [53] "누구"           "로운"
# stop word 제외하는 필터 
word_lee <-  word_lee  %>% 
  filter(!word %in% stop_word)

위의 단어를 제거 하고,  아래와 같이 그래프를 그려보면, 후보자 의도가 적힌 글자를 알 수 있다. 


library(ggplot2)

# 단어 단어 상위 빈도 보기 
ggplot(head(word_lee,20), aes(x = reorder(word,n), y = n)) +
  geom_col() +
  coord_flip() +
  geom_text(aes(label =n), hjust = -0.3) +
  labs( x = NULL) + 
  theme(text = element_text(family = "nanumgothic"))

이재명 후보자 대선 출마선언 문자 분석표



"기회", "경제"의  문구가 가장 많이 나온다.   보다 구체적인 해석은 보는 사람이 판단하면 된다. 


단어 빈도 비교하기 

  단어 빈도를 비교하는데,  오즈비라는 것이 있다.  오즈비는 승률을 계산 할때 주로 사용된다.    아래의 링크를 보면,  참고 할 수 있다. 

https://rdmkyg.blogspot.com/2021/05/2.html


즉 두 후보자 중에 어느 후보자가 어떤 단어를 많이 쓰느냐에 따라서 확률 하고 분포가 바뀌는 것이다.   

 # 이재명 후보와 윤석열 후보의  candidate 필드를 만들어 분리 
  
  lee <- raw_lee %>% 
    as_tibble() %>% 
    mutate(candidate = "lee")

  yoon <- raw_yoon %>% 
    as_tibble() %>% 
    mutate(candidate ="yoon")
  


위는 2개의 파일을 받아서 이재명 후보의 것인지 윤석열 후보의 것인지 구분 하는 것이다. 
그런 다음에 데이터를 합치는 작업을 하고 데이터를 다시 전처리를 하고 토큰화 작업을 한다. 


# 데이터 합치기 
  bind_speeches <- bind_rows(lee, yoon) %>% 
    select(candidate, value)
  
# 기본적인 데이터 전처리 
  speeches <- bind_speeches %>% 
    mutate(value = str_replace_all(value, "[^가-힝]", " "),
           value = str_squish(value))

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

그 이후에,  단어의 자릿수 2~4자리 까지의 명사를 추출 받는다.  데이터를 분석해 보니,  4자
이상은 의미가 있는 단어가 없었다. 
 집단 하위별 단어 빈도 구하기 
  frequency <- speeches %>% 
    count(candidate, word) %>% 
    filter(str_count(word) >1 &
           str_count(word) <5 )
  
  head(frequency)
## # A tibble: 6 × 3
##   candidate word      n
##   <chr>     <chr> <int>
## 1 lee       가능      3
## 2 lee       가치      1
## 3 lee       갈등      2
## 4 lee       감당      1
## 5 lee       감성      1
## 6 lee       감수      1


위에서 정의한 Stop_word를 제외 하고 상위 10개 추출 한다. 
# stop word 제외 하기 
 frequency <- frequency %>% 
                filter(!word %in% stop_word) 

# 상위 10개 추출 
 top10 <- frequency %>% 
   group_by(candidate) %>% 
   slice_max(n, n =10)

  lee_10 <-   top10 %>% 
       filter(candidate == "lee") %>% 
       slice(., 1:10)

  yoon_10 <-   top10 %>% 
       filter(candidate == "yoon") %>% 
       slice(.,1:10)

  top10 <- bind_rows(lee_10, yoon_10)     
그리고,  두 후보가 강조하는 말이 얼마나 겹쳐지는지 아래와 같이 볼 수 있다. 

# 변수의 항목별 그래프 그리기
  top10 %>% 
    ggplot(aes(x = reorder(word,n),
               y =n, 
               fill= candidate)) +
    geom_col() +
    coord_flip()+
    labs( x = NULL) + 
    facet_wrap(~candidate)

두 후보의 강조하는 말 겹침도


  "공정" 말고는 강조하는 말이 겹쳐 지지 않는다. 
 
y축을 scales= "free_y"  즉  y측을 자유스럽게 하면 아래와 같은 그림이 그려 진다. 
 

# 그래프별 y 축 설정 하기     
  top10 %>% 
    ggplot(aes(x= reorder(word,n),
               y = n,
               fill = candidate)) +
    geom_col() +
    coord_flip() +
    labs( x = NULL) + 
    facet_wrap(~ candidate ,
               scales = "free_y"
               )
두 후보간의 비교



오즈비 계산


연설문의 단어의 빈도를 wide form으로 변환  즉,   이재명 후보와  윤석열 후보가 하였던 말을 모두 모은 것이다.  아래의 리스트를 보면  이재명 후보가 말을 했는데,  윤석열 후보가 언급하지 않는 말은  숫자 0으로 되어 있다. 

 frequency_wide <- frequency %>% 
    pivot_wider(names_from =  candidate,
                values_from = n,
                values_fill = list(n = 0))

 frequency_wide  
## # A tibble: 590 × 3
##    word    lee  yoon
##    <chr> <int> <int>
##  1 가능      3     1
##  2 가치      1     7
##  3 갈등      2     0
##  4 감당      1     0
##  5 감성      1     0
##  6 감수      1     0
##  7 감행      1     0
##  8 강국      1     0
##  9 강력      5     0
## 10 강자      1     0
## # … with 580 more rows

연설문의 단어 빈도를 계산 한 다음에 두 후보간 오즈비 변수를 구한다. 

# 연설문의 빈도를 조정 하려면,  
 frequency_wide <-   frequency_wide %>% 
       mutate(ratio_lee = (lee + 1)/sum(lee +1),
              ratio_yoon = (yoon +1)/sum(yoon +1))

 
# 오즈비 변수 추가 하기 
 frequency_wide <- frequency_wide %>% 
   mutate(odd_ratio = ratio_lee/ratio_yoon)

# 오즈비가 큰 변수 대로 추가 하기
 frequency_wide <- frequency_wide %>% 
        arrange(-odd_ratio) 


위와 같이 계산 하면 오즈비가 이재명 후보는 오즈비가 높은 순으로 강조하는 단어가 배열 되고,  윤석열 후보는 낮은 순으로 강조하는 단어가 배열 된다. 

아래는 위에 10개는 이재명 후보,  아래부터 10개는 윤석열 후보의 데이터를 뽑아서,  둘을 바인딩(합친) 것이다. 

# 오즈비가 가장 높거나 가장 낮은 단어 추출 하기 
# 오즈비는 다른 후보자에 비해 자신이 강조하는 것이 표현된다.     
# 전제 데이터 숫자 세기
 n = nrow(frequency_wide)
 
 top10 <- bind_rows(
   
   frequency_wide[1:10,] %>% 
     mutate(candidate = "lee") %>% 
     select(candidate,word, odd_ratio, lee),
   
   frequency_wide[(n-9):n,] %>% 
     mutate(candidate = "yoon") %>% 
     select(candidate,word, odd_ratio, yoon)
 ) 

 
 top10 <-   top10 %>% 
     mutate(n = ifelse(is.na(yoon), lee, yoon )) %>% 
     select(-c(lee,yoon))

즉 아래의 그래프는 다른 후보보다 많이 강조하는 것을 보여 준다.   즉,  위의 그래프와 같이 본인이 많이 언급 한 것을 보여 주지는 않는다. 
   
 top10 %>% 
   ggplot(aes(x = reorder_within(word, n, candidate),
              y = n,
              fill = candidate
              )) +
   geom_col()+
   coord_flip() +
   facet_wrap(~ candidate, scales = "free_y") +
   scale_x_reordered() +
   labs(x = NULL)
다른 후보보다 강조한 단어 표현,  오즈비 계산


 아래를 보면 두 후보 간 공통 적인 생각은 비교적 적다. 

# 공통적인 생각 보기  
 frequency_wide %>% 
   filter(lee >=4 & yoon >=4) %>% 
   arrange(abs(1 -  odd_ratio)) 
## # A tibble: 3 × 6
##   word    lee  yoon ratio_lee ratio_yoon odd_ratio
##   <chr> <int> <int>     <dbl>      <dbl>     <dbl>
## 1 사회      8     6   0.00765    0.00678     1.13 
## 2 공정      5     9   0.00510    0.00968     0.527
## 3 경제     13     5   0.0119     0.00581     2.05

아래는 각 후보들이 강조 하는 말을 분석 한 것이다. 
# 윤석렬의 강조하는 문장 찾기 
 bind_speeches %>% 
   filter(candidate== "yoon") %>% 
   filter(str_detect(value, "자유"))
## # A tibble: 16 × 2
##    candidate value                                                              
##    <chr>     <chr>                                                              
##  1 yoon      "그 상식을 무기로, 무너진 자유민주주의와 법치, 시대와 세대를 관통… 
##  2 yoon      "우리 헌법의 근간인 자유민주주의에서‘자유’를 빼내려 합니다. 민주…  
##  3 yoon      "그렇기 때문에 자유가 빠진 민주주의는 진짜 민주주의가 아니고 독재… 
##  4 yoon      "자유민주주의는 승자를 위한 것이고 그 이외의 사람은 도외시하는 것… 
##  5 yoon      "인간은 본래 모두 평등한 존재입니다. 그래서 누가 누구를 지배할 수 …
##  6 yoon      "그러나 자유민주국가에서는 나의 자유만 소중한 것이 아니라 다른 사… 
##  7 yoon      "존엄한 삶에 필요한 경제적 기초와 교육의 기회가 없다면 자유는 공허…
##  8 yoon      "자유를 지키기 위한 연대와 책임이 중요합니다. "                    
##  9 yoon      "그리고 이는 자유민주주의를 추구하는 국민의 권리입니다."           
## 10 yoon      "국제 사회는 인권과 법치, 자유민주주의 가치를 공유하는 국가들 사이…
## 11 yoon      "혁신은 자유롭고 창의적인 사고, 자율적인 분위기, 공정한 기회와 보… 
## 12 yoon      "광범위한 표현의 자유, 공정과 상식, 법치의 자양분을 먹고 창의와 혁…
## 13 yoon      "국민들이 뻔히 보고 있는 앞에서, 오만하게 법과 상식을 짓밟는 정권… 
## 14 yoon      "공정과 상식을 무너뜨리고 자유와 법치를 부정하는 세력이 더 이상 집…
## 15 yoon      "법과 정의, 자유민주주의 가치를 현실에 구현하는 것이 말처럼 쉬운 … 
## 16 yoon      "그리고, 청년들이 마음껏 뛰는 역동적인 나라, 자유와 창의가 넘치는 …
 # 이재명이 강조하는 문장 찾기 
 bind_speeches %>% 
   filter(candidate== "lee") %>% 
   filter(str_detect(value, "투자"))
## # A tibble: 5 × 2
##   candidate value                                                               
##   <chr>     <chr>                                                               
## 1 lee       투자만 하면 고용, 소득, 소비가 자동으로 늘어 경제가 선순환하던 그런…
## 2 lee       이제는 투자할 돈은 남아돌고 성장해도 고용이 늘지 않는 시대입니다.   
## 3 lee       대공황시대의 뉴딜처럼 대전환의 시대에는 공공이 길을 내고 민간이 투… 
## 4 lee       대대적 인프라 확충과 강력한 산업경제 재편으로 투자기회를 확대하고 … 
## 5 lee       더 많은 문화예술체육 투자로 건강한 국민이 높은 수준의 문화예술을 만…

로그 오즈비

  로그 오즈비는 오즈비에 log를 취한 것으로 1보다 큰 것은 양수가 되고, 작은 것은 음수가 된다.  
아래를 해석 하면, 양수의 값이 클수록,  이재명 후보가 강조한 단어이고,   음수의 값이 클수록 윤석열 후보가 강조한 단어이다.    ※ 오즈비 특성 상 다른 후보보다 강조한 문구가 잡힌다. 

# 로그 오즈비 구하기  로그 오즈비는 1보다 큰 것은 양수가 되고,   작은 것은 음수가 된다.  
 frequency_wide <- frequency_wide %>% 
   mutate(log_odds_ratio = log(odd_ratio))
 
 
# 로그 오즈비를  중요한 단어 비교하기 
 
 top10 <- bind_rows(
   
   frequency_wide[1:10,] %>% 
     mutate(candidate = "lee") %>% 
     select(candidate,word, odd_ratio, lee, log_odds_ratio),
   
   frequency_wide[(n-9):n,] %>% 
     mutate(candidate = "yoon") %>% 
     select(candidate,word, odd_ratio, yoon,log_odds_ratio)
 ) 
 
 
 top10 <-   top10 %>% 
   mutate(n = ifelse(is.na(yoon), lee, yoon )) %>% 
   select(-c(lee,yoon))
 

아래와 같이 각 후보의 생각이 많이 들어간 내용을 비교 분석 할 수 있다. 
 # 로그 오즈비로 막대 그래프 만들기 
  ggplot(top10, aes(x = reorder(word, log_odds_ratio),
                    y = log_odds_ratio,
                    fill =candidate)) +
    geom_col() +
    coord_flip() +
    labs(x = NULL) +
    theme(text = element_text(family =  "nanumgothic"))
로그 오즈비 그래프 두 후보의 강조하는 단어


장기간 동안 두 후보간의 의견을 보았다.   이것은 대선 출마문으로 이루어진 데이터로 언론에서 조회해서 얻은 데이터 이다.    






댓글 없음:

댓글 쓰기

css cheat sheet 클래스 선택자, margin(마진), display , center 조정 간단한 구성 요소

 앞에서는 html의 간단한 sheet를 소개 하였습니다.   html은  주로 골격을 나타나는 것이라, 디자인을 하는데는 css로 하여야 합니다.  아래 코드와 같이 css 관련 하여 매우 간단하게 코딩 하겠습니다.  body 부분의 css 코딩  ...