2 . 정당강령 비교 사례

국민의힘과 더불어민주당 홈페이지에 게재된 정당 강령을 스크랩해 두 문서에 사용된 단어의 빈도를 계산하는 작업. 총빈도, 상대빈도, 감정어 빈도를 계산해 의미를 파악한다.

R을 10시간 정도 학습하면 가능한 작업이다. 만들어 놓은 코드를 재활용하면 1시간 정도에 작업을 마무리할 수 있다. 즉, 1시간 정도면 두세꼭지의 기사를 쓸수 있는 거리가 생긴다.

2.1 패키지

c(
  "tidyverse", # 어지러웠던 R세상을 깔끔하게 정돈. 
# "dplyr",     # tidyverse 부착. 공구상자
# "stringr",   # tidyverse 부착. 문자형자료 처리
  "tidytable", # datatable패키지를 tidy인터페이스로. 빠르고 함수명 혼란 제거
  "rvest",     # 웹 스크래핑 도구
  "janitor",   # 정제 및 기술통계 도구
  "tidytext",  # 텍스트를 깔끔하게 정돈하는 도구
  "RcppMeCab", # 형태소분석기
  "tidylo",    # 상대빈도 분석
  "gt"         # tidy원리를 적용한 표 생성. 
  ) -> pkg 
sapply(pkg, function(x){
  if(!require(x, ch = T)) install.packages(x, dependencies = T)
})
sapply(pkg, require, ch = T)
## tidyverse tidytable     rvest   janitor  tidytext RcppMeCab    tidylo        gt 
##      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE

2.2 자료

2.2.1 본문 스크랩

rvest패키지로 웹사이트에서 자료 스크랩

  • 국민의당
# 코드만 표시
url <- "https://www.peoplepowerparty.kr/renewal/about/preamble.do"
text_css <- "#wrap > div.content-area.about.preamble > div > div.page-content > div.content-box > div > div.line-txt-box > div"

url %>% read_html() %>% 
  html_node(css = text_css) %>% 
  html_text() -> ppp_v
saveRDS(ppp_v, "ppp_v.rds")
  • 더불어민주당
# 코드만 표시
url <- "https://theminjoo.kr/introduce/rule/doct"
text_css <- "#content > div.minjoo_rule.tap_wrap > div.rule_cnt.tap_cnt_box.container_t > div > dl"

url %>% read_html() %>% 
  html_node(css = text_css) %>% 
  html_text() -> tmj_v
saveRDS(tmj_v, "tmj_v.rds")

tidytext패키지와 RcppMeCab패키지를 이용해 품사를 기준으로 토큰화해 데이터프레임으로 저장. 셀 하나당 토큰 하나.

readRDS("ppp_v.rds") %>% 
  tibble(text = .) %>% 
  # 품사로 토큰화
  unnest_tokens(output = word, input = text, token  = pos) %>% 
  separate(col = word, 
           into = c("word", "pos"),
           sep = "/") -> ppp_df
readRDS("tmj_v.rds") %>% 
  tibble(text = .) %>% 
  # 품사로 토큰화
  unnest_tokens(word, text, token  = pos) %>% 
  separate(col = word, 
           into = c("word", "pos"),
           sep = "/") -> tmj_df

2.3 빈도 분석

2.3.1 총 단어

셀 하나에 토큰이 하나씩 저장돼 있으므로 데이터프레임의 행 갯수가 강령에 사용된 단어의 수.

nrow(ppp_df) -> n_ppp
nrow(tmj_df) -> n_tmj
data.table(
  국민의당 = n_ppp,
  더불어민주당 = n_tmj
) %>% gt() %>% 
  tab_header("강령 어휘 수")
강령 어휘 수
국민의당 더불어민주당
6339 11244

2.3.2 상위 빈도 명사

정당별 총 사용 어휘로 나눈 상대빈도 계산. 사용 단어 수가 6천 ~1만이므로, 10000분율로 계산.

# 국민의당 강령 사용 어휘수
ppp_df -> df
n_ppp -> n_total
# 명사 빈도
df %>% 
  # 단어 길이 1개는 분석에서 제외
  filter(str_length(word) > 1) %>% 
  # 명사만 선택
  filter(pos == 'nng') %>% 
  # 단어 빈도 계산해 정렬
  count(word, sort = T) %>% 
  # 1만분률 계산
  mutate(n_bytotal10000 = round(n/n_total * 10000, 0)) %>% 
  head(15) -> top_ppp

# 더불어민주당 강령 사용 어휘수
tmj_df -> df
n_tmj -> n_total
# 명사 빈도
df %>% 
  filter(str_length(word) > 1) %>% 
  filter(pos == 'nng') %>% 
  count(word, sort = T) %>% 
  mutate(n_bytotal10000 = round(n/n_total * 10000, 0)) %>% 
  head(15) -> top_tmj

