지금 우리 학교는은 전세계 Netflilx에서 매우 Hot 하다. 나도 처음 나오자 마자 정주행 했던 것이다. 역시 한국 좀비 영화가 재미가 있다. 이유는 앞의 이야기를 예측을 할 수 없기 때문이다. R에서 텍스트 마이닝에서 감정 분석 하는 것이 있다.
Nexflix 화면 |
사람들의 반응은 어떠 하였는가 ? 나는 이것 보고 나서, 유튜브나, 넷플릿스 다른 것이 재미 없었다.
만약에 R4.2버전에서 KoNLP가 설치 되어 있지 않았다면 아래의 패이지를 참고해서 설치 한다.
https://rdmkyg.blogspot.com/2022/06/r-42-windows-rjava-kolnp.html
이버 댓글을 수집하는 패키지인 "N2H4" 패키지로 Naver News 의 댓글을 데이터화 시켰다. 여기에서 댓글 자체가 영화 평과 같다고 생각 하였다.
일단, 필요한 패키지는 모두 불러오자.
# 라이브러리 불러오기
## 불필요 객체 지우기
rm(list = ls())
## 패키지 불러오기
pkgs <- c(
# Data munipulate packages
"dplyr",
"rio",
"stringr",
"httr",
"ggwordcloud",
"readr",
"readxl",
# crawl Packages
"N2H4",
# text mining packages
"tidytext",
"ggwordcloud",
"KoNLP",
"tidyr",
"textclean",
# graphic packages
"ggplot2"
)
# 패키지 동시에 적용
sapply(pkgs, require, character.only = T)
KoNLP의 사전을 불러온다.
useNIADic()
## Backup was just finished!
## 1213109 words dictionary was built.
아래는 네이버, 뉴스기사의 댓글을 가져온 것이다.
# 지금 우리 학교는 데이터 url
url = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=032&aid=0003126541"
url1 = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=052&aid=0001698211"
url2 = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=437&aid=0000288469"
url3 = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=020&aid=0003408896"
url4 = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=028&aid=0002577686"
url5 = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=052&aid=0001696962"
url6 = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=025&aid=0003170581"
url7= "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=079&aid=0003603645"
url8 = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=366&aid=0000791610"
url9 = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=103&oid=214&aid=0001176617"
# 지금 우리 학교는 이라는 컨텐츠를 하나로 묶는다.
url_vt <- c(url, url1, url2, url3, url4, url5, url6, url7, url8, url9)
rm(url, url1, url2, url3, url4, url5, url6, url7, url8, url9)
# 네이버 뉴스 댓글 가지고 오기
comment_get_fun <- function(x){
comments <- getAllComment(url_vt[x]) %>% select(contents)
return(list(comments))
}
x = 1:length(url_vt)
# 뉴스 댓글 가져오는 함수 가져오기
mylist <- sapply(x, comment_get_fun)
raw_news_comment <- tibble(do.call(rbind, mylist))
head(raw_news_comment)
## # A tibble: 6 × 1
## contents
## <chr>
## 1 "우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재하기까지를 생각…
## 2 "짱개들은 좀비도이제 강시가 원조라고 우기겠군"
## 3 "도대체 댓글 24시간 내내 쓰는 놈들은 누구일까 ㅋㅋㅋㅋㅋㅋㅋㅋ"
## 4 "하 🐕중국인들 넘쳐나네 ㅋㅋㅋㅋ 시진핑에 어머니 받칠 애들 ㅋㅋㅋㅋㅋㅋ"
## 5 "짱OOO들 열심히 퍼다가 리뷰질 하드라.\n같쟎은 그 종자. 짱개"
## 6 "넷플 중국인 15억 뒤지는드라마 한편 만들면 전세계1위가능"
여기에서 뉴스기사를 가져올때, 가장 핵심인 함수는 "N2H4" 패키지의 getAllComment 라는 함수이며, 함수 안에 URL을 삽입하면, 댓글을 가져온다.
아래는 감정사전을 가져오는 것이다.
url = "https://drive.google.com/u/0/uc?id=14t6CqgzfNpJjU45W8HFmLBlHaI_FjOUw&export=download"
GET(url, write_disk(tf <- tempfile(fileext = ".csv")))
dic <- read_csv(tf)
## Rows: 14854 Columns: 2
rm(url,tf)
감정사전은 군산대학교 소프트웨어 융합 공학과에서 만든 'KNU 한국어 감성사전'을 이용하였다. 아래는 긍정 단어와, 부정단어가 있다.
# 긍정단어
dic %>%
filter(polarity == 2) %>%
arrange(word)
## # A tibble: 2,602 × 2
## word polarity
## <chr> <dbl>
## 1 가능성이 늘어나다 2
## 2 가능성이 있다고 2
## 3 가능하다 2
## 4 가볍고 상쾌하다 2
## 5 가볍고 상쾌한 2
## 6 가볍고 시원하게 2
## 7 가볍고 편안하게 2
## 8 가볍고 환하게 2
## 9 가운데에서 뛰어남 2
## 10 가장 거룩한 2
## # … with 2,592 more rows
# 부정 단어
dic %>%
filter(polarity == -2) %>%
arrange(word)
## # A tibble: 4,799 × 2
## word polarity
## <chr> <dbl>
## 1 가난 -2
## 2 가난뱅이 -2
## 3 가난살이 -2
## 4 가난살이하다 -2
## 5 가난설음 -2
## 6 가난에 -2
## 7 가난에 쪼들려서 -2
## 8 가난하게 -2
## 9 가난하고 -2
## 10 가난하고 어렵다 -2
## # … with 4,789 more rows
댓글 데이터 베이스 전처리 한다. 일단 번호를 주고, 데이터 공백을 없애주고, 특수문자와 영문을 뺀 한글 2자리수 이상의 단어(word) 만 추려서, 감성 분석을 하였다.
댓글 데이터 전처리 하기
news_comment <- raw_news_comment %>%
mutate(id = row_number(),
reply = str_squish(contents))
# 데이터 토콘화
word_comment <- news_comment %>%
unnest_tokens(input = reply,
output = word,
token = "words",
drop = F)%>%
filter(str_detect(word, "[가-힣]")) %>%
filter(str_count(word) >= 2)
word_comment %>%
select(word, reply)
## # A tibble: 12,980 × 2
## word reply
## <chr> <chr>
## 1 우리네 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 2 인생사가 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 3 반영되어 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 4 안타깝게 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 5 봤네요 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 6 현재의 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 7 우리가 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 8 존재하기까지를 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 9 생각하여 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## 10 인류가 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재…
## # … with 12,970 more rows
감정 점수를 부여는 댓글의 문장과 감성사전을 join(매칭) 하여, 점수를 부여 하였고, 2점 보다 높으면, 긍정인 글로 정리 하였고, -2점 인 것은 부정으로 정리 하였다.
# 감정점수 부여
word_comment <- word_comment %>%
left_join(dic, by ="word") %>%
mutate(polarity = ifelse(is.na(polarity), 0 , polarity))
# 감정 분류 하기
word_comment <- word_comment %>%
mutate(sentiment = ifelse(polarity == 2, "pos",
ifelse(polarity == -2, "neg", "neu")))
word_comment %>%
count(sentiment)
## # A tibble: 3 × 2
## sentiment n
## <chr> <int>
## 1 neg 139
## 2 neu 12684
## 3 pos 157
중립단어를 제외 하고, 가장 자주 사용되는 단어, 긍정 10개, 부정 10개 씩 정리하여 추출 하여, 그래프를 그렸다.
# 중립 단어를 제외하고, 긍정 단어와 부정 단어중
# 가장 자주 사용된 단어를 10개씩 추출
top10_sentiment <- word_comment %>%
filter(sentiment != "neu") %>%
count(sentiment, word) %>%
group_by(sentiment) %>%
slice_max(n, n =10)
top10_sentiment %>%
ggplot(aes(x = reorder(word,n),
y = n,
fill = sentiment
)) +
geom_col() +
coord_flip() +
geom_text(aes(label = n), hjust = -0.3)+
facet_wrap(~ sentiment, scales = "free") +
scale_y_continuous(expand = expansion(mult = c(0.05, 0.15))) +
labs(x = NULL) +
theme(text = element_text(family="nanumgothic"))
텍스트 마이닝에서 빠질수 없는 wordcloud를 그렸다. 너무, 솔직히, 진짜, 그냥 단어가 들어 갔다. 너무, 솔직히, 진짜의 부사어는 긍정이나, 부정어에서 충분히 같이 쓸 수 있는 단어이다.
# 지금 우리 학교는의 wordcloud 그리기
word_comment %>%
count(word, sort = T) %>%
ggplot(aes(label = word,
size =n,
color = 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()
# 댓글별 감정 점수 구하기
score_comment <- word_comment %>%
group_by(id, reply) %>%
summarise(score = sum(polarity)) %>%
ungroup()
score_comment %>%
select(score, reply)
## # A tibble: 1,283 × 2
## score reply
## <dbl> <chr>
## 1 -1 우리네 인생사가 반영되어 안타깝게 봤네요. 현재의 우리가 존재하기까지를…
## 2 0 짱개들은 좀비도이제 강시가 원조라고 우기겠군
## 3 0 도대체 댓글 24시간 내내 쓰는 놈들은 누구일까 ㅋㅋㅋㅋㅋㅋㅋㅋ
## 4 0 하 🐕중국인들 넘쳐나네 ㅋㅋㅋㅋ 시진핑에 어머니 받칠 애들 ㅋㅋㅋㅋㅋㅋ
## 5 1 짱OOO들 열심히 퍼다가 리뷰질 하드라. 같쟎은 그 종자. 짱개
## 6 0 넷플 중국인 15억 뒤지는드라마 한편 만들면 전세계1위가능
## 7 0 엄청나게 뛰어제끼는 한국좀비. 너무정신사나워..
## 8 0 훠훠훠 역쉬 k방역 그리고 k학폭 입니다.
## 9 -1 왜케 넷플릭스는 잔인한 장면과 피칠갑화면... 엽기적 내용, 싸이코 등장인…
## 10 -1 예고편 보는데 찬혁이 나와서 깜놀..
## # … with 1,273 more rows
# 긍정 댓글
score_comment %>%
select(score, reply) %>%
arrange(-score)
## # A tibble: 1,283 × 2
## score reply
## <dbl> <chr>
## 1 6 "정말 재미있게 봤음. 오징어게임보다 더 재미 있게 봤다. 대한민국 드라마…
## 2 6 "이런거 그만봐요. 마인드컨트롤로 조종 되는거에요. 행복하고 유쾌하고 즐…
## 3 6 "김대중 대통령님 감사합니다 . . 수구꼴통들이 국가 부도 사태에 빠뜨려놓…
## 4 6 "DJ..창조적인 지식은 높은 부가가치를 창출한다. 우리나라는 높은 교육수…
## 5 5 "탑스타 간판없이 무명으로 꾸린 영화가 대박난다는건 좋은 현상이다. 영화…
## 6 5 "예전에 보지않던 좀비물인데 코로나이후 새롭게 보임. 몰입도도 좋고 재미…
## 7 5 "좀비 영화의 새로운 연출은 좋다 . 일부 아닌것도 있지만 좋은것이 더 많…
## 8 5 "재미있게 잘 봤어요 학생들 연기하느라 고생했어요 당신들 고생덕에 즐거…
## 9 5 "한류 재도약의 근간은 민주주의 바탕이 있기때문에 가능하다.제작의 창의…
## 10 4 "썩 재미있던데 웹툰 본 사람들이 일부 비교하면서 어쩌구저쩌구 많이 하더…
## # … with 1,273 more rows
# 부정 댓글
score_comment %>%
select(score, reply) %>%
arrange(score)
## # A tibble: 1,283 × 2
## score reply
## <dbl> <chr>
## 1 -8 좀비 너무보기에 혐오스럽다! ~~~학교라는곳을 좀비랑 연계해 굳이 이런 영…
## 2 -8 넷플 한국드라마 특징이 엽기적으로 잔인하고 더럽게 잔인하고 끝까지 잔인…
## 3 -5 좀비물은 인간형상을 극도로 왜곡하고 인간이 인간을 공격하고 죽이는 것이…
## 4 -5 이런 기사에 안좋은 글을쓰시는 분들은 스스로의 망상에 빠져있는 분들이거…
## 5 -5 ㅋㅋㅋㅋㅋ 오징어게임 지옥 우리학교는 전부 1위 하면서 어째 한국에 대한…
## 6 -5 공포감 확대와 강조는 이해하나 굳이 내장까지 먹고 아비규환속 살아 남은 …
## 7 -5 글쎄 ㅠㅠ 캐릭터들에 좀더 애정을 가지고 만들었으면 좋았겠다는 생각이들…
## 8 -5 반전이 없는게 반전임. 너무 많이, 너무 안타깝게 죽어서. 살아 남은 사람…
## 9 -4 혐오스럽다 딱봐도 불건전
## 10 -4 잼있지만 쓸데없는 로맨스나 신파는 좀 줄이고 8~10부작 정도가 적당. 그리…
## # … with 1,273 more rows
위에는 댓글별로 점수를 구하였고, 물론 점수가 높은 것은 긍정 댓글이고, 점수가 낮은 것은 부정 댓글이다.
# 감정 점수 빈도 구하기
score_comment %>%
count(score) %>%
print(n =Inf)
## # A tibble: 13 × 2
## score n
## <dbl> <int>
## 1 -8 2
## 2 -5 6
## 3 -4 5
## 4 -3 12
## 5 -2 81
## 6 -1 87
## 7 0 923
## 8 1 46
## 9 2 96
## 10 3 9
## 11 4 7
## 12 5 5
## 13 6 4
# 감정 분류하고 막대 그래프 만들기
score_comment <- score_comment %>%
mutate(sentiment = ifelse(score >= 1, "pos",
ifelse(score <=-1, "neg", "neu") ))
frequency_score <- score_comment %>%
count(sentiment) %>%
mutate(ratio = n/sum(n)*100)
frequency_score
## # A tibble: 3 × 3
## sentiment n ratio
## <chr> <int> <dbl>
## 1 neg 193 15.0
## 2 neu 923 71.9
## 3 pos 167 13.0
# 감정분류 막대 그래프 그리기
frequency_score %>%
ggplot(aes(x=sentiment,
y = n,
fill = sentiment)) +
geom_col() +
geom_text(aes(label = n, vjust = -0.3)) +
scale_x_discrete(limits = c("pos", "neu", "neg"))
위와 같이 긍정, 부정, 중립의 갯수를 막대 그래프로 볼 수 있다. 눈으로만 보았을 경우 중립의 비중이 높고, 긍정과, 부정의 비중은 적다. 구체적인 비중을 보고 싶으면 아래와 같이 그래프를 그리면 된다.
댓글 감정비율로 누적 막대 그래프 만들기
# frequency_score 에는 샘플 데이터 contry 처럼 x 축을 구성 할 변수가 없다
# 이때는 임의의 값을 넣은 더비변수를 활용함 (dummy variable)
frequency_score$dummy <- 0
frequency_score
## # A tibble: 3 × 4
## sentiment n ratio dummy
## <chr> <int> <dbl> <dbl>
## 1 neg 193 15.0 0
## 2 neu 923 71.9 0
## 3 pos 167 13.0 0
# 감정을 비율 막대 그래프로 그린다.
frequency_score %>%
ggplot(aes(x = dummy,
y =ratio,
fill= sentiment)) +
geom_col()+
geom_text(aes(label = paste0(round(ratio,1), "%")),
position = position_stack(vjust =0.5)) +
theme(axis.text.x = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank())
감정 변수별 주요 단어 보기
# 로그 오즈비를 이용해 긍정 댓글 과 부정 댓글에 상대적으로 어떤
# 단어가 자주 사용되었는지 알아 보자.
comment <- score_comment %>%
unnest_tokens(input = reply,
output = word,
token = "words",
drop = F) %>%
filter(str_detect(word, "[가-힣]")) %>%
filter(str_count(word) >=2)
# 감정 및 단어별 빈도 구하기ㅣ
frequency_word <- comment %>%
count(sentiment, word, sort = T)
# 긍정 댓글 고빈도 단어
frequency_word %>%
filter(sentiment == "pos")
## # A tibble: 1,988 × 3
## sentiment word n
## <chr> <chr> <int>
## 1 pos 너무 23
## 2 pos 좀비 18
## 3 pos 재미 16
## 4 pos 드라마 11
## 5 pos 재미있게 11
## 6 pos 우리나라 10
## 7 pos 대한민국 9
## 8 pos 그냥 8
## 9 pos 좋고 8
## 10 pos 지금 8
## # … with 1,978 more rows
# 부정 댓글 고빈도 단어
frequency_word %>%
filter(sentiment == "neg")
## # A tibble: 2,400 × 3
## sentiment word n
## <chr> <chr> <int>
## 1 neg 너무 32
## 2 neg 그냥 23
## 3 neg 좀비 21
## 4 neg 진짜 14
## 5 neg 지옥 12
## 6 neg 노잼 10
## 7 neg 그리고 9
## 8 neg 연기 9
## 9 neg 지겹다 9
## 10 neg 하는 9
## # … with 2,390 more rows
부정적인 조건을 두고, "너무"가 포함된 글을 보았다.
score_comment %>%
filter(sentiment == "neg") %>%
filter(str_detect(reply,"너무"))
## # A tibble: 32 × 4
## id reply score sentiment
## <int> <chr> <dbl> <chr>
## 1 63 밖에는 물고뜯고 피철철 난린데 좀비 피해온 교실에있는 … -3 neg
## 2 94 게임하는건 좋지만 학교에서 저리 위험한 행동은 자제해야… -2 neg
## 3 135 이게 왜 열풍인지 모르겠다 애들 연기 너무 못하고 특히 … -3 neg
## 4 170 오징어 만큼은 안됨. 극의 긴장감이 중간 중간 너무 떨어… -3 neg
## 5 196 배우들 연기 너무 못해서 아쉬움 처음에는 일반인 데리고 … -1 neg
## 6 253 쓸데없이 1818을 너무많이함 ㅜㅜ 연기가안됨 -2 neg
## 7 276 너무 지루하고 재미없어 빨리보기로 그냥 넘기고 말았다. … -3 neg
## 8 297 몰입도 떨어지는 지루한 스토리가 곳곳에 있던데 런닝타임… -1 neg
## 9 351 좀비 너무보기에 혐오스럽다! ~~~학교라는곳을 좀비랑 연… -8 neg
## 10 355 잔인하고 악함이 극에달한 저런 컨텐츠가 유행하는 현실이… -1 neg
## # … with 22 more rows
부정에는 잔인함과, 배우 연기 이야기, 그리고 지루한 스토리가 나왔다. 물론 나도 내용이 길어서 조금은 지루 하였다. 그래도 시청자 세계 1등을 현재(2022-02-06) 까지 기록 하고 있다.
감정 변수별 주요 단어를 긍정이 부정 보다 강조를 많이 한것과, 부정이 긍정보다 많이 강조한 로그 오즈비를 계산하기 위해서, 단어별로, wide form 으로 변환 하였다.
# wid_form으로 변환 하여, 로그 오즈비를 구한다.
comment_wide <- frequency_word %>%
filter(sentiment != "neu") %>%
pivot_wider(names_from = sentiment,
values_from = n,
values_fill = list(n = 0) )
comment_wide
## # A tibble: 4,016 × 3
## word neg pos
## <chr> <int> <int>
## 1 너무 32 23
## 2 그냥 23 8
## 3 좀비 21 18
## 4 재미 2 16
## 5 진짜 14 7
## 6 지옥 12 0
## 7 드라마 7 11
## 8 재미있게 0 11
## 9 노잼 10 0
## 10 우리나라 2 10
## # … with 4,006 more rows
로그 오즈비를 구한 다음, 긍정 10개와 부정 10개를 추출 하여, 긍정과 부정을 비교 분석 하였다.
"Do It 쉽게 배우는 R 텍스트 마이닝"의 코드를 많이 참조 하여, 작성 되었다.
그런데 신문기사에 대한 평가는 아래와 같았다.
이것은 댓글을 적지 않아도, 평을 낼 수 있다. 대부분이 좋다고 평하였다.
영화 내용은 그렇다 치더라도, 기사 제목이 아래와 같아서 좋다고 평한 것 같다.
'지금 우리 학교는', 미국 포함 전세계 1위···'오징어게임' 열풍 잇나
댓글 없음:
댓글 쓰기