Description for Sentiment Analysis
감정 분석 (Sentiment Analysis)
pacman::p_load("readr",
"dplyr", "tidyr",
"stringr",
"tidytext",
"textclean",
"KoNLP",
"ggplot2")
# 감정 사전 불러오기
dic <- read.csv(".../knu_sentiment_lexicon.csv")
dic
# A tibble: 14,854 × 2
word polarity
<chr> <dbl>
1 ㅡㅡ -1
2 ㅠㅠ -1
3 ㅠ_ㅠ -1
4 ㅠ -1
5 ㅜㅡ -1
6 ㅜㅜ -1
7 ㅜ_ㅜ -1
8 ㅜ.ㅜ -1
9 ㅜ -1
10 ㅗ -1
# ℹ 14,844 more rows
Result!
군산대학교 소프트웨어융합공학과에서 만든 ’KNU 한국어 감정 사전’을 불러온다. 감정 사전은 감정 단어 word
와 감정의 강도를 숫자로 표현한 polarity
로 구성되어 있다.
KNU 한국어 감정 사전 깃허브 출처는 여기이다.
# A tibble: 4,871 × 2
word polarity
<chr> <dbl>
1 "(-;" 1
2 "(^-^)" 1
3 "(^^)" 1
4 "(^^*" 1
5 "(^_^)" 1
6 "(^o^)" 1
7 "*^^*" 1
8 "/^o^\\" 1
9 "3" 1
10 ":'-(" 1
# ℹ 4,861 more rows
# A tibble: 9,829 × 2
word polarity
<chr> <dbl>
1 (-_-) -1
2 (;_;) -1
3 (T_T) -1
4 (^_^; -1
5 (ㅡㅡ) -1
6 )-: -1
7 -_-^ -1
8 :) -1
9 :-D -1
10 :-P -1
# ℹ 9,819 more rows
# 중성 단어
dic %>%
filter(polarity == 0 ) %>%
arrange(word)
# A tibble: 154 × 2
word polarity
<chr> <dbl>
1 8-) 0
2 :p 0
3 B-) 0
4 가까스로 0
5 가라앉다 0
6 가라앉지 않은 0
7 가르침을 받아 0
8 가리지 않고 0
9 감싸고 달래다 0
10 강구하다 0
# ℹ 144 more rows
Result!
감정 단어를 나타내는 word
는 한 단어로 구성된 단일어, 두 개 이상의 단어가 결합된 복합어, “^^”, “ㅠㅠ” 같은 이모티콘으로 구성되어 있다. polarity
는 -2
에서 +2
까지 5가지 정수로 되어 있다. 예를 들어, “좋은”, “기쁜”과 같은 긍정 단어는 polarity
가 +
, “나쁜”, “슬픈”과 같은 부정 단어는 -
로 표현된다. 긍정과 부정 중 어느 한쪽으로 판단하기 어려운 중성 단어는 0
으로 표현된다.
# A tibble: 2 × 2
word polarity
<chr> <dbl>
1 좋은 2
2 나쁜 -2
# A tibble: 2 × 2
word polarity
<chr> <dbl>
1 슬픈 -2
2 기쁜 2
# A tibble: 2 × 2
word polarity
<chr> <dbl>
1 행복하다 2
2 좌절하다 -2
# 이모티콘
dic %>%
filter(!str_detect(word, "[가-힣]")) %>% # 한글이 아닌 단어만 추출
arrange(word)
# A tibble: 77 × 2
word polarity
<chr> <dbl>
1 (-; 1
2 (-_-) -1
3 (;_;) -1
4 (T_T) -1
5 (^-^) 1
6 (^^) 1
7 (^^* 1
8 (^_^) 1
9 (^_^; -1
10 (^o^) 1
# ℹ 67 more rows
# 감정 사전 단어 개수
dic %>%
# 변수 sentiment에 긍정 단어, 부정 단어, 그리고 중성 단어를 각각 "pos", "neg", "neu"로 입력
mutate(sentiment = ifelse(polarity >= 1, "pos", ifelse(polarity <= -1, "neg", "neu"))) %>%
count(sentiment) # 변수 sentiment의 항목별 개수 확인
# A tibble: 3 × 2
sentiment n
<chr> <int>
1 neg 9829
2 neu 154
3 pos 4871
Result!
감정 사전의 단어는 긍정 단어 4,871개, 부정 단어 9,829개, 그리고 중성 단어 154개로 총 14,854개이다.
단어
로 토큰화해야 한다.# 예시 문장
df <- tibble(sentence = c("디자인 예쁘고 마감도 좋아서 만족스럽다.",
"디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다."))
df
# A tibble: 2 × 1
sentence
<chr>
1 디자인 예쁘고 마감도 좋아서 만족스럽다.
2 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다.
# 1. 단어 기준으로 토큰화
word.token <- df %>%
unnest_tokens(input = sentence, # 토큰화를 수행할 텍스트가 포함된 변수명
output = word, # 출력 변수명
token = "words", # 단어 기준으로 토큰화
drop = F) # 원문 제거 X
word.token %>%
print(n = Inf) # 모든 행 출력
# A tibble: 12 × 2
sentence word
<chr> <chr>
1 디자인 예쁘고 마감도 좋아서 만족스럽다. 디자인
2 디자인 예쁘고 마감도 좋아서 만족스럽다. 예쁘고
3 디자인 예쁘고 마감도 좋아서 만족스럽다. 마감도
4 디자인 예쁘고 마감도 좋아서 만족스럽다. 좋아서
5 디자인 예쁘고 마감도 좋아서 만족스럽다. 만족스럽다
6 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 디자인은
7 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 괜찮다
8 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 그런데
9 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 마감이
10 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 나쁘고
11 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 가격도
12 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 비싸다
# 2. 토큰화한 단어에 감정 점수 부여
word.score <- word.token %>%
left_join(dic, by = "word") %>% # 토큰화 결과와 감정 사전을 변수 word를 기준으로 결합
mutate(polarity = ifelse(is.na(polarity), 0, polarity)) # 결측값 NA를 0으로 대체 -> 단어가 감정 사전에 없으면 결측값이 부여되기 때문
word.score %>%
print(n = Inf) # 모든 행 출력
# A tibble: 12 × 3
sentence word polarity
<chr> <chr> <dbl>
1 디자인 예쁘고 마감도 좋아서 만족스럽다. 디자… 0
2 디자인 예쁘고 마감도 좋아서 만족스럽다. 예쁘… 2
3 디자인 예쁘고 마감도 좋아서 만족스럽다. 마감… 0
4 디자인 예쁘고 마감도 좋아서 만족스럽다. 좋아… 2
5 디자인 예쁘고 마감도 좋아서 만족스럽다. 만족… 2
6 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 디자… 0
7 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 괜찮… 1
8 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 그런… 0
9 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 마감… 0
10 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 나쁘… -2
11 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 가격… 0
12 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. 비싸… -2
Result!
첫 번째 문장의 “예쁘고”, “좋아서”, “만족스럽다”에 긍정 점수가 부여되었으며, 두 번째 문장은 “괜찮다”에 긍점 점수가 부여되었다. 반면, 두 번째 문장의 “나쁘고”, “비싸다”에는 부정 점수가 부여된 것을 알 수 있다.
# 3. 문장별로 감정 점수 합산
score_df <- word.score %>%
group_by(sentence) %>% # 변수 sentence에 대해 그룹화 수행 -> 문장별로 점수를 합산하기 위해
summarise(score = sum(polarity)) # 점수합 계산
score_df
# A tibble: 2 × 2
sentence score
<chr> <dbl>
1 디자인 예쁘고 마감도 좋아서 만족스럽다. 6
2 디자인은 괜찮다. 그런데 마감이 나쁘고 가격도 비싸다. -3
Result!
문장별로 감정 점수를 합산하면, 첫 번째 문장은 세 단어(“예쁘고”, “좋아서”, “만족스럽다”)가 +2
이므로 합산해 6이 되었다. 두 번째 문장은 한 단어(“괜찮다”)가 +1
, 두 단어(“나쁘고”, “비싸다”)는 -2
이므로 합산해 -3이 되었다.
# 데이터 불러오기
raw_news_comment <- read_csv(".../news_comment_parasite.csv")
raw_news_comment
# A tibble: 4,150 × 5
reg_time reply press title url
<dttm> <chr> <chr> <chr> <chr>
1 2020-02-10 16:59:02 "정말 우리 집에 좋은 일이 생… MBC '기… http…
2 2020-02-10 13:32:24 "와 너무 기쁘다! 이 시국에 … SBS [영… http…
3 2020-02-10 12:30:09 "우리나라의 영화감독분들 그… 한겨… ‘기… http…
4 2020-02-10 13:08:22 "봉준호 감독과 우리나라 대한… 한겨… ‘기… http…
5 2020-02-10 16:25:41 "노벨상 탄느낌이네요\n축하축… 한겨… ‘기… http…
6 2020-02-10 12:31:45 "기생충 상 받을때 박수 쳤어… 한겨… ‘기… http…
7 2020-02-10 12:31:33 "대한민국 영화사를 새로 쓰고… 한겨… ‘기… http…
8 2020-02-11 09:20:52 "저런게 아카데미상 받으면 '… 한겨… ‘기… http…
9 2020-02-10 20:53:27 "다시한번 보여주세요 영화관… 한겨… ‘기… http…
10 2020-02-10 20:22:41 "대한민국 BTS와함께 봉준호… 한겨… ‘기… http…
# ℹ 4,140 more rows
id
추가 : 댓글의 내용이 같아도 구별할 수 있도록 고유 번호를 부여한다.
와 같은 html 특수 문자를 포함하고 있어서 내용을 알아보기 힘들다. 이를 해결하기 위해 Package "textclean"
의 함수 replace_html
를 이용해 html 태그를 공백으로 바꾸고, Package "stringr"
의 함수 str_squish
를 이용해 중복 공백을 제거한다.Caution!
감정 사전은 특수 문자, 모음, 자음으로 된 두 글자 미만의 이모티콘도 포함하고 있기 때문에 특수 문자나 두 글자 미만의 단어는 제거하지 않는다.
news_comment <- raw_news_comment %>%
mutate(id = row_number(), # 행 번호(row_number)을 고유 번호로 부여
reply = str_squish(replace_html(reply))) # html 특수 문자 제거
news_comment
# A tibble: 4,150 × 6
reg_time reply press title url id
<dttm> <chr> <chr> <chr> <chr> <int>
1 2020-02-10 16:59:02 정말 우리 집에 좋은 일… MBC '기… http… 1
2 2020-02-10 13:32:24 와 너무 기쁘다! 이 시… SBS [영… http… 2
3 2020-02-10 12:30:09 우리나라의 영화감독분… 한겨… ‘기… http… 3
4 2020-02-10 13:08:22 봉준호 감독과 우리나라… 한겨… ‘기… http… 4
5 2020-02-10 16:25:41 노벨상 탄느낌이네요 축… 한겨… ‘기… http… 5
6 2020-02-10 12:31:45 기생충 상 받을때 박수 … 한겨… ‘기… http… 6
7 2020-02-10 12:31:33 대한민국 영화사를 새로… 한겨… ‘기… http… 7
8 2020-02-11 09:20:52 저런게 아카데미상 받으… 한겨… ‘기… http… 8
9 2020-02-10 20:53:27 다시한번 보여주세요 영… 한겨… ‘기… http… 9
10 2020-02-10 20:22:41 대한민국 BTS와함께 봉… 한겨… ‘기… http… 10
# ℹ 4,140 more rows
glimpse(news_comment) # 데이터 구조 확인
Rows: 4,150
Columns: 6
$ reg_time <dttm> 2020-02-10 16:59:02, 2020-02-10 13:32:24, 2020-02-…
$ reply <chr> "정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼…
$ press <chr> "MBC", "SBS", "한겨레", "한겨레", "한겨레", "한겨레…
$ title <chr> "'기생충' 아카데미 작품상까지 4관왕…영화사 새로 썼…
$ url <chr> "https://news.naver.com/main/read.nhn?mode=LSD&mid=…
$ id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, …
# 단어 기준으로 토큰화
word_comment <- news_comment %>%
unnest_tokens(input = reply, # 토큰화를 수행할 텍스트가 포함된 변수명
output = word, # 출력 변수명
token = "words", # 단어 기준으로 토큰화
drop = F) # 원문 제거 X
word_comment %>%
select(word, reply) # 변수 word와 reply만 선택
# A tibble: 37,718 × 2
word reply
<chr> <chr>
1 정말 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
2 우리 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
3 집에 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
4 좋은 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
5 일이 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
6 생겨 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
7 기쁘고 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
8 행복한 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
9 것처럼 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
10 나의 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 …
# ℹ 37,708 more rows
word_score <- word_comment %>% # 전처리 & 토큰화를 수행한 결과가 저장되어 있는 객체 in 2-2
left_join(dic, by = "word") %>% # 감정 사전과 변수 word를 기준으로 결합
mutate(polarity = ifelse(is.na(polarity), 0, polarity)) # 결측값 NA를 0으로 대체 -> 단어가 감정 사전에 없으면 결측값이 부여되기 때문
word_score %>%
select(word, polarity) # 변수 word와 polarity만 선택
# A tibble: 37,718 × 2
word polarity
<chr> <dbl>
1 정말 0
2 우리 0
3 집에 0
4 좋은 2
5 일이 0
6 생겨 0
7 기쁘고 2
8 행복한 2
9 것처럼 0
10 나의 0
# ℹ 37,708 more rows
word_comment1 <- word_score %>% # 감정 점수가 부여된 객체 in 2-3
# 감정 분류 : 변수 sentiment에 polarity가 2이면 "pos", -2이면 "neg", 그 외는 "neu"로 입력
mutate(sentiment = ifelse(polarity == 2, "pos",
ifelse(polarity == -2, "neg", "neu")))
word_comment1 %>%
count(sentiment) # 변수 sentiment의 항목별 개수 확인
# A tibble: 3 × 2
sentiment n
<chr> <int>
1 neg 285
2 neu 36671
3 pos 762
Result!
부정 단어 285개, 중성 단어 36,671개, 긍정 단어 762개가 포함되어 있다.
# 자주 사용한 단어 추출
top10_sentiment <- word_comment1 %>%
filter(sentiment != "neu") %>% # 감정 범주가 "중립"인 단어 제외
count(sentiment, word) %>% # 변수 sentiment의 항목별 단어 빈도 계산
group_by(sentiment) %>% # 변수 sentiment에 대해 그룹화 수행 -> 각 항목별로 자주 사용한 단어를 추출하기 위해
slice_max(n, n = 10) # 자주 사용한 단어 10개 추출
top10_sentiment
# A tibble: 22 × 3
# Groups: sentiment [2]
sentiment word n
<chr> <chr> <int>
1 neg 소름 56
2 neg 소름이 16
3 neg 아니다 15
4 neg 우울한 9
5 neg 해 8
6 neg 미친 7
7 neg 가난한 5
8 neg 어려운 5
9 neg 힘든 5
10 neg 더러운 4
# ℹ 12 more rows
# 시각화
ggplot(top10_sentiment, # 자주 사용한 단어가 저장되어 있는 객체
aes(x = reorder(word, n), # reorder : 항목별 내림차순 정렬
y = n,
fill = sentiment)) + # 긍정 단어와 부정 단어에 대해 막대 색깔 다르게 표현
geom_col() + # 막대 그래프
coord_flip() + # 막대를 가로로 회전
geom_text(aes(label = n), hjust = -0.3) + # 막대 끝에 빈도 표시
facet_wrap(~ sentiment, # 변수 sentiment의 항목별로 그래프 작성 -> 긍정 단어와 부정 단어 각각의 막대 그래프 작성
scales = "free") + # x축과 y축 통일 X
scale_y_continuous(expand = expansion(mult = c(0.05, 0.15))) + # 막대와 그래프 경계의 간격 조정
labs(x = NULL) # x축 제목 제거 -> 막대를 가로로 회전했기 때문에 y축 제목이 제거됨
Result!
그래프를 보면 긍정 단어는 “대단하다”, “자랑스럽다”, “축하” 등의 빈도가 높으며 이러한 단어들은 아카데미상 수상을 축하하는 댓글들에 사용된 단어라고 예상할 수 있다. 반면, 부정 단어는 “소름”, “아니다”, “우울한” 등의 빈도가 높으며 이러한 단어들은 영화를 보며 생긴 부정적인 감정을 표현한 댓글들에 사용된 단어라고 예상할 수 있다.
Caution!
“소름”, “미친” 등은 부정적인 단어가 아니라 긍정적인 감정을 극적으로 표현하는 단어일 수도 있기 때문에 감정 사전을 수정해서 점수를 부여해야 한다. 이러한 작업은 4. 감정 사전 수정에서 다룬다.
score_comment <- word_score %>% # 감정 점수가 부여된 객체 in 2-3
group_by(id, reply) %>% # 변수 id와 reply에 대해 그룹화 수행 -> 각 댓글별로 점수합을 계산하기 위해
summarise(score = sum(polarity)) %>% # 점수합 계산
ungroup() # 그룹 해제
score_comment %>%
select(score, reply) # 변수 score와 reply만 선택
# A tibble: 4,140 × 2
score reply
<dbl> <chr>
1 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 일…
2 6 와 너무 기쁘다! 이 시국에 정말 내 일같이 기쁘고 감사하다!!! …
3 4 우리나라의 영화감독분들 그리고 앞으로 그 꿈을 그리는 분들에…
4 3 봉준호 감독과 우리나라 대한민국 모두 자랑스럽다. 세계 어디를…
5 0 노벨상 탄느낌이네요 축하축하 합니다
6 0 기생충 상 받을때 박수 쳤어요.감독상도 기대해요.봉준호 감독 …
7 0 대한민국 영화사를 새로 쓰고 계시네요 ㅊㅊㅊ
8 0 저런게 아카데미상 받으면 '태극기 휘날리며'' '광해' '명량''은…
9 0 다시한번 보여주세요 영화관에서 보고싶은디
10 2 대한민국 BTS와함께 봉준호감독님까지 대단하고 한국의 문화에 …
# ℹ 4,130 more rows
# 긍정 댓글
score_comment %>%
select(score, reply) %>% # 변수 score와 reply만 선택
arrange(-score) # 점수를 내림차순 정렬
# A tibble: 4,140 × 2
score reply
<dbl> <chr>
1 11 아니 다른상을 받은것도 충분히 대단하고 굉장하지만 최고의 영…
2 9 봉준호의 위대한 업적은 진보 영화계의 위대한 업적이고 대한민…
3 7 이 수상소식을 듣고 억수로 기뻐하는 가족이 있을것 같다. SNS를…
4 7 감사 감사 감사 수상 소감도 3관왕 답네요
5 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 일…
6 6 와 너무 기쁘다! 이 시국에 정말 내 일같이 기쁘고 감사하다!!! …
7 6 축하 축하 축하 모두들 수고 하셨어요 기생충 화이팅
8 6 축하!!!! 축하!!!!! 오스카의 정복은 무엇보다 시나리오의 힘이…
9 6 조여정 ㆍ예쁜얼굴때문에 연기력을 제대로 평가받지 못해 안타깝…
10 6 좋은 걸 좋다고 말하지 못하는 인간들이 참 불쌍해지네....댓글 …
# ℹ 4,130 more rows
# 부정 댓글
score_comment %>%
select(score, reply) %>% # 변수 score와 reply만 선택
arrange(score) # 점수를 오름차순 정렬
# A tibble: 4,140 × 2
score reply
<dbl> <chr>
1 -7 기생충 영화 한국인 으로써 싫다 대단히 싫다!! 가난한 서민들의…
2 -6 이 페미민국이 잘 되는 게 아주 싫다. 최악의 나쁜일들과 불운, …
3 -5 특정 인물의 성공을 국가의 부흥으로 연관짓는 것은 미개한 발상…
4 -4 좌파들이 나라 망신 다 시킨다..ㅠ 설레발 오지게 치더니..꼴랑 …
5 -4 부패한 386 민주화 세대 정권의 무분별한 포퓰리즘으로 탄생한 …
6 -4 기생충 내용은 좋은데 제목이 그래요. 극 중 송강호가족이 부잣…
7 -4 이런 감독과 이런 배우보고 좌좀 이라고 지1랄하던 그분들 다 어…
8 -4 축하합니다. 근데 현실 세계인 한국에선 그보다 훨씬 나쁜 넘인 …
9 -4 큰일이다....국제적 망신이다...전 세계사람들이 우리나라를 기…
10 -4 더럽고 추잡한 그들만의 리그
# ℹ 4,130 more rows
Result!
감정 점수가 높은 긍정 댓글을 보면 제작진의 수상을 축하하고 대한민국의 위상이 올라간 것을 기뻐하는 긍정적인 내용이 많은 반면, 감정 점수가 낮은 부정 댓글을 보면 감독의 정치 성향이나 영화 내용으로 연상되는 사회 문제를 비판하는 부정적인 내용이 많다는 것을 알 수 있다.
# 1. 감정 점수 빈도 계산
score_comment %>% # 댓글별로 감정 점수를 계산한 객체 in 2-5
count(score) %>% # 점수에 대한 빈도 계산
print(n = Inf) # 모든 행 출력
# A tibble: 17 × 2
score n
<dbl> <int>
1 -7 1
2 -6 1
3 -5 1
4 -4 17
5 -3 35
6 -2 175
7 -1 206
8 0 2897
9 1 222
10 2 432
11 3 57
12 4 71
13 5 7
14 6 14
15 7 2
16 9 1
17 11 1
Result!
댓글의 감정 점수 빈도를 보면, 감정 사전에 없는 단어만 사용해 0점이 부여된 댓글이 2,897개로 가장 많고, 점수가 높거나 낮은 양 극단으로 갈수록 빈도가 감소하는 경향이 있다.
# 2. 감정 분류
score_comment <- score_comment %>% # 댓글별로 감정 점수를 계산한 객체 in 2-5
# 변수 sentiment에 감정 점수가 1 이상이면 "pos", -1이하면 "neg", 그 외는 "neu"로 입력
mutate(sentiment = ifelse(score >= 1, "pos",
ifelse(score <= -1, "neg", "neu")))
score_comment
# A tibble: 4,140 × 4
id reply score sentiment
<int> <chr> <dbl> <chr>
1 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 … 6 pos
2 2 와 너무 기쁘다! 이 시국에 정말 내 일같이 기… 6 pos
3 3 우리나라의 영화감독분들 그리고 앞으로 그 꿈… 4 pos
4 4 봉준호 감독과 우리나라 대한민국 모두 자랑스… 3 pos
5 5 노벨상 탄느낌이네요 축하축하 합니다 0 neu
6 6 기생충 상 받을때 박수 쳤어요.감독상도 기대해… 0 neu
7 7 대한민국 영화사를 새로 쓰고 계시네요 ㅊㅊㅊ 0 neu
8 8 저런게 아카데미상 받으면 '태극기 휘날리며'' … 0 neu
9 9 다시한번 보여주세요 영화관에서 보고싶은디 0 neu
10 10 대한민국 BTS와함께 봉준호감독님까지 대단하고… 2 pos
# ℹ 4,130 more rows
# 3. 댓글의 전반적인 감정 경향
frequency_score <- score_comment %>% # 감정 범주가 할당된 객체
count(sentiment) %>% # 변수 sentiment에 대해 그룹화 수행 -> 각 항목별로 비율을 계산하기 위해
mutate(ratio = n/sum(n)*100) # 비율 계산
frequency_score
# A tibble: 3 × 3
sentiment n ratio
<chr> <int> <dbl>
1 neg 436 10.5
2 neu 2897 70.0
3 pos 807 19.5
Result!
중립 댓글이 70%로 가장 많고, 긍정 댓글은 19.5%, 부정 댓글은 10.5%로 구성되어 있다.
# 시각화 Ver.1 : 막대 그래프
ggplot(frequency_score, 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")) # x축 순서
Result!
그래프를 보면 중립, 긍정, 부정 순으로 댓글이 많음을 한눈에 알 수 있다.
# 시각화 Ver.2 : 누적 막대 그래프
frequency_score$dummy <- 0 # 더미 변수 생성
frequency_score
# A tibble: 3 × 4
sentiment n ratio dummy
<chr> <int> <dbl> <dbl>
1 neg 436 10.5 0
2 neu 2897 70.0 0
3 pos 807 19.5 0
ggplot(frequency_score, 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.title.x = element_blank(), # x축 이름 제거
axis.text.x = element_blank(), # x축 값 제거
axis.ticks.x = element_blank()) # x축 눈금 제거
Result!
누적 막대 그래프는 하나의 막대 위에 여러 범주의 비율을 표현하며, 이를 통해 구성 요소의 비중 차이를 한눈에 파악할 수 있다. 출력 그래프를 보면 막대가 감정 범주별로 누적되어 어떤 감정 범주의 댓글이 많은지 쉽게 알 수 있다.
# 1. 토큰화 & 두 글지 이상 한글 단어만 추출
comment <- score_comment %>% # 댓글별로 감정 점수를 계산 & 감정 범주가 부여된 객체 in 2-6
unnest_tokens(input = reply, # 토큰화를 수행할 텍스트가 포함된 변수명
output = word, # 출력 변수명
token = "words", # 단어 기준으로 토큰화
drop = F) %>% # 원문 제거 X
filter(!str_detect(word, "[^가-힣]") & # 한글만 추출
str_count(word) >= 2) # 두 글자 이상 단어만 추출
comment
# A tibble: 33,445 × 5
id reply score sentiment word
<int> <chr> <dbl> <chr> <chr>
1 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 정말
2 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 우리
3 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 집에
4 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 좋은
5 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 일이
6 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 생겨
7 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 기쁘고
8 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 행복한
9 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 것처럼
10 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 나의
# ℹ 33,435 more rows
Caution!
앞에서 감정 점수를 계산할 때는 감정 사전의 특수 문자, 모음, 자음으로 된 이모티콘도 활용해야 하므로 특수 문자를 제거하고 두 글자 이상의 한글 단어만 남기는 작업을 전처리에서 수행하지 않았다. 여기서는 감정 단어가 아니라 의미를 해석할 수 있는 단어
를 분석하므로 두 글자 이상의 한글 단어만 남겨야 한다.
# 2. 감정 및 단어별 빈도 계산
frequency_word <- comment %>%
count(sentiment, word, sort = T) # sentiment (감정)별 word (단어)의 빈도 계산 / sort = T : 내림차순 정렬
frequency_word
# A tibble: 19,223 × 3
sentiment word n
<chr> <chr> <int>
1 neu 축하합니다 214
2 neu 봉준호 203
3 neu 기생충 164
4 neu 축하드립니다 155
5 neu 정말 146
6 neu 대박 134
7 neu 진짜 121
8 pos 봉준호 106
9 pos 정말 97
10 neu 자랑스럽습니다 96
# ℹ 19,213 more rows
# 긍정 댓글 고빈도 단어
frequency_word %>%
filter(sentiment == "pos")
# A tibble: 5,234 × 3
sentiment word n
<chr> <chr> <int>
1 pos 봉준호 106
2 pos 정말 97
3 pos 대단하다 83
4 pos 진짜 79
5 pos 자랑스럽다 78
6 pos 축하 63
7 pos 대한민국 61
8 pos 영화 58
9 pos 멋지다 55
10 pos 기생충 53
# ℹ 5,224 more rows
# 부정 댓글 고빈도 단어
frequency_word %>%
filter(sentiment == "neg")
# A tibble: 4,080 × 3
sentiment word n
<chr> <chr> <int>
1 neg 소름 49
2 neg 봉준호 47
3 neg 기생충 33
4 neg 이런 33
5 neg 정말 32
6 neg 진짜 26
7 neg 좌빨 21
8 neg 너무 20
9 neg 블랙리스트에 19
10 neg 영화 18
# ℹ 4,070 more rows
Result!
추출한 단어를 보면 긍정 댓글과 부정 댓글의 내용이 어떻게 다른지 알 수 있다. 하지만 단순히 빈도가 높은 단어를 추출했기 때문에, “봉준호”, “기생충” 같은 단어도 추출되었다. 긍정 댓글과 부정 댓글의 차이를 이해하려면 양쪽에서 상대적으로 자주 사용한 단어를 비교해야 한다.
# 1. Wide Format Dataset으로 변환
comment_wide <- frequency_word %>% # 감정 및 단어별 빈도가 저장되어 있는 객체 in 3-1
filter(sentiment != "neu") %>% # 감정 범주가 "중립"인 단어 제외
pivot_wider(names_from = sentiment, # 변수명으로 입력할 값이 들어 있는 변수
values_from = n, # 변수에 채워 넣을 값이 들어 있는 변수
values_fill = list(n = 0))# 결측값 NA를 0으로 대체
comment_wide
# A tibble: 8,380 × 3
word pos neg
<chr> <int> <int>
1 봉준호 106 47
2 정말 97 32
3 대단하다 83 1
4 진짜 79 26
5 자랑스럽다 78 1
6 축하 63 0
7 대한민국 61 4
8 영화 58 18
9 멋지다 55 0
10 기생충 53 33
# ℹ 8,370 more rows
# 2. 로그 오즈비 계산
comment_wide <- comment_wide %>%
mutate(log_odds_ratio = log(((pos + 1) / (sum(pos + 1))) /
((neg + 1) / (sum(neg + 1)))))
comment_wide
# A tibble: 8,380 × 4
word pos neg log_odds_ratio
<chr> <int> <int> <dbl>
1 봉준호 106 47 0.589
2 정말 97 32 0.876
3 대단하다 83 1 3.52
4 진짜 79 26 0.873
5 자랑스럽다 78 1 3.46
6 축하 63 0 3.95
7 대한민국 61 4 2.30
8 영화 58 18 0.920
9 멋지다 55 0 3.81
10 기생충 53 33 0.250
# ℹ 8,370 more rows
top10 <- comment_wide %>% # 로그 오즈비가 저장되어 있는 객체 in 3-2
mutate(sentiment = ifelse(log_odds_ratio > 0, "pos", "neg")) %>% # 변수 sentiment에 로그 오즈비가 양수이면 "pos", 음수이면 "neg" 입력
group_by(sentiment) %>% # 변수 sentiment에 대해 그룹화 -> 각 항목별로 로그 오즈비가 높은 단어를 추출하기 위해
slice_max(abs(log_odds_ratio), n = 10) # 로그 오즈비의 절댓값이 가장 높은 단어 10개 추출
top10 %>%
print(n = Inf) # 모든 행 출력
# A tibble: 30 × 5
# Groups: sentiment [2]
word pos neg log_odds_ratio sentiment
<chr> <int> <int> <dbl> <chr>
1 소름 2 49 -3.03 neg
2 좌빨 1 21 -2.61 neg
3 못한 0 7 -2.29 neg
4 미친 0 7 -2.29 neg
5 좌좀 0 6 -2.16 neg
6 소름이 1 12 -2.08 neg
7 가난한 0 5 -2.00 neg
8 모르는 0 5 -2.00 neg
9 아쉽다 0 5 -2.00 neg
10 닭그네 0 4 -1.82 neg
11 돋았다 0 4 -1.82 neg
12 두고 0 4 -1.82 neg
13 못하고 0 4 -1.82 neg
14 사회적 0 4 -1.82 neg
15 싫다 0 4 -1.82 neg
16 있는데 0 4 -1.82 neg
17 정권이 0 4 -1.82 neg
18 지들이 0 4 -1.82 neg
19 틀딱 0 4 -1.82 neg
20 힘든 0 4 -1.82 neg
21 축하 63 0 3.95 pos
22 멋지다 55 0 3.81 pos
23 대단한 47 0 3.66 pos
24 좋은 42 0 3.55 pos
25 대단하다 83 1 3.52 pos
26 자랑스럽다 78 1 3.46 pos
27 최고 27 0 3.12 pos
28 세계적인 24 0 3.01 pos
29 최고의 23 0 2.97 pos
30 위대한 22 0 2.92 pos
Result!
출력 결과를 보면 30행으로 구성되어 있다는 것을 알 수 있다. 긍정 댓글과 부정 댓글에서 로그 오즈비가 가장 높은 단어를 10개씩 추출했는데 20행이 아니라 30행인 이유는 부정 댓글에서 로그 오즈비가 “-1.82”인 단어가 모두 추출되었기 때문이다. 빈도가 동일하더라도 원하는 개수만큼만 단어를 추출하기 위해서는 옵션 with_ties = F
를 지정하면 된다. 해당 옵션을 지정하면 빈도가 동일한 단어의 경우 원본 데이터의 정렬 순서에 따라 단어를 출력한다.
# 빈도가 동일하더라도 원하는 개수만큼 단어 추출
top10 <- comment_wide %>% # 로그 오즈비가 저장되어 있는 객체 in 3-2
mutate(sentiment = ifelse(log_odds_ratio > 0, "pos", "neg")) %>% # 변수 sentiment에 로그 오즈비가 양수이면 "pos", 음수이면 "neg" 입력
group_by(sentiment) %>% # 변수 sentiment에 대해 그룹화 -> 각 항목별로 로그 오즈비가 높은 단어를 추출하기 위해
slice_max(abs(log_odds_ratio), n = 10, # 로그 오즈비의 절댓값이 가장 높은 단어 10개 추출
with_ties = F) # 빈도가 동일하더라도 옵션 n에 지정한 개수만큼만 단어 추출
top10 %>%
print(n = Inf) # 모든 행 출력
# A tibble: 20 × 5
# Groups: sentiment [2]
word pos neg log_odds_ratio sentiment
<chr> <int> <int> <dbl> <chr>
1 소름 2 49 -3.03 neg
2 좌빨 1 21 -2.61 neg
3 못한 0 7 -2.29 neg
4 미친 0 7 -2.29 neg
5 좌좀 0 6 -2.16 neg
6 소름이 1 12 -2.08 neg
7 가난한 0 5 -2.00 neg
8 모르는 0 5 -2.00 neg
9 아쉽다 0 5 -2.00 neg
10 닭그네 0 4 -1.82 neg
11 축하 63 0 3.95 pos
12 멋지다 55 0 3.81 pos
13 대단한 47 0 3.66 pos
14 좋은 42 0 3.55 pos
15 대단하다 83 1 3.52 pos
16 자랑스럽다 78 1 3.46 pos
17 최고 27 0 3.12 pos
18 세계적인 24 0 3.01 pos
19 최고의 23 0 2.97 pos
20 위대한 22 0 2.92 pos
ggplot(top10, aes(x = reorder(word, log_odds_ratio), # reorder : 항목별 내림차순 정렬
y = log_odds_ratio,
fill = sentiment)) + # 감정 범주에 대해 막대 색깔 다르게 표현
geom_col() + # 막대 그래프
coord_flip() + # 가로로 회전
labs(x = NULL) # x축 제목 제거 -> 막대를 가로로 회전했기 때문에 y축 제목이 제거됨
Result!
출력 그래프를 보면 긍정 댓글에서는 “축하”, “멋지다”와 같은 단어가 부정 댓글에 비해 상대적으로 많이 사용되었으며, 부정 댓글에서는 “소름”, “좌빨”과 같은 단어가 긍정 댓글에 비해 상대적으로 많이 사용되었다는 것을 알 수 있다.
# "소름"이 사용된 댓글
score_comment %>% # 댓글별로 감정 점수를 계산 & 감정 범주가 부여된 객체 in 2-6
filter(str_detect(reply, "소름")) %>% # "소름"을 포함한 댓글만 추출
select(reply) # 다른 변수 제외하고 댓글 내용만 확인
# A tibble: 131 × 1
reply
<chr>
1 소름돋네요
2 와..진짜소름 저 소리처음질렀어요 눈물나요.. ㅠㅠ
3 생중계 보며 봉준호 할 때 소름이~~~!! ㅠㅠ 수상소감들으며 함께 가슴…
4 와 보다가 소름 짝 수고들하셨어요
5 한국어 소감 듣는데 소름돋네 축하드립니다
6 대단하다!! 봉준호 이름 나오자마자 소름
7 와우 브라보~ 키아누리브스의 봉준호, 순간 소름이.. 멋지십니다.
8 소름 돋네요. 축하합니다
9 소름.... 기생충 각본집 산거 다시한번 잘했다는 생각이ㅠㅠㅠ 축하해…
10 소름끼쳤어요 너무 멋집니다 ^^!!!!
# ℹ 121 more rows
# "미친"이 사용된 댓글
score_comment %>% # 댓글별로 감정 점수를 계산 & 감정 범주가 부여된 객체 in 2-6
filter(str_detect(reply, "미친")) %>% # "미친"을 포함한 댓글만 추출
select(reply) # 다른 변수 제외하고 댓글 내용만 확인
# A tibble: 15 × 1
reply
<chr>
1 와 3관왕 미친
2 미친거야 이건~~
3 Korea 대단합니다 김연아 방탄 봉준호 스포츠 음악 영화 못하는게 없어…
4 청룡영화제에서 다른나라가 상을 휩쓴거죠? 와..미쳤다 미국영화제에서…
5 설마했는데 감독상, 작품상, 각본상을 죄다 휩쓸어버릴 줄이야. 이건 …
6 완전 완전...미친촌재감~이런게 바로 애국이지~ 존경합니다~
7 이세상엔 참 미 친 인간들이 많다는걸 댓글에서 다시한번 느낀다..모두…
8 올해 아카데미 최다 수상작이기도 하다 이건 진짜 미친사건이다
9 CJ회장이 저기서 왜 언급되는지... 미친 부회장.. 공과사 구분 못하는 …
10 미친봉
11 미친 3관왕 ㄷㄷㄷㄷㄷ
12 진짜 미친일...
13 나도모르게 보다가 육성으로 미친...ㅋㅋㅋㅋ 대박ㅜ
14 헐...감독상...미친...미쳤다..소름돋는다...
15 인정할건인정하자 봉감독 송배우 이배우 조배우등 인정하자 또 가로세…
Result!
댓글 원문을 살펴보면 “소름”, “미친”이 주로 긍정적인 의미로 사용되었음을 알 수 있다.
# 감정 사전에서 "소름", "미친"의 감정 점수 확인
dic %>% # 감정 사전 in 1-1
filter(word %in% c("소름", "소름이", "미친")) # "소름", "소름이", "미친"만 추출
# A tibble: 3 × 2
word polarity
<chr> <dbl>
1 소름이 -2
2 소름 -2
3 미친 -2
Result!
감정 사전을 살펴보면, 해당 단어들의 감정 점수가 모두 음수, 즉, 부정 단어로 분류되어 있다.
Caution!
감정 분석은 감정 사전에 기반을 두기 때문에 텍스트의 맥락이 감정 사전의 맥락과 다르면 이처럼 반대되는 감정 점수를 부여하는 오류가 발생한다. 좀 더 정확하게 분석하려면 감정 사전을 수정해서 활용해야 한다. 수정할 때 같은 단어라도 맥락에 따라 표현하는 감정이 다르기 때문에 단어의 감정 점수는 신중하게 정해야 한다. 예를 들어, “빠르다”라는 단어는 스마트폰 사용 후기라면 속도가 빠르다는 의미로 사용될 테니 긍정어라고 볼 수 있다. 하지만 동영상 강의 후기라면 강의 진행 속도나 강사의 말이 빠르다는 의미로 사용될테니 부정어라고 볼 수 있다. 분석하는 텍스트의 맥락에 맞게 단어의 감정 점수를 정해야 정확히 분석할 수 있다.
polarity
를 “2”로 수정해 새로운 감정 사전을 만든다.# 새로운 감정 사전
new_dic <- dic %>% # 감정 사전 in 1-1
mutate(polarity = ifelse(word %in% c("소름", "소름이", "미친"), 2, polarity)) # "소름", "소름이", "미친"의 polarity를 2로 수정
new_dic %>%
filter(word %in% c("소름", "소름이", "미친")) # "소름", "소름이", "미친"만 추출
# A tibble: 3 × 2
word polarity
<chr> <dbl>
1 소름이 2
2 소름 2
3 미친 2
new_word_comment <- word_comment1 %>% # 원본 댓글을 단어 기준으로 토큰화 & 감정 점수와 범주가 부여된 객체 in 2-4
select(-polarity) %>% # 기존의 감정 사전을 이용해 부여한 감정 점수 제거
left_join(new_dic, by = "word") %>% # 수정한 감정 사전을 이용해 감정 점수 부여 -> 수정한 감정 사전과 변수 word를 기준으로 결합
mutate(polarity = ifelse(is.na(polarity), 0, polarity)) # 결측값 NA를 0으로 대체 -> 단어가 감정 사전에 없으면 결측값이 부여되기 때문
new_word_comment
# A tibble: 37,718 × 9
reg_time reply press title url id word sentiment
<dttm> <chr> <chr> <chr> <chr> <int> <chr> <chr>
1 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 정말 neu
2 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 우리 neu
3 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 집에 neu
4 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 좋은 pos
5 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 일이 neu
6 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 생겨 neu
7 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 기쁘… pos
8 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 행복… pos
9 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 것처… neu
10 2020-02-10 16:59:02 정말 … MBC '기생… http… 1 나의 neu
# ℹ 37,708 more rows
# ℹ 1 more variable: polarity <dbl>
new_score_comment <- new_word_comment %>% # 감정 점수가 부여된 객체 in 4-2
group_by(id, reply) %>% # 변수 id와 reply에 대해 그룹화 수행 -> 각 댓글별로 점수합을 계산하기 위해
summarise(score = sum(polarity)) %>% # 점수합 계산
ungroup() # 그룹 해제
# 긍정 댓글
new_score_comment %>%
select(score, reply) %>% # 변수 score와 reply만 선택
arrange(-score) # 점수를 내림차순 정렬
# A tibble: 4,140 × 2
score reply
<dbl> <chr>
1 11 아니 다른상을 받은것도 충분히 대단하고 굉장하지만 최고의 영…
2 9 봉준호의 위대한 업적은 진보 영화계의 위대한 업적이고 대한민…
3 8 소름 소름 진짜 멋지다 대단하다
4 7 이 수상소식을 듣고 억수로 기뻐하는 가족이 있을것 같다. SNS를…
5 7 감사 감사 감사 수상 소감도 3관왕 답네요
6 6 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 일…
7 6 와 너무 기쁘다! 이 시국에 정말 내 일같이 기쁘고 감사하다!!! …
8 6 축하 축하 축하 모두들 수고 하셨어요 기생충 화이팅
9 6 생중계 보며 봉준호 할 때 소름이~~~!! ㅠㅠ 수상소감들으며 함…
10 6 축하!!!! 축하!!!!! 오스카의 정복은 무엇보다 시나리오의 힘이…
# ℹ 4,130 more rows
# 부정 댓글
new_score_comment %>%
select(score, reply) %>% # 변수 score와 reply만 선택
arrange(score) # 점수를 오름차순 정렬
# A tibble: 4,140 × 2
score reply
<dbl> <chr>
1 -7 기생충 영화 한국인 으로써 싫다 대단히 싫다!! 가난한 서민들의…
2 -6 이 페미민국이 잘 되는 게 아주 싫다. 최악의 나쁜일들과 불운, …
3 -5 특정 인물의 성공을 국가의 부흥으로 연관짓는 것은 미개한 발상…
4 -4 좌파들이 나라 망신 다 시킨다..ㅠ 설레발 오지게 치더니..꼴랑 …
5 -4 부패한 386 민주화 세대 정권의 무분별한 포퓰리즘으로 탄생한 …
6 -4 기생충 내용은 좋은데 제목이 그래요. 극 중 송강호가족이 부잣…
7 -4 이런 감독과 이런 배우보고 좌좀 이라고 지1랄하던 그분들 다 어…
8 -4 축하합니다. 근데 현실 세계인 한국에선 그보다 훨씬 나쁜 넘인 …
9 -4 큰일이다....국제적 망신이다...전 세계사람들이 우리나라를 기…
10 -4 더럽고 추잡한 그들만의 리그
# ℹ 4,130 more rows
# 1. 감정 분류
new_score_comment <- new_score_comment %>% # 댓글별로 감정 점수를 계산한 객체 in 4-3
# 변수 sentiment에 감정 점수가 1 이상이면 "pos", -1이하면 "neg", 그 외는 "neu"로 입력
mutate(sentiment = ifelse(score >= 1, "pos",
ifelse(score <= -1, "neg", "neu")))
new_score_comment
# A tibble: 4,140 × 4
id reply score sentiment
<int> <chr> <dbl> <chr>
1 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 … 6 pos
2 2 와 너무 기쁘다! 이 시국에 정말 내 일같이 기… 6 pos
3 3 우리나라의 영화감독분들 그리고 앞으로 그 꿈… 4 pos
4 4 봉준호 감독과 우리나라 대한민국 모두 자랑스… 3 pos
5 5 노벨상 탄느낌이네요 축하축하 합니다 0 neu
6 6 기생충 상 받을때 박수 쳤어요.감독상도 기대해… 0 neu
7 7 대한민국 영화사를 새로 쓰고 계시네요 ㅊㅊㅊ 0 neu
8 8 저런게 아카데미상 받으면 '태극기 휘날리며'' … 0 neu
9 9 다시한번 보여주세요 영화관에서 보고싶은디 0 neu
10 10 대한민국 BTS와함께 봉준호감독님까지 대단하고… 2 pos
# ℹ 4,130 more rows
# 2. 수정한 감정 사전을 활용한 댓글의 전반적인 감정 경향
new_score_comment %>% # 감정 범주가 할당된 객체
count(sentiment) %>% # 변수 sentiment에 대해 그룹화 수행 -> 각 항목별로 비율을 계산하기 위해
mutate(ratio = n/sum(n)*100) # 비율 계산
# A tibble: 3 × 3
sentiment n ratio
<chr> <int> <dbl>
1 neg 368 8.89
2 neu 2890 69.8
3 pos 882 21.3
Result!
출력 결과를 보면 원본 감정 사전을 활용했을 때와 비교해 부정 댓글(“neg”)의 비율은 10.5%에서 8.89%로 줄어들고, 긍정 댓글(“pos”)의 비율은 19.5%에서 21.3%로 늘어났다. 감정 범주 비율이 달라진 이유는 수정한 사전으로 감정 점수를 부여하자 “소름”, “소름이”, “미친”을 사용한 댓글 일부가 긍정 댓글로 분류되었기 때문이다.
# "소름", "소름이", "미친"을 사용한 댓글의 감정 범주 빈도 비교
word <- "소름|소름이|미친" # 함수 str_detect에 여러 문자를 입력할 때는 "|"로 구분
# 원본 감정 사전 활용
score_comment %>%
filter(str_detect(reply, word)) %>% # "소름", "소름이", "미친"을 사용한 댓글 추출
count(sentiment) # 빈도 계산
# 수정한 감정 사전 활용
new_score_comment %>%
filter(str_detect(reply, word)) %>% # "소름", "소름이", "미친"을 사용한 댓글 추출
count(sentiment) # 빈도 계산
Result!
“소름”, “소름이”, “미친”을 사용한 댓글의 감정 범주 빈도가 달라졌음을 알 수 있다.
Caution!
수정한 사전을 사용하더라도 댓글에 함께 사용된 단어들의 감정 점수가 낮으면 부정 댓글로 분류될 수 있다.
# 1. 토큰화 & 두 글지 이상 한글 단어만 추출
new_comment <- new_score_comment %>% # 감정 범주가 할당된 객체 in 4-4
unnest_tokens(input = reply, # 토큰화를 수행할 텍스트가 포함된 변수명
output = word, # 출력 변수명
token = "words", # 단어 기준으로 토큰화
drop = F) %>% # 원문 제거 X
filter(!str_detect(word, "[^가-힣]") & # 한글만 추출
str_count(word) >= 2) # 두 글자 이상 단어만 추출
new_comment
# A tibble: 33,445 × 5
id reply score sentiment word
<int> <chr> <dbl> <chr> <chr>
1 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 정말
2 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 우리
3 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 집에
4 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 좋은
5 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 일이
6 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 생겨
7 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 기쁘고
8 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 행복한
9 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 것처럼
10 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 나의
# ℹ 33,435 more rows
# 2. 감정 및 단어별 빈도 구하기
new_frequency_word <- new_comment %>%
count(sentiment, word, sort = T) # sentiment (감정)별 word (단어)의 빈도 계산 / sort = T : 내림차순 정렬
new_frequency_word
# A tibble: 19,188 × 3
sentiment word n
<chr> <chr> <int>
1 neu 축하합니다 214
2 neu 봉준호 200
3 neu 기생충 164
4 neu 축하드립니다 151
5 neu 정말 145
6 neu 대박 134
7 neu 진짜 120
8 pos 봉준호 113
9 pos 정말 110
10 neu 자랑스럽습니다 94
# ℹ 19,178 more rows
# 3. Wide Format Dataset으로 변환
new_comment_wide <- new_frequency_word %>%
filter(sentiment != "neu") %>% # 감정 범주가 "중립"인 단어 제외
pivot_wider(names_from = sentiment, # 변수명으로 입력할 값이 들어 있는 변수
values_from = n, # 변수에 채워 넣을 값이 들어 있는 변수
values_fill = list(n = 0)) # 결측값 NA를 0으로 대체
new_comment_wide
# A tibble: 8,408 × 3
word pos neg
<chr> <int> <int>
1 봉준호 113 43
2 정말 110 20
3 진짜 88 18
4 대단하다 86 1
5 자랑스럽다 78 1
6 영화 63 17
7 축하 63 0
8 대한민국 61 4
9 축하합니다 57 9
10 기생충 56 30
# ℹ 8,398 more rows
# 4. 로그 오즈비 계산
new_comment_wide <- new_comment_wide %>%
mutate(log_odds_ratio = log(((pos + 1) / (sum(pos + 1))) /
((neg + 1) / (sum(neg + 1)))))
new_comment_wide
# A tibble: 8,408 × 4
word pos neg log_odds_ratio
<chr> <int> <int> <dbl>
1 봉준호 113 43 0.674
2 정말 110 20 1.39
3 진짜 88 18 1.27
4 대단하다 86 1 3.49
5 자랑스럽다 78 1 3.40
6 영화 63 17 0.990
7 축하 63 0 3.88
8 대한민국 61 4 2.24
9 축하합니다 57 9 1.48
10 기생충 56 30 0.331
# ℹ 8,398 more rows
# 5. 상대적으로 중요한 단어 추출
new_top10 <- new_comment_wide %>%
mutate(sentiment = ifelse(log_odds_ratio > 0, "pos", "neg")) %>% # 변수 sentiment에 로그 오즈비가 양수이면 "pos", 음수이면 "neg" 입력
group_by(sentiment) %>% # 변수 sentiment에 대해 그룹화 -> 각 항목별로 로그 오즈비가 높은 단어를 추출하기 위해
slice_max(abs(log_odds_ratio), n = 10, # 로그 오즈비의 절댓값이 가장 높은 단어 10개 추출
with_ties = F) # 빈도가 동일하더라도 옵션 n에 지정한 개수만큼만 단어 추출
new_top10
# A tibble: 20 × 5
# Groups: sentiment [2]
word pos neg log_odds_ratio sentiment
<chr> <int> <int> <dbl> <chr>
1 좌빨 1 21 -2.68 neg
2 못한 0 7 -2.36 neg
3 좌좀 0 6 -2.22 neg
4 가난한 0 5 -2.07 neg
5 모르는 0 5 -2.07 neg
6 아쉽다 0 5 -2.07 neg
7 닭그네 0 4 -1.89 neg
8 못하고 0 4 -1.89 neg
9 사회적 0 4 -1.89 neg
10 싫다 0 4 -1.89 neg
11 축하 63 0 3.88 pos
12 멋지다 56 0 3.76 pos
13 소름 56 0 3.76 pos
14 대단한 47 0 3.59 pos
15 대단하다 86 1 3.49 pos
16 좋은 42 0 3.48 pos
17 자랑스럽다 78 1 3.40 pos
18 최고 28 0 3.09 pos
19 세계적인 24 0 2.94 pos
20 최고의 23 0 2.90 pos
# 6. 시각화
ggplot(new_top10, aes(x = reorder(word, log_odds_ratio), # reorder : 항목별 내림차순 정렬
y = log_odds_ratio,
fill = sentiment)) + # 감정 범주에 대해 막대 색깔 다르게 표현
geom_col() + # 막대 그래프
coord_flip() + # 가로로 회전
labs(x = NULL) # x축 제목 제거 -> 막대를 가로로 회전했기 때문에 y축 제목이 제거됨
Result!
출력 그래프를 보면 긍정 댓글에서는 “축하”, “소름”과 같은 단어가 부정 댓글에 비해 상대적으로 많이 사용되었으며, 부정 댓글에서는 “좌빨”, “못한”과 같은 단어가 긍정 댓글에 비해 상대적으로 많이 사용되었다는 것을 알 수 있다. 원본 감정 사전을 활용하여 분석하였을 때와 달리 “소름”의 로그 오즈비는 양수이다.
# 로그 오즈비 분석 결과 비교
# 원본 감정 사전 활용한 로그 오즈비 결과
top10 %>%
select(-pos, -neg) %>% # 변수 pos, neg 제외
arrange(-log_odds_ratio) %>% # 내림차순 정렬
print(n = Inf) # 모든 행 출력
# 수정한 감정 사전 활용한 로그 오즈비 결과
new_top10 %>%
select(-pos, -neg) %>% # 변수 pos, neg 제외
arrange(-log_odds_ratio) %>% # 내림차순 정렬
print(n = Inf) # 모든 행 출력
Result!
원본 감정 사전을 사용했을 때와 달리 “소름”이 긍정 댓글에 자주 사용한 단어로 추출되고 “미친”은 목록에서 사라졌다. 수정한 감정 사전을 이용했을 때 “미친”이 목록에서 사라진 이유는 로그 오즈비가 10위 안에 들지 못할 정도로 낮기 때문이다. 다음 코드의 출력 결과를 보면 “미친”의 로그 오즈비가 1.80인 것을 알 수 있다.
# "미친"의 로그 오즈비
new_comment_wide %>%
filter(word == "미친")
# A tibble: 1 × 4
word pos neg log_odds_ratio
<chr> <int> <int> <dbl>
1 미친 7 0 1.80
# 긍정 댓글 원문
new_score_comment %>% # 감정 범주가 할당된 객체 in 4-4
filter(sentiment == "pos" & # 긍정 댓글이면서 "축하"를 사용한 댓글 추출
str_detect(reply, "축하")) %>%
select(reply) # 다른 변수 제외하고 댓글 내용만 확인
# A tibble: 189 × 1
reply
<chr>
1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! 나의 일인 양 …
2 와 너무 기쁘다! 이 시국에 정말 내 일같이 기쁘고 감사하다!!! 축하드…
3 우리나라의 영화감독분들 그리고 앞으로 그 꿈을 그리는 분들에게 큰 …
4 아카데미나 다른 상이나 지들만의 잔치지~ 난 대한민국에서 받는 상이 …
5 정부에 빨대 꼽은 정치시민단체 기생충들이 득실거리는 떼한민국애서 …
6 대단해요 나는 안봤는데 그렇게 잘 만들어 한국인의 기백을 세계에 알…
7 나한테 돌아오는게 하나도 없는데 왜이렇게 자랑스럽지?ㅎㅎㅎ 축하 합…
8 한국영화 100년사에 한횟을 긋네요. 정말 축하 합니다
9 와 대단하다 진짜 축하드려요!!! 대박 진짜
10 각본상, 국제 영화상 수상 축하. 편집상은 꽝남.
# ℹ 179 more rows
new_score_comment %>% # 감정 범주가 할당된 객체 in 4-4
filter(sentiment == "pos" & # 긍정 댓글이면서 "소름"을 사용한 댓글 추출
str_detect(reply, "소름")) %>%
select(reply) # 다른 변수 제외하고 댓글 내용만 확인
# A tibble: 77 × 1
reply
<chr>
1 생중계 보며 봉준호 할 때 소름이~~~!! ㅠㅠ 수상소감들으며 함께 가슴…
2 와 보다가 소름 짝 수고들하셨어요
3 대단하다!! 봉준호 이름 나오자마자 소름
4 와우 브라보~ 키아누리브스의 봉준호, 순간 소름이.. 멋지십니다.
5 소름 돋네요. 축하합니다
6 소름.... 기생충 각본집 산거 다시한번 잘했다는 생각이ㅠㅠㅠ 축하해…
7 봉준호 아저씨 우리나라 자랑입니다 헐리웃 배우들과 화면에 같이 비춰…
8 추카해요. 봉준호하는데 막 완전 소름 돋았어요.
9 소름돋아서 닭살돋고.. 그냥 막 감동이라 눈물이 나려했어요.. 대단하…
10 한국 영화 최초 아카데미상 수상, 92년 역사의 국제 장편 영화상과 최…
# ℹ 67 more rows
Result!
긍정 댓글은 수상을 축하하고 대한민국의 위상이 올라갔다는 내용이 많다는 것을 알 수 있다.
# 부정 댓글 원문
new_score_comment %>% # 감정 범주가 할당된 객체 in 4-4
filter(sentiment == "neg" & # 부정 댓글이면서 "좌빨"을 사용한 댓글 추출
str_detect(reply, "좌빨")) %>%
select(reply) # 다른 변수 제외하고 댓글 내용만 확인
# A tibble: 34 × 1
reply
<chr>
1 자칭 보수들은 분노의 타이핑중 ㅋㅋㅋㅋㅋㅋ전세계를 좌빨로 몰수는 …
2 자칭보수 왈 : 미국에 로비했다 ㅋㅋ좌빨영화가 상받을리 없다 ㅋㅋㅋ…
3 좌빨 봉준호 영화는 쳐다도 안본다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
4 봉준호 그렇게 미국 싫어하는데 상은 쳐 받으러 가는 좌빨 수준ㅋㅋㅋ
5 좌빨 기생충들은 댓글도 달지마라 미국 영화제 수상이 니들하고 뭔상관…
6 얘들은 왜 인정을 안하냐? ㅋㅋ 니들 이미 변호인 찍을대 부터 송강호 …
7 넷상 보수들 만큼 이중적인 새1끼들 없음. 봉준호 송강호 보고 종북좌…
8 우선 축하합니다.그리고 다음에는 조씨가족을 모델로한 뻔뻔하고 거짓…
9 Korea 대단합니다 김연아 방탄 봉준호 스포츠 음악 영화 못하는게 없어…
10 좌빨 감독이라고 블랙리스트에 올랐던 사람을 세계인이 인정해주네. 방…
# ℹ 24 more rows
new_score_comment %>% # 감정 범주가 할당된 객체 in 4-4
filter(sentiment == "neg" & # 부정 댓글이면서 "못한"을 사용한 댓글 추출
str_detect(reply, "못한")) %>%
select(reply) # 다른 변수 제외하고 댓글 내용만 확인
# A tibble: 7 × 1
reply
<chr>
1 한번도경험하지. 못한 조국가족사기단기생충. 개봉박두
2 여기서 정치얘기하는 건 학창시절 공부 못한 거 인증하는 꼴... 주제좀 …
3 이 기사를 반문으로 먹고 사는 자유왜국당과, mb아바타 간철수 댓글알바…
4 한국미국일본 vs 주적북한,중국러시아 이 구도인 현 시대 상황 속에서, …
5 친일수꼴 들과 자한당넘들이 나라에 경사만 있으면 엄청 싫어합니다, 맨…
6 각본상,국제상,감독상 ...어디서 듣도보도 못한 아차상 같은 쩌리처리용…
7 난 밥을 먹고 기생충은 오스카를 먹다, 기생충은 대한민국의 국격을 높…
Result!
부정 댓글은 수상 자체보다는 감독의 정치 성향이나 댓글을 단 사용자들의 정치 성향을 비판하는 내용이 많다는 것을 알 수 있다.
# 예시 : 신조어가 포함된 문장
df <- tibble(sentence = c("이번 에피소드 쩐다",
"이 영화 핵노잼")) %>%
# 토큰화
unnest_tokens(input = sentence, # 토큰화를 수행할 텍스트가 포함된 변수명
output = word, # 출력 변수명
token = "words", # 단어 기준으로 토큰화
drop = F) # 원문 제거 X
df %>%
left_join(dic, by = "word") %>% # 토큰화 결과와 감정 사전을 변수 word를 기준으로 결합
mutate(polarity = ifelse(is.na(polarity), 0, polarity)) %>% # 결측값 NA를 0으로 대체 -> 단어가 감정 사전에 없으면 결측값이 부여되기 때문
group_by(sentence) %>% # 변수 sentence에 대해 그룹화 수행 -> 각 댓글별로 점수합을 계산하기 위해
summarise(score = sum(polarity)) # 점수합 계산
# A tibble: 2 × 2
sentence score
<chr> <dbl>
1 이 영화 핵노잼 0
2 이번 에피소드 쩐다 0
Result!
두 문장 모두 감정 사전에 없는 단어여서 감정 점수가 부여되지 않았다. 다음과 같이 감정 사전에 신조어와 감정 점수를 추가하면 신조어에도 감정 점수를 부여할 수 있다.
# A tibble: 2 × 2
word polarity
<chr> <dbl>
1 쩐다 2
2 핵노잼 -2
# 사전에 신조어 추가
newword_dic <- bind_rows(dic, newword) # 원본 감정 사전과 신조어 목록을 행으로 결합
newword_dic
# A tibble: 14,856 × 2
word polarity
<chr> <dbl>
1 ㅡㅡ -1
2 ㅠㅠ -1
3 ㅠ_ㅠ -1
4 ㅠ -1
5 ㅜㅡ -1
6 ㅜㅜ -1
7 ㅜ_ㅜ -1
8 ㅜ.ㅜ -1
9 ㅜ -1
10 ㅗ -1
# ℹ 14,846 more rows
# 새로운 사전을 활용해 감정 점수 부여
df %>%
left_join(newword_dic, by = "word") %>% # 토큰화 결과와 새로운 감정 사전을 변수 word를 기준으로 결합
mutate(polarity = ifelse(is.na(polarity), 0, polarity)) %>% # 결측값 NA를 0으로 대체 -> 단어가 감정 사전에 없으면 결측값이 부여되기 때문
group_by(sentence) %>% # 변수 sentence에 대해 그룹화 수행 -> 각 댓글별로 점수합을 계산하기 위해
summarise(score = sum(polarity)) # 점수합 계산
# A tibble: 2 × 2
sentence score
<chr> <dbl>
1 이 영화 핵노잼 -2
2 이번 에피소드 쩐다 2
Caution!
어떤 신조어를 사전에 추가할지 모르겠다면 감정 점수가 부여되지 않은 단어 중에 빈도가 높은 단어를 살펴보면 도움이 된다. 빈도가 높은 단어 중에 감정을 표현하는 단어가 있으면 점수를 부여해 사전에 추가하면 된다.
# 1. 자주 사용한 감정 단어 확인
# 1-1. 단어에 감정 점수 부여
word_comment <- word_comment %>% # 단어 기준으로 토큰화 & 전처리를 수행한 객체 in 2-2
left_join(dic, by = "word") %>% # 토큰화 결과와 감정 사전을 변수 word를 기준으로 결합
mutate(polarity = ifelse(is.na(polarity), 0, polarity)) # 결측값 NA를 0으로 대체 -> 단어가 감정 사전에 없으면 결측값이 부여되기 때문
word_comment
# A tibble: 37,718 × 8
reg_time reply press title url id word polarity
<dttm> <chr> <chr> <chr> <chr> <int> <chr> <dbl>
1 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 정말 0
2 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 우리 0
3 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 집에 0
4 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 좋은 2
5 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 일이 0
6 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 생겨 0
7 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 기쁘… 2
8 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 행복… 2
9 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 것처… 0
10 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 나의 0
# ℹ 37,708 more rows
# 1-2. 감정 분류
word_comment <- word_comment %>%
# 감정 분류 : 변수 sentiment에 polarity가 2이면 "pos", -2이면 "neg", 둘 다 아니면 "neu"로 입력
mutate(sentiment = ifelse(polarity == 2, "pos",
ifelse(polarity == -2, "neg", "neu")))
word_comment
# A tibble: 37,718 × 9
reg_time reply press title url id word polarity
<dttm> <chr> <chr> <chr> <chr> <int> <chr> <dbl>
1 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 정말 0
2 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 우리 0
3 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 집에 0
4 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 좋은 2
5 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 일이 0
6 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 생겨 0
7 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 기쁘… 2
8 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 행복… 2
9 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 것처… 0
10 2020-02-10 16:59:02 정말 우… MBC '기… http… 1 나의 0
# ℹ 37,708 more rows
# ℹ 1 more variable: sentiment <chr>
# 1-3. 자주 사용한 감정 단어 추출
top10_sentiment <- word_comment %>%
filter(sentiment != "neu") %>% # 감정 범주가 "중립"인 댓글 제외
count(sentiment, word) %>% # 변수 sentiment의 항목별 단어 빈도 계산
group_by(sentiment) %>% # 변수 sentiment에 대해 그룹화 수행 -> 각 항목별로 자주 사용한 단어를 추출하기 위해
slice_max(n, n = 10) # 자주 사용한 단어 10개 추출
top10_sentiment
# A tibble: 22 × 3
# Groups: sentiment [2]
sentiment word n
<chr> <chr> <int>
1 neg 소름 56
2 neg 소름이 16
3 neg 아니다 15
4 neg 우울한 9
5 neg 해 8
6 neg 미친 7
7 neg 가난한 5
8 neg 어려운 5
9 neg 힘든 5
10 neg 더러운 4
# ℹ 12 more rows
# 2. 텍스트의 감정 점수 계산
# 텍스트별로 단어의 감정 점수 합산
score_comment <- word_comment %>%
group_by(id, reply) %>% # 변수 id와 reply에 대해 그룹화 수행 -> 각 댓글별로 점수합을 계산하기 위해
summarise(score = sum(polarity)) %>% # 점수합 계산
ungroup() # 그룹 해제
score_comment
# A tibble: 4,140 × 3
id reply score
<int> <chr> <dbl>
1 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 것처럼!! … 6
2 2 와 너무 기쁘다! 이 시국에 정말 내 일같이 기쁘고 감사하… 6
3 3 우리나라의 영화감독분들 그리고 앞으로 그 꿈을 그리는 … 4
4 4 봉준호 감독과 우리나라 대한민국 모두 자랑스럽다. 세계 … 3
5 5 노벨상 탄느낌이네요 축하축하 합니다 0
6 6 기생충 상 받을때 박수 쳤어요.감독상도 기대해요.봉준호 … 0
7 7 대한민국 영화사를 새로 쓰고 계시네요 ㅊㅊㅊ 0
8 8 저런게 아카데미상 받으면 '태극기 휘날리며'' '광해' '명… 0
9 9 다시한번 보여주세요 영화관에서 보고싶은디 0
10 10 대한민국 BTS와함께 봉준호감독님까지 대단하고 한국의 문… 2
# ℹ 4,130 more rows
# 3. 감정 범주별 주요 단어 확인
# 3-1. 감정 범주 변수 생성
score_comment <- score_comment %>%
# 변수 sentiment에 감정 점수가 1 이상이면 "pos", -1이하면 "neg", 그 외는 "neu"로 입력
mutate(sentiment = ifelse(score >= 1, "pos",
ifelse(score <= -1, "neg", "neu")))
score_comment
# A tibble: 4,140 × 4
id reply score sentiment
<int> <chr> <dbl> <chr>
1 1 정말 우리 집에 좋은 일이 생겨 기쁘고 행복한 … 6 pos
2 2 와 너무 기쁘다! 이 시국에 정말 내 일같이 기… 6 pos
3 3 우리나라의 영화감독분들 그리고 앞으로 그 꿈… 4 pos
4 4 봉준호 감독과 우리나라 대한민국 모두 자랑스… 3 pos
5 5 노벨상 탄느낌이네요 축하축하 합니다 0 neu
6 6 기생충 상 받을때 박수 쳤어요.감독상도 기대해… 0 neu
7 7 대한민국 영화사를 새로 쓰고 계시네요 ㅊㅊㅊ 0 neu
8 8 저런게 아카데미상 받으면 '태극기 휘날리며'' … 0 neu
9 9 다시한번 보여주세요 영화관에서 보고싶은디 0 neu
10 10 대한민국 BTS와함께 봉준호감독님까지 대단하고… 2 pos
# ℹ 4,130 more rows
# 3-2. 토큰화 & 두 글지 이상 한글 단어만 추출
comment <- score_comment %>%
unnest_tokens(input = reply, # 토큰화를 수행할 텍스트가 포함된 변수명
output = word, # 출력 변수명
token = "words", # 단어 기준으로 토큰화
drop = F) %>% # 원문 제거 X
filter(!str_detect(word, "[^가-힣]") & # 한글만 추출
str_count(word) >= 2) # 두 글자 이상 단어만 추출
comment
# A tibble: 33,445 × 5
id reply score sentiment word
<int> <chr> <dbl> <chr> <chr>
1 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 정말
2 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 우리
3 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 집에
4 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 좋은
5 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 일이
6 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 생겨
7 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 기쁘고
8 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 행복한
9 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 것처럼
10 1 정말 우리 집에 좋은 일이 생겨 기쁘고 … 6 pos 나의
# ℹ 33,435 more rows
# 3-3. 감정 범주별 단어 빈도 구하기
frequency_word <- comment %>%
count(sentiment, word, sort = T) # sentiment (감정)별 word (단어)의 빈도 계산 / sort = T : 내림차순 정렬
frequency_word
# A tibble: 19,223 × 3
sentiment word n
<chr> <chr> <int>
1 neu 축하합니다 214
2 neu 봉준호 203
3 neu 기생충 164
4 neu 축하드립니다 155
5 neu 정말 146
6 neu 대박 134
7 neu 진짜 121
8 pos 봉준호 106
9 pos 정말 97
10 neu 자랑스럽습니다 96
# ℹ 19,213 more rows
# 3-4. Wide Format Dataset으로 변환
comment_wide <- frequency_word %>% # 감정 및 단어별 빈도가 저장되어 있는 객체 in 3-1
filter(sentiment != "neu") %>% # 감정 범주가 "중립"인 단어 제외
pivot_wider(names_from = sentiment, # 변수명으로 입력할 값이 들어 있는 변수
values_from = n, # 변수에 채워 넣을 값이 들어 있는 변수
values_fill = list(n = 0))# 결측값 NA를 0으로 대체
comment_wide
# A tibble: 8,380 × 3
word pos neg
<chr> <int> <int>
1 봉준호 106 47
2 정말 97 32
3 대단하다 83 1
4 진짜 79 26
5 자랑스럽다 78 1
6 축하 63 0
7 대한민국 61 4
8 영화 58 18
9 멋지다 55 0
10 기생충 53 33
# ℹ 8,370 more rows
# 3-5. 로그 오즈비 계산
comment_wide <- comment_wide %>%
mutate(log_odds_ratio = log(((pos + 1) / (sum(pos + 1))) /
((neg + 1) / (sum(neg + 1)))))
comment_wide
# A tibble: 8,380 × 4
word pos neg log_odds_ratio
<chr> <int> <int> <dbl>
1 봉준호 106 47 0.589
2 정말 97 32 0.876
3 대단하다 83 1 3.52
4 진짜 79 26 0.873
5 자랑스럽다 78 1 3.46
6 축하 63 0 3.95
7 대한민국 61 4 2.30
8 영화 58 18 0.920
9 멋지다 55 0 3.81
10 기생충 53 33 0.250
# ℹ 8,370 more rows
# 3-6. 긍정, 부정 텍스트에 상대적으로 자주 사용한 단어 추출
top10 <- comment_wide %>%
mutate(sentiment = ifelse(log_odds_ratio > 0, "pos", "neg")) %>% # 변수 sentiment에 로그 오즈비가 양수이면 "pos", 음수이면 "neg" 입력
group_by(sentiment) %>% # 변수 sentiment에 대해 그룹화 -> 각 항목별로 로그 오즈비가 높은 단어를 추출하기 위해
slice_max(abs(log_odds_ratio), n = 10, # 로그 오즈비의 절댓값이 가장 높은 단어 10개 추출
with_ties = F) # 빈도가 동일하더라도 옵션 n에 지정한 개수만큼만 단어 추출
top10
# A tibble: 20 × 5
# Groups: sentiment [2]
word pos neg log_odds_ratio sentiment
<chr> <int> <int> <dbl> <chr>
1 소름 2 49 -3.03 neg
2 좌빨 1 21 -2.61 neg
3 못한 0 7 -2.29 neg
4 미친 0 7 -2.29 neg
5 좌좀 0 6 -2.16 neg
6 소름이 1 12 -2.08 neg
7 가난한 0 5 -2.00 neg
8 모르는 0 5 -2.00 neg
9 아쉽다 0 5 -2.00 neg
10 닭그네 0 4 -1.82 neg
11 축하 63 0 3.95 pos
12 멋지다 55 0 3.81 pos
13 대단한 47 0 3.66 pos
14 좋은 42 0 3.55 pos
15 대단하다 83 1 3.52 pos
16 자랑스럽다 78 1 3.46 pos
17 최고 27 0 3.12 pos
18 세계적인 24 0 3.01 pos
19 최고의 23 0 2.97 pos
20 위대한 22 0 2.92 pos
Text and figures are licensed under Creative Commons Attribution CC BY 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".