# 표 하나로 결합
bind_cols(top_ppp, top_tmj) %>% 
  gt() %>% tab_header(
    "상위 빈도 명사"
  ) %>% tab_spanner(
    label = "국민의당",
    columns = 1:3
  ) %>% tab_spanner(
    label = "더불어민주당",
    columns = 4:6
  ) %>% cols_label(
    word...1 = "명사",
    n...2 = "빈도",
    n_bytotal10000...3 = "만분율",
    word...4 = "명사",
    n...5 = "빈도",
    n_bytotal10000...6 = "만분율"
  )
## New names:
## • `word` -> `word...1`
## • `n` -> `n...2`
## • `n_bytotal10000` -> `n_bytotal10000...3`
## • `word` -> `word...4`
## • `n` -> `n...5`
## • `n_bytotal10000` -> `n_bytotal10000...6`
상위 빈도 명사
국민의당 더불어민주당
명사 빈도 만분율 명사 빈도 만분율
사회 58 91 사회 132 117
국민 40 63 강화 109 97
경제 39 62 보장 84 75
제도 36 57 경제 69 61
마련 27 43 교육 63 56
미래 26 41 국민 59 52
강화 22 35 지원 57 51
변화 21 33 지역 56 50
정치 20 32 국가 49 44
환경 20 32 구축 48 43
보장 18 28 문화 48 43
적극 18 28 실현 48 43
교육 17 27 기반 40 36
정책 17 27 협력 39 35
권력 16 25 확대 38 34

2.3.2.1 함께 사용한 단어

dplyr패키지의 inner_join()함수로 두 정당 자료를 결합해 함께 사용한 단어를 추출한다.

# 데이터프레임 공통어 결합
inner_join(
  ppp_df %>% count(word, sort = T),
  tmj_df %>% count(word, sort = T),
  by = c("word")
  ) %>% filter(str_length(word) > 1) %>% 
  # 1만분률 계산
  mutate(ppp_by10000 = round(n.x/n_ppp, 5) * 10000,
         tmj_by10000 = round(n.y/n_tmj, 5) * 10000) %>% 
  arrange(desc(ppp_by10000)) %>% 
  head(15) %>% 
  gt() %>% tab_header(
    "양당이 함께 사용한 단어"
  ) %>% tab_spanner(
    label = "빈도",
    columns = 2:3
  ) %>% tab_spanner(
    label = "만분률",
    columns = 4:5
  ) %>% cols_label(
    word = "단어",
    n.x = "국민의당",
    ppp_by10000 = "국민의당",
    n.y = "더불어민주당",
    tmj_by10000 = "더불어민주당"
  )
양당이 함께 사용한 단어
단어 빈도 만분률
국민의당 더불어민주당 국민의당 더불어민주당
한다 124 301 195.6 267.7
사회 58 132 91.5 117.4
도록 48 34 75.7 30.2
으로 45 74 71.0 65.8
국민 40 59 63.1 52.5
경제 39 69 61.5 61.4
제도 36 28 56.8 24.9
위해 33 62 52.1 55.1
우리 30 10 47.3 8.9
마련 27 20 42.6 17.8
미래 26 11 41.0 9.8
위한 26 31 41.0 27.6
강화 22 113 34.7 100.5
변화 21 9 33.1 8.0
정치 20 20 31.6 17.8

2.3.3 상대빈도

국민의당과 더불어민주당 문서 단어의 상대적인 빈도 계산.

2.3.3.1 상위공통어 중 상대적으로 더 많이 쓴 단어

양당이 함께 많이 사용한 단어 중 각각 국민의당과 더불어민주당 기준으로 정렬해 비교한다.

# 국민의당 기준 공통어 데이터프레임 결합
inner_join(
  ppp_df %>% count(word, sort = T),
  tmj_df %>% count(word, sort = T),
  by = c("word")
  ) %>% filter(str_length(word) > 1) %>% 
  mutate(ppp_by10000 = round(n.x/n_ppp, 5) * 10000,
         tmj_by10000 = round(n.y/n_tmj, 5) * 10000,
  # 사용 어휘 차이 빈도 계산
         diff = ppp_by10000 - tmj_by10000) %>%
  # 차이가 큰 순서를 국민의당기준으로 정렬
  arrange(desc(diff)) %>% 
  head(15) -> com_ppp
