Sentiment Analysis

Text Mining

Description for Sentiment Analysis

Yeongeun Jeon , Jung In Seo
2023-11-12

감정 분석 (Sentiment Analysis)


패키지 설치

pacman::p_load("readr",
               "dplyr", "tidyr",
               "stringr",
               "tidytext",
               "textclean",
               "KoNLP",
               "ggplot2")

1. 감정 사전 활용



1-1. 감정 사전 불러오기

# 감정 사전 불러오기
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 한국어 감정 사전 깃허브 출처는 여기이다.


# 긍정 단어 
dic %>% 
  filter(polarity %in% c(1, 2) ) %>% 
  arrange(word)
# 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
# 부정 단어
dic %>% 
  filter(polarity %in% c(-1, -2) ) %>% 
  arrange(word)
# 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으로 표현된다.

# 단어 "좋은"과 "나쁜"
dic %>% 
  filter(word %in% c("좋은", "나쁜"))
# A tibble: 2 × 2
  word  polarity
  <chr>    <dbl>
1 좋은         2
2 나쁜        -2
# 단어 "기쁜"과 "슬픈"
dic %>% 
  filter(word %in% c("기쁜", "슬픈"))
# A tibble: 2 × 2
  word  polarity
  <chr>    <dbl>
1 슬픈        -2
2 기쁜         2
# 단어 "행복하다"와 "좌절하다"
dic %>%
  filter(word %in% c("행복하다", "좌절하다"))
# 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개이다.


1-2. 감정 점수 계산

# 예시 문장
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이 되었다.


2. 댓글 감정 분석


2-1. 데이터 불러오기

# 데이터 불러오기
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

2-2. 전처리

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

2-3. 감정 점수 부여

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

2-4. 자주 사용한 단어 확인

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. 감정 사전 수정에서 다룬다.


2-5. 댓글별 감정 점수 계산

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! 감정 점수가 높은 긍정 댓글을 보면 제작진의 수상을 축하하고 대한민국의 위상이 올라간 것을 기뻐하는 긍정적인 내용이 많은 반면, 감정 점수가 낮은 부정 댓글을 보면 감독의 정치 성향이나 영화 내용으로 연상되는 사회 문제를 비판하는 부정적인 내용이 많다는 것을 알 수 있다.


2-6. 감정 경향 확인

# 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! 누적 막대 그래프는 하나의 막대 위에 여러 범주의 비율을 표현하며, 이를 통해 구성 요소의 비중 차이를 한눈에 파악할 수 있다. 출력 그래프를 보면 막대가 감정 범주별로 누적되어 어떤 감정 범주의 댓글이 많은지 쉽게 알 수 있다.


3. 감정 범주별 주요 단어 확인


3-1. 감정 범주별 단어 빈도 계산

# 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! 추출한 단어를 보면 긍정 댓글과 부정 댓글의 내용이 어떻게 다른지 알 수 있다. 하지만 단순히 빈도가 높은 단어를 추출했기 때문에, “봉준호”, “기생충” 같은 단어도 추출되었다. 긍정 댓글과 부정 댓글의 차이를 이해하려면 양쪽에서 상대적으로 자주 사용한 단어를 비교해야 한다.


3-2. 로그 오즈비 계산

# 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

3-3. 상대적으로 중요한 단어 추출

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      

3-4. 시각화

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! 출력 그래프를 보면 긍정 댓글에서는 “축하”, “멋지다”와 같은 단어가 부정 댓글에 비해 상대적으로 많이 사용되었으며, 부정 댓글에서는 “소름”, “좌빨”과 같은 단어가 긍정 댓글에 비해 상대적으로 많이 사용되었다는 것을 알 수 있다.


4. 감정 사전 수정

# "소름"이 사용된 댓글
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! 감정 분석은 감정 사전에 기반을 두기 때문에 텍스트의 맥락이 감정 사전의 맥락과 다르면 이처럼 반대되는 감정 점수를 부여하는 오류가 발생한다. 좀 더 정확하게 분석하려면 감정 사전을 수정해서 활용해야 한다. 수정할 때 같은 단어라도 맥락에 따라 표현하는 감정이 다르기 때문에 단어의 감정 점수는 신중하게 정해야 한다. 예를 들어, “빠르다”라는 단어는 스마트폰 사용 후기라면 속도가 빠르다는 의미로 사용될 테니 긍정어라고 볼 수 있다. 하지만 동영상 강의 후기라면 강의 진행 속도나 강사의 말이 빠르다는 의미로 사용될테니 부정어라고 볼 수 있다. 분석하는 텍스트의 맥락에 맞게 단어의 감정 점수를 정해야 정확히 분석할 수 있다.


4-1. 감정 사전 수정

# 새로운 감정 사전
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

4-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>

4-3. 댓글별 감정 점수 계산

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

4-4. 감정 경향 확인

# 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 
# 댓글의 전반적인 감정 경향 비교
# 원본 감정 사전 활용한 댓글의 전반적인 감정 경향
score_comment %>%
  count(sentiment) %>%
  mutate(ratio = n/sum(n)*100)

# 수정한 감정 사전을 활용한 댓글의 전반적인 감정 경향
new_score_comment %>%                              
  count(sentiment) %>%                             
  mutate(ratio = n/sum(n)*100)                    


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! 수정한 사전을 사용하더라도 댓글에 함께 사용된 단어들의 감정 점수가 낮으면 부정 댓글로 분류될 수 있다.


4-5. 감정 범주별 주요 단어 확인

# 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

4-6. 주요 단어가 사용된 댓글 확인

# 긍정 댓글 원문
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! 부정 댓글은 수상 자체보다는 감독의 정치 성향이나 댓글을 단 사용자들의 정치 성향을 비판하는 내용이 많다는 것을 알 수 있다.


5. 신조어에 감정 점수 부여

# 예시 : 신조어가 포함된 문장
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! 두 문장 모두 감정 사전에 없는 단어여서 감정 점수가 부여되지 않았다. 다음과 같이 감정 사전에 신조어와 감정 점수를 추가하면 신조어에도 감정 점수를 부여할 수 있다.

# 신조어 목록 생성
newword <- tibble(word = c("쩐다", "핵노잼"),                  # 신조어 
                  polarity = c(2, -2))                         # 감정 점수

newword
# 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      

Reuse

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 ...".