텍스트 마이닝은 현업에서 은근히 많이 사용하는 기능이다. 즉, 텍스트 데이터를 처리를 엑셀로 하기 어렵고, 응용 해야 할 문제 들이 많이 있다. 아래의 내용은 " 쉽게 배우는 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)
# 연속된 공백 제거 하기
txt <- "치킨은 맛있다 정말 맛있다. "
str_squish(txt)
## [1] "치킨은 맛있다 정말 맛있다."
데이터 로딩
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 그래프 그리기
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()
형태소 분석
# 명사 기준 토큰화
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"))
단어 빈도 비교하기
# 이재명 후보와 윤석열 후보의 candidate 필드를 만들어 분리
lee <- raw_lee %>%
as_tibble() %>%
mutate(candidate = "lee")
yoon <- raw_yoon %>%
as_tibble() %>%
mutate(candidate ="yoon")
# 데이터 합치기
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)
집단 하위별 단어 빈도 구하기
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 제외 하기
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 축 설정 하기
top10 %>%
ggplot(aes(x= reorder(word,n),
y = n,
fill = candidate)) +
geom_col() +
coord_flip() +
labs( x = NULL) +
facet_wrap(~ candidate ,
scales = "free_y"
)
오즈비 계산
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)
# 오즈비가 가장 높거나 가장 낮은 단어 추출 하기
# 오즈비는 다른 후보자에 비해 자신이 강조하는 것이 표현된다.
# 전제 데이터 숫자 세기
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 더 많은 문화예술체육 투자로 건강한 국민이 높은 수준의 문화예술을 만…
로그 오즈비
# 로그 오즈비 구하기 로그 오즈비는 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"))
댓글 없음:
댓글 쓰기