# 더불어민주당 기준 공통어 데이터프레임 결합
inner_join(
  ppp_df %>% count(word, sort = T),
  tmj_df %>% count(word, sort = T),
  by = c("word")
  ) %>% filter(str_length(word) > 1) %>% 
  mutate(ppp_by10000 = round(n.x/n_ppp, 5) * 10000,
         tmj_by10000 = round(n.y/n_tmj, 5) * 10000,
  # 사용 어휘 차이 빈도 계산       
         diff = ppp_by10000 - tmj_by10000) %>% 
  # 차이가 큰 순서를 더불어민주당 순서로 정렬
  arrange(diff) %>% 
  head(15) -> com_tmj

# 데이터프레임 결합
bind_cols(
  com_ppp %>% select.(-c(n.x, n.y)), 
  com_tmj %>% select.(-c(n.x, n.y)) 
) %>% gt() %>% tab_header(
  "공동어 중 상대적으로 더 많이 쓴 단어"
  ) %>% tab_spanner(
    label = "국민의당 기준",
    columns = 1:4
  ) %>% tab_spanner(
    label = "더불어민주당 기준",
    columns = 5:8
  ) %>% cols_label(
    word...1 = "명사",
    ppp_by10000...2 = "만분율ppp",
    tmj_by10000...3  = "만분율tmj",
    diff...4 = "차이",
    word...5 = "명사",
    ppp_by10000...6 = "만분율ppp",
    tmj_by10000...7  = "만분율tmj",
    diff...8 = "차이",
  )
## New names:
## • `word` -> `word...1`
## • `ppp_by10000` -> `ppp_by10000...2`
## • `tmj_by10000` -> `tmj_by10000...3`
## • `diff` -> `diff...4`
## • `word` -> `word...5`
## • `ppp_by10000` -> `ppp_by10000...6`
## • `tmj_by10000` -> `tmj_by10000...7`
## • `diff` -> `diff...8`
공동어 중 상대적으로 더 많이 쓴 단어
국민의당 기준 더불어민주당 기준
명사 만분율ppp 만분율tmj 차이 명사 만분율ppp 만분율tmj 차이
도록 75.7 30.2 45.5 한다 195.6 267.7 -72.1
우리 47.3 8.9 38.4 강화 34.7 100.5 -65.8
제도 56.8 24.9 31.9 보장 28.4 74.7 -46.3
미래 41.0 9.8 31.2 지원 18.9 50.7 -31.8
변화 33.1 8.0 25.1 문화 11.0 42.7 -31.7
마련 42.6 17.8 24.8 실현 12.6 42.7 -30.1
는다 20.5 1.8 18.7 지역 20.5 49.8 -29.3
권력 25.2 7.1 18.1 교육 26.8 56.0 -29.2
모두 25.2 8.0 17.2 협력 6.3 34.7 -28.4
개혁 22.1 8.0 14.1 사회 91.5 117.4 -25.9
정치 31.6 17.8 13.8 구축 18.9 42.7 -23.8
세대 18.9 5.3 13.6 국가 20.5 43.6 -23.1
위한 41.0 27.6 13.4 에너지 1.6 24.0 -22.4
기회 25.2 12.5 12.7 참여 3.2 24.9 -21.7
10 14.2 2.7 11.5 체계 6.3 27.6 -21.3

2.3.3.2 문서 전반의 상대빈도

문서 전체의 사용 단어의 빈도를 계산해 두 정당에서 상대적으로 더 많이 사용한 단어가 무엇인지 계산.

tidylo패키지의 bind_log_odds()함수로 계산하는 가중로그승산비를 이용.

  • bind_log_odds(tbl, set, feature, n, uninformative = FALSE, unweighted = FALSE) -tbl: 정돈데이터(feature와 set이 하나의 행에 저장) -set: feature를 비교하기 위한 set(group)에 대한 정보(예: 긍정 vs. 부정)이 저장된 열
  • feature: feature(단어나 바이그램 등의 텍스트자료)가 저장된 열.
  • n: feature-set의 빈도를 저장한 열
  • uninformative: uninformative 디리슐레 분포 사용 여부. 기본값은 FALSE
  • unweighted: 비가중 로그승산 사용여부. 기본값은 FALSE. TRUE로 지정하면 비가중 로그승산비(log_odds) 열을 추가

가중로그승산비에 대한 보다 자세한 설명은 R텍스트마이닝 8.2.4 가중로그승산비 참조

# 행방향 결합. 1 = 국민의당 2 = 더불어민주당
bind_rows(ppp_df, tmj_df, .id = "party")  %>% 
  filter(str_length(word) > 1) %>% 
  count(word, party) %>% 
  bind_log_odds(set = party,
                feature = word, 
                n = n) %>% 
  arrange(-log_odds_weighted) -> weighted_log_odds_df

# 열 결합
bind_cols(
  #국민의당 상대적으로 더 많이 사용한 단어
  weighted_log_odds_df %>%   
  group_by(party = ifelse(party == 1, "ppp", "tmj")) %>% 
  arrange(party) %>% 
  select.(-party) %>%   
  head(15),
  # 더불어민주당이 상대적으로 더 많이 사용한 단어
  weighted_log_odds_df %>%   
  group_by(party = ifelse(party == 1, "ppp", "tmj")) %>% 
  arrange(desc(party)) %>% 
  select.(-party) %>%     
  head(15) 
  ) %>% gt() %>% tab_header(
  "상대적으로 많이 사용한 단어"
  ) %>% tab_spanner(
    label = "국민의당 기준",
    columns = 1:3
  ) %>% tab_spanner(
    label = "더불어민주당 기준",
    columns = 4:6
  ) %>% cols_label(
    word...1 = "명사",
    n...2 = "빈도",
    log_odds_weighted...3 = "가중상대빈도",
    word...4 = "명사",
    n...5 = "빈도",
    log_odds_weighted...6 = "가중상대빈도"
  ) %>% fmt_number(
    columns = starts_with("log"), 
    decimals = 2
  )
## New names:
## • `word` -> `word...1`
## • `n` -> `n...2`
## • `log_odds_weighted` -> `log_odds_weighted...3`
## • `word` -> `word...4`
## • `n` -> `n...5`
## • `log_odds_weighted` -> `log_odds_weighted...6`
상대적으로 많이 사용한 단어
국민의당 기준 더불어민주당 기준
명사 빈도 가중상대빈도 명사 빈도 가중상대빈도
사법 7 2.20 미디어 19 2.50
앞장서 6 2.04 생활 18 2.43
우리 30 1.95 예술 16 2.29
폐지 5 1.86 상생 11 1.90
도록 48 1.79 으로써 11 1.90
미래 26 1.68 포용 11 1.90
경우 4 1.66 당원 10 1.81
내일 4 1.66 여성 10 1.81
대통령 4 1.66 의료 10 1.81
동물 4 1.66 촉진 9 1.72
비리 4 1.66 공동 8 1.62
스스로 4 1.66 소통 8 1.62
앞장선다 4 1.66 임금 8 1.62
인간 4 1.66 탄소 8 1.62
최대한 4 1.66 책무 7 1.52

2.3.4 감정어 빈도

감정어가 포함돼 데이터프레임 사전과 분석대상 문서를 inner_join()한다.

2.3.4.1 감정사전

먼저 간정사전 만들기.

  • KNU한국어감성사전 군산대 소프트웨어융합공학과 Data Intelligence Lab에서 개발한 한국어감정사전이다. 표준국어대사전을 구성하는 각 단어의 뜻풀이를 분석하여 긍부정어를 추출했다.
# 코드만 표시
url_v <- "https://github.com/park1200656/KnuSentiLex/archive/refs/heads/master.zip"
dest_v <- "knusenti.zip"
download.file(url = url_v, 
              destfile = dest_v,
              mode = "wb")
# 압축을 풀면 KnuSentiLex-master 폴더 생성
unzip("knusenti.zip")
# 생성된 폴더내 9번째 파일이 사전파일. 
# 이 파일명을 사전파일 이름 지정
senti_name_v <- list.files("KnuSentiLex-master/.")[9]
# 데이터프레임으로 이입
senti_dic_df <- read_tsv(str_c("data/KnuSentiLex-master/", senti_name_v), col_names = F)
# 데이터프레임 열 이름 변경
senti_dic_df <- senti_dic_df %>% rename(word = X1, sScore = X2)
# 감정값 오류 수정
senti_dic_df %>% 
  filter(!is.na(sScore)) %>% 
  add_row(word = "갈등", sScore = -1) -> senti_dic_df 
# 수정 확인
senti_dic_df %>% 
  filter(!is.na(sScore)) %>% count(sScore)
# 파일로 저장
saveRDS(senti_dic_df, "knu_dic.rds")
# 저장 확인
list.files(pattern = "^knu")

2.3.4.2 단어 빈도

앞서 만든 감정사전 데이터프레임과 분석대상 문서의 데이터프레임을 inner_join()

# 감정사전과 결합
readRDS("knu_dic.rds") -> knu_dic_df
ppp_df %>% inner_join(knu_dic_df) -> emo_ppp
## Joining, by = "word"
tmj_df %>% inner_join(knu_dic_df) -> emo_tmj
## Joining, by = "word"
# 두 정당의 값을 보기 편하게 열방향으로 결합
bind_cols(
  # 국민의당
  emo_ppp %>% 
    count(word, sScore, sort = T) %>% 
    filter(str_length(word) > 1) %>% 
    mutate(word = reorder(word, n)) %>% 
    head(15),
  # 더불어민주당
  emo_tmj %>% 
    count(word, sScore, sort = T) %>% 
    filter(str_length(word) > 1) %>% 
    mutate(word = reorder(word, n)) %>% 
    head(15) 
) %>% gt() %>% tab_header(
  "많이 사용한 감정어"
  ) %>% tab_spanner(
    label = "국민의당",
    columns = 1:3
  ) %>% tab_spanner(
    label = "더불어민주당",
    columns = 4:6
  ) %>% cols_label(
    word...1 = "감정어",
    sScore...2 = "감정점수",
    n...3 = "빈도",
    word...4 = "감정어",
    sScore...5 = "감정점수",
    n...6 = "빈도"
  ) 
## New names:
## • `word` -> `word...1`
## • `sScore` -> `sScore...2`
## • `n` -> `n...3`
## • `word` -> `word...4`
## • `sScore` -> `sScore...5`
## • `n` -> `n...6`
많이 사용한 감정어
국민의당 더불어민주당
감정어 감정점수 빈도 감정어 감정점수 빈도
적극 1 18 발전 1 36
혁신 2 15 혁신 2 31
발전 1 11 적극 1 27
함께 1 11 안정 1 21
행복 2 10 위기 -1 21
안전 2 9 개선 2 19
안정 1 8 가치 1 15
개선 2 6 존중 1 11
존중 1 6 소득 1 10
능력 1 5 안전 2 10
새로운 1 5 함께 1 10
조화 2 5 부담 -2 9
위기 -1 4 새로운 1 9
쾌적 2 4 조화 2 9
가치 1 3 향상 1 9

2.3.4.3 긍정어/부정어 비율

두 정당의 강령에서 긍정어와 부정어의 비율을 계산해 비교.

emo_ppp %>% 
  mutate(감정 = case_when(
    sScore > 0 ~ "긍정",
    sScore < 0 ~ "부정",
    TRUE ~ "중립"
    )) -> emo2_ppp
emo_tmj %>% 
  mutate(감정 = case_when(
    sScore > 0 ~ "긍정",
    sScore < 0 ~ "부정",
   TRUE ~ "중립"
   )) -> emo2_tmj
# 공통결합
inner_join(by = "감정",
    emo2_ppp %>% tabyl(감정) %>% 
    adorn_totals() %>% 
    adorn_pct_formatting(),
    emo2_tmj %>% tabyl(감정) %>% 
    adorn_totals() %>% 
    adorn_pct_formatting()
) %>% gt() %>% tab_header(
  "감정어 비율"
  ) %>% tab_spanner(
    columns = 2:3,
    label = "국민의당"
  ) %>% tab_spanner(
    columns = 4:5,
    label = "더불어민주당"
  ) %>% cols_label(
    n.x = "빈도",
    percent.x = "백분율",
    n.y = "빈도",
    percent.y = "백분율"
  )  
감정어 비율
감정 국민의당 더불어민주당
빈도 백분율 빈도 백분율
긍정 149 57.5% 272 51.1%
부정 84 32.4% 155 29.1%
중립 26 10.0% 105 19.7%
Total 259 100.0% 532 100.0%

2.3.4.4 함께 사용된 긍정어

inner_join(
  emo2_ppp %>% count(word, 감정, sort = T),
  emo2_tmj %>% count(word, 감정, sort = T),
  by = c("word", "감정")
) %>% 
  filter(str_length(word) > 1) %>% 
  mutate(ppp_by10000 = round(n.x/n_ppp, 5) * 10000,
         tmj_by10000 = round(n.y/n_tmj, 5) * 10000) %>% 
  # 감정기준 정렬
  arrange(감정) %>% 
  select.(-감정) %>% 
  head(15) %>% 
  gt() %>% tab_header(
    "양당이 함께 사용한 긍정어"
  ) %>% tab_spanner(
    columns = starts_with("n"),
    label = "빈도"
  ) %>% tab_spanner(
    columns = ends_with("10000"),
    label = "만분률"
  ) %>% cols_label(
    n.x = "ppp",
    n.y = "tmj",
    ppp_by10000 = "ppp",
    tmj_by10000 = "tmj"
  )
양당이 함께 사용한 긍정어
word 빈도 만분률
ppp tmj ppp tmj
적극 18 27 28.4 24.0
혁신 15 31 23.7 27.6
발전 11 36 17.4 32.0
함께 11 10 17.4 8.9
행복 10 8 15.8 7.1
안전 9 10 14.2 8.9
안정 8 21 12.6 18.7
개선 6 19 9.5 16.9
존중 6 11 9.5 9.8
능력 5 4 7.9 3.6
새로운 5 9 7.9 8.0
조화 5 9 7.9 8.0
가치 3 15 4.7 13.3
예방 3 5 4.7 4.4
특권 3 2 4.7 1.8

2.3.4.5 함께 사용된 부정어

# 공통어 결합
inner_join(
  emo2_ppp %>% count(word, 감정, sort = T),
  emo2_tmj %>% count(word, 감정, sort = T),
  by = c("word", "감정")
) %>% 
  filter(str_length(word) > 1) %>% 
  mutate(ppp_by10000 = round(n.x/n_ppp, 5) * 10000,
         tmj_by10000 = round(n.y/n_tmj, 5) * 10000) %>% 
  # 감정기준 정렬
  arrange(desc(감정)) %>% 
  select.(-감정) %>% 
  head(14) %>% 
  gt() %>% tab_header(
    "양당이 함께 사용한 부정어"
  ) %>% tab_spanner(
    columns = starts_with("n"),
    label = "빈도"
  ) %>% tab_spanner(
    columns = ends_with("10000"),
    label = "만분률"
  ) %>% cols_label(
    n.x = "ppp",
    n.y = "tmj",
    ppp_by10000 = "ppp",
    tmj_by10000 = "tmj"
  )
양당이 함께 사용한 부정어
word 빈도 만분률
ppp tmj ppp tmj
위기 4 21 6.3 18.7
부담 3 9 4.7 8.0
피해 3 5 4.7 4.4
갈등 2 6 3.2 5.3
소외 2 1 3.2 0.9
장애 2 8 3.2 7.1
질병 2 1 3.2 0.9
폭력 2 4 3.2 3.6
훼손 2 2 3.2 1.8
벗어나 1 1 1.6 0.9
불신 1 1 1.6 0.9
어려운 1 1 1.6 0.9
의회 1 4 1.6 3.6
재난 1 1 1.6 0.9

2.3.4.6 감정어 상대빈도

# 행방향 결합. 1 = 국민의당 2 = 더불어민주당
bind_rows(emo_ppp, emo_tmj, .id = "party") %>% 
  filter(str_length(word) > 1) %>% 
  count(word, party) %>% 
  bind_log_odds(set = party,
                feature = word, 
                n = n) %>% 
  arrange(-log_odds_weighted) -> weighted_log_odds_df
# 열결합
bind_cols(
  # 국민의당
  weighted_log_odds_df %>%   
  group_by(party = ifelse(party == 1, "ppp", "tmj")) %>% 
  arrange(party) %>% 
  select.(-party) %>%   
  head(15),  
  # 더불어민주당
  weighted_log_odds_df %>%   
  group_by(party = ifelse(party == 1, "ppp", "tmj")) %>% 
  arrange(desc(party)) %>% 
  select.(-party) %>%   
  head(15) 
) %>% gt() %>% tab_header(
  "상대적으로 더 많이 사용한 감정어"
  ) %>% tab_spanner(
    label = "국민의당 기준",
    columns = 1:3
  ) %>% tab_spanner(
    label = "더불어민주당 기준",
    columns = 4:6
  ) %>% cols_label(
    word...1 = "감정어",
    n...2 = "빈도",
    log_odds_weighted...3 = "가중상대빈도",
    word...4 = "감정어",
    n...5 = "빈도",
    log_odds_weighted...6 = "가중상대빈도"
  ) %>% fmt_number(
    columns = starts_with("log"), 
    decimals = 2
  )
## New names:
## • `word` -> `word...1`
## • `n` -> `n...2`
## • `log_odds_weighted` -> `log_odds_weighted...3`
## • `word` -> `word...4`
## • `n` -> `n...5`
## • `log_odds_weighted` -> `log_odds_weighted...6`
상대적으로 더 많이 사용한 감정어
국민의당 기준 더불어민주당 기준
감정어 빈도 가중상대빈도 감정어 빈도 가중상대빈도
쾌적 4 1.64 남용 4 1.18
파괴 3 1.42 장애인 4 1.18
획기적으로 3 1.42 완성 3 1.02
믿음 2 1.15 혐오 3 1.02
범죄 2 1.15 자긍심 2 0.83
재능 2 1.15 개성 1 0.59
가난 1 0.81 기대 1 0.59
명예 1 0.81 두려움 1 0.59
부조리 1 0.81 부정 1 0.59
원동력 1 0.81 불법 1 0.59
인정 1 0.81 불안 1 0.59
전문가 1 0.81 비판 1 0.59
정상 1 0.81 수익 1 0.59
존경 1 0.81 아픔 1 0.59
지나친 1 0.81 역경 1 0.59

2.4 단어 맥락(KWIC)

  • KWIC: KeyWord In Context

특정 단어가 사용된 맥락을 파악하기 위해 특정 단어가 속한 문장 혹은 전후 단어를 함께 추출

2.4.1 단어가 포함된 문장 탐색

crayon패키지로 특정 단어를 장식해 문장의 어느 위치에 사용됐는지 탐색

브라우저가 ANSI코드를 인식

old.hooks <- fansi::set_knit_hooks(knitr::knit_hooks)
library(glue)
library(crayon)
library(fansi)
options(crayon.enabled = TRUE)
crayon_words <- function(input_text, word = " ") {
  replaced_text <- str_replace_all(input_text, word, "{red {word}}")
  for(i in 1:length(replaced_text)) {
    crayon_text <- glue::glue_col(deparse(replaced_text[[i]]))
    print(crayon_text)
  }
}
"국가적 위기 해결에 앞장서야" %>% 
  crayon_words(input_text = ., "해결")
## "국가적 위기 해결에 앞장서야"

먼저 문장 단위로 토큰화.

readRDS("ppp_v.rds") %>% 
  tibble(text = .) %>% 
  unnest_tokens(output = sentence, input = text, 
                token  = "regex", pattern = "\\.") -> ppp_st
readRDS("tmj_v.rds") %>% 
  tibble(text = .) %>% 
  unnest_tokens(sentence, text, 
                token  = "regex", pattern = "\\.") -> tmj_st

여기서는 양당이 함께 감정어로 많이 사용했지만, 더불어민주당이 특히 많이 사용한 ’위기’가 어느 맥락에서 사용됐는지 탐색한다.

국민의당이 기술한 위기

ppp_st %>% 
  filter(str_detect(sentence, "위기")) %>% 
  # 공백문자 및 공백 제거
  mutate(sentence = str_remove_all(sentence, pattern = "\r|\n"),
         sentence = str_squish(sentence)) %>% 
  pull() -> ppp_txt
crayon_words(input_text = ppp_txt, "위기")
## "지금 우리는 세계질서의 대전환과 북한의 핵무장, 지구환경 변화와 거듭되고 있는 질병과 재난, 경제의 질적 변화로 인한 불확실성과 양극화의 심화, 인구절벽 등 중대한 위기 앞에 서 있다"
## "국가적 위기 해결에 앞장서야 할 정치는 국민이 부여한 권한에 대한 책임과 역할을 다하지 못하고, 오히려 국민 분열을 조장하는 등 사회적 혼란과 함께 정치 불신을 심화 시켜 왔다"
## "지방 소멸 위기에 대비하여 각 지역이 지속가능한 경쟁력을 갖추도록 하고 삶의 질을 획기적으로 향상한다"
## "소득․지역․계층에 따른 격차 없이 질병에 대해 적절하게 치료를 받을 수 있는 권리를 보장하고, 감염병 등 공중보건 위기에 효과적으로 대처한다"
## "나라와 국민을 위해 헌신하고 희생하신 분들은 물론 그 가족과 유족에 대해서는 정부가 끝까지 책임지고 보상과 지원을 하도록 국가의 책임을 강화하는 한편, 군인과 보훈가족이 사회적으로 존경받고 귀감이 되도록 예우를 하는 분위기를 조성해 나간다"

더불어민주당이 기술한 위기

tmj_st %>% 
  filter(str_detect(sentence, "위기")) %>% 
  # 공백문자 및 공백 제거
  mutate(sentence = str_remove_all(sentence, pattern = "\r|\n"),
         sentence = str_squish(sentence)) %>% 
  pull() -> tmj_txt
crayon_words(input_text = tmj_txt, "위기") 
## "4차 산업혁명 시대의 디지털 전환과 기후위기로 인한 미래 불확실성이 증대하고 있으며, 팬데믹 이후 전세계적 경제위기는 국민의 삶을 위협하고 사회‧경제적 불안정을 가중시키고 있다"
## "디지털 전환, 기후위기 대응, 탄소중립 실현 등 대전환 시대에 경제적 생산성과 사회적 지속가능성 간의 선순환을 추구하고, 포용성장을 통해 국민 개개인의 역량이 발현될 수 있는 사회적 여건을 구축한다"
## "금융혁신으로 산업 경쟁력과 실물지원 기능을 향상시키고, 불안정한 국제금융질서와 금융위기에 대응하며, 금융시장의 견제와 균형을 회복할 수 있는 관리·감독체계를 마련하여 건전성과 공공성을 확보한다"
## "기후위기에 대응하기 위한 에너지 전환을 지속 추진하고 합리적인 탄소중립전략을 수립한다"
## "농수축협의 활성화, 가격안정을 위한 수급균형과 유통구조 개선, 탄소중립 실현 및 지역순환형 생산소비 시스템 구축 등을 통해 식량자급을 달성하고 기후위기에 대비한다"
## "경제위기를 빌미로 한 무분별한 정리해고의 남용을 방지한다"
## "인구감소와 고령화로 인한 지방소멸의 위기를 극복하기 위해 도시재생 및 농‧산‧어촌의 회생을 추진한다"
## "이를 통해 저출생·고령화, 지방소멸 위기, 지역 양극화를 근본적으로 치유할 수 있는 기반을 강화한다"
## "국민 안전을 책임지는 튼튼한 안보 한반도의 지속적인 평화와 국민 안전을 지키기 위해 신속하고 효율적인 국가위기관리체계를 구축한다"
## "인류의 보편적 가치인 인권, 민주주의 구현과 함께 평화, 반테러, 비핵화, 기후 및 감염병 위기 대응 등을 실현하기 위해 국제사회와 적극 협력하고, 글로벌 선도국가로서의 위상을 확립해 나간다"
## "기후위기 대응, 보건의료협력 등 시대적 요구에 부합하는 남북협력 의제를 설정하여, 평화경제의 새로운 기회를 만들고 남북 공동번영을 도모한다"
## "기후·에너지·환경 지구생태계의 회복과 보전, 특히 기후 위기에 대한 적극 대응과 탄소중립 실현을 위해 노력한다"
## "에너지 취약계층의 에너지 복지를 확대하고 글로벌 공급망 위기대응을 위해 에너지 안보를 제고한다"
## "지속가능한 에너지 전환 달성 기후위기 해결을 위한 핵심 수단이자 에너지 수급의 지속가능성을 높이는 에너지 전환을 지속적으로 추진한다"
## "기후위기 대응과 탄소중립사회 구현기후위기에 적극 대응하며 탄소중립사회로의 신속한 전환을 위해 노력한다"
## "기후위기로 심화되는 불평등과 에너지 빈곤문제 해결을 위해 노력한다"
## "미세먼지, 황사 등 동북아시아 환경문제 해결을 위해 국가 간 협력을 강화하고, 국제사회의 책임 있는 일원으로서 기후위기에 선제적으로 대응한다"
## "팬데믹 시대의 복합적 위기를 극복하고 민주시민 역량을 가진 창의적 인재를 육성하기 위해 교육대전환을 실현한다"
## "기후 변화와 지속적인 감염병 등 다양한 위기로부터 초래된 교육결손을 극복하기 위한 지원을 확대한다"
## "지방대 위기 대응을 위해 지자체·대학·산업체 등이 자원을 결집하여 대학의 교육을 내실화한다"

2.4.2 정규표현식으로 탐색

정규표현식을 이용하면 필요한 부분만 추출해서 볼수 있다.

위기 앞에 임의 문자 . 1개부터 11개 사이 {1,11}를 추출해 표시

ppp_txt %>% 
  str_extract(".{1,11}위기") %>% 
  crayon_words(., "위기") 
## "인구절벽 등 중대한 위기"
## "국가적 위기"
## "지방 소멸 위기"
## "감염병 등 공중보건 위기"
## "도록 예우를 하는 분위기"
tmj_txt %>% 
  str_extract(".{1,11}위기") %>% 
  crayon_words(., "위기") 
## " 디지털 전환과 기후위기"
## "디지털 전환, 기후위기"
## " 국제금융질서와 금융위기"
## "기후위기"
## "자급을 달성하고 기후위기"
## "경제위기"
## "로 인한 지방소멸의 위기"
## "·고령화, 지방소멸 위기"
## "속하고 효율적인 국가위기"
## ", 기후 및 감염병 위기"
## "기후위기"
## " 보전, 특히 기후 위기"
## "하고 글로벌 공급망 위기"
## "너지 전환 달성 기후위기"
## "기후위기"
## "기후위기"
## "있는 일원으로서 기후위기"
## "데믹 시대의 복합적 위기"
## " 감염병 등 다양한 위기"
## "지방대 위기"

더불어민주당은 기후위기에 대한 언급이 두드러지게 많다.

‘기후’ 빈도

tmj_txt %>% 
  str_extract(".{1,10}위기") %>% 
  str_detect("기후") %>% 
  tabyl() %>% 
  gt() %>% 
  tab_header("더불어민주당 위기 중 기후 비중")
더불어민주당 위기 중 기후 비중
. n percent
FALSE 9 0.45
TRUE 11 0.55

2.5 마무리

데이터저널리즘의 본질은 자료의 수집과 분석. 시각화는 부차적인 요소. 빅데이터의 수집과 분석 역시 부차적. 모든 언론인이 늘 특종상을 받을 기사를 써야하는 것은 아니다. 데이터저널리즘도 마찬가지.