layout: true background-image: url(https://user-images.githubusercontent.com/6179259/60290723-50002480-9954-11e9-96fe-3fbd4d7d11d9.png) background-size: cover --- class: center, middle, title-slide, ## 텍스트 분석을 위한 R ### <https://mrchypark.github.io/textR> #### [[pdf버전]](https://github.com/mrchypark/textR/blob/master/docs/textR.pdf) [[문의하기]](http://pf.kakao.com/_RXANd) [[의견 및 오류 신고]](https://github.com/mrchypark/textR/issues/new) #### [스타누르기](https://github.com/mrchypark/textR)는 컨텐츠 제작자를 춤추게 합니다. ### 박찬엽 ### 2018년 09월 18일.small[(update: 2019년 7월 1일)] --- class: center, middle, title-slide, ## 텍스트 관련 R 패키지 설치 가이드 ### <https://mrchypark.github.io/textR/installation> ### [pdf 다운로드](https://github.com/mrchypark/textR/blob/master/docs/installation.pdf) .footnote[\* R 3.6.0 을 기준으로 작성하였습니다.] --- class: center, middle, title-slide, # 사전 지식 ## tidyverse <img src=https://github.com/tidyverse/tidyverse/raw/master/man/figures/logo.png align="center"> --- ### tidyverse 패키지는 1. RStudio가 개발, 관리하는 패키지 1. 공식 문서가 매우 잘 되어 있음 1. 사용자층이 두터워 영어로 검색하면 많은 질답을 찾을 수 있음 1. 커뮤니티 설명글도 매우 많음 1. 6개의 핵심 패키지 포함 23가지 패키지로 이루어진 메타 패키지 1. tidy data 라는 사상과 파이프 연산자로 대동단결 1. 사상에 영감을 받아 맞춰서 제작하는 개인 패키지가 많음(ex> [tidyquant](https://github.com/business-science/tidyquant), [tidytext](https://github.com/juliasilge/tidytext) 등) ```r if (!requireNamespace("tidyverse")) { install.packages("tidyverse")} library(tidyverse) ``` --- class: center, middle, title-slide, ### 파이프 연산자(%>%) ![](https://raw.githubusercontent.com/mrchypark/dabrp_classnote3/master/docs/img/pipes.png) --- ### 파이프 연산자(%>%) 함수를 중첩해서 사용할 일이 점점 빈번해 짐 ```r plot(diff(log(sample(rnorm(10000, mean = 10, sd = 1), size = 100, replace = FALSE))), col = "red", type = "l") ``` **%>%를 사용하면** 1. 생각의 순서대로 함수를 작성할 수 있음 1. 중간 변수 저장을 할 필요가 없음 1. 순서가 읽이 용이하여 기억하기 좋음 ```r rnorm(10000,mean=10,sd=1) %>% sample(size=100,replace=FALSE) %>% log %>% diff %>% plot(col="red",type="l") ``` --- ### 파이프 연산자(%>%) flights 데이터에 파이프 연산자 사용예 1 ```r flights %>% group_by(year,month,day) %>% summarise(delay = mean(dep_delay, na.rm = TRUE)) ``` ``` ## # A tibble: 365 x 4 ## # Groups: year, month [12] ## year month day delay ## <int> <int> <int> <dbl> ## 1 2013 1 1 11.5 ## 2 2013 1 2 13.9 ## 3 2013 1 3 11.0 ## 4 2013 1 4 8.95 ## 5 2013 1 5 5.73 ## 6 2013 1 6 7.15 ## 7 2013 1 7 5.42 ## 8 2013 1 8 2.55 ## 9 2013 1 9 2.28 ## 10 2013 1 10 2.84 ## # ... with 355 more rows ``` --- ### 파이프 연산자(%>%) group_by()는 filter()와도 함께 사용할 수 있음 ```r flights %>% group_by(dest) %>% filter(n() > 365) -> popular_dests popular_dests ``` ``` ## # A tibble: 332,577 x 19 ## # Groups: dest [77] ## year month day dep_time sched_dep_time dep_delay arr_time ## <int> <int> <int> <int> <int> <dbl> <int> ## 1 2013 1 1 517 515 2 830 ## 2 2013 1 1 533 529 4 850 ## 3 2013 1 1 542 540 2 923 ## 4 2013 1 1 544 545 -1 1004 ## 5 2013 1 1 554 600 -6 812 ## 6 2013 1 1 554 558 -4 740 ## 7 2013 1 1 555 600 -5 913 ## 8 2013 1 1 557 600 -3 709 ## 9 2013 1 1 557 600 -3 838 ## 10 2013 1 1 558 600 -2 753 ## # ... with 332,567 more rows, and 12 more variables: sched_arr_time <int>, ## # arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>, ## # origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>, ## # minute <dbl>, time_hour <dttm> ``` --- ### 파이프 연산자(%>%) 사용할 데이터부터 순서대로 함수를 작성할 수 있는 장점 ```r popular_dests %>% filter(arr_delay > 0) %>% mutate(prop_delay = arr_delay / sum(arr_delay)) %>% select(year:day, dest, arr_delay, prop_delay) ``` ``` ## # A tibble: 131,106 x 6 ## # Groups: dest [77] ## year month day dest arr_delay prop_delay ## <int> <int> <int> <chr> <dbl> <dbl> ## 1 2013 1 1 IAH 11 0.000111 ## 2 2013 1 1 IAH 20 0.000201 ## 3 2013 1 1 MIA 33 0.000235 ## 4 2013 1 1 ORD 12 0.0000424 ## 5 2013 1 1 FLL 19 0.0000938 ## 6 2013 1 1 ORD 8 0.0000283 ## 7 2013 1 1 LAX 7 0.0000344 ## 8 2013 1 1 DFW 31 0.000282 ## 9 2013 1 1 ATL 12 0.0000400 ## 10 2013 1 1 DTW 16 0.000116 ## # ... with 131,096 more rows ``` --- ### 단정한 데이터(tidy data) 1. [Hadley Wickham](https://cran.r-project.org/web/packages/tidyr/vignettes/tidy-data.html) 2. [고감자님의 블로그](http://freesearch.pe.kr/archives/3942) 3. [헬로우데이터과학](http://www.hellodatascience.com/?p=287) 1.1 Each variable forms a column. 1.2 각 변수는 개별의 열(column)으로 존재한다. 1.3 각 열에는 개별 속성이 들어간다. 2.1 Each observation forms a row. 2.2 각 관측치는 행(row)를 구성한다. 2.3 각 행에는 개별 관찰 항목이 들어간다. 3.1 Each type of observational unit forms a table. 3.2 각 테이블은 단 하나의 관측기준에 의해서 조직된 데이터를 저장한다. 3.3 각 테이블에는 단일 유형의 데이터가 들어간다. \* 출처 : [금융데이터 분석을 위한 R 입문][2] --- ### 단정한 데이터(tidy data) ![](http://garrettgman.github.io/images/tidy-1.png) \* 출처 : [Garrett Grolemund의 Data Science with R 블로그](http://garrettgman.github.io/tidying/) --- ### long form과 wide form .pull-left[ ### long form 1. 컴퓨터가 계산하기 좋은 모양 1. tidy data의 요건을 충족 1. tidyverse의 패키지 대부분의 입력 형태 ] .pull-right[ ### wide form 1. 사람이 눈으로 보기 좋은 모양 1. 2개 변수에 대한 값만 확인 가능 1. dashboard 형이라고도 하며 조인 등 연산이 어려움 ] --- ## 데이터 패키지(presidentSpeech) 소개 대통령 기록 연구실의 대통령 연설문을 제공([패키지 설명](https://forkonlp.github.io/presidentSpeechKr/)) ```r if (!requireNamespace("presidentSpeech")) { remotes::install_github("forkonlp/presidentSpeech") } library(presidentSpeech) ``` --- ### 조건 확인 함수 **대통령 조건 확인** ```r get_president() ``` ``` ## [1] "이승만" "윤보선" "박정희" "최규하" "전두환" "노태우" "김영삼" ## [8] "김대중" "노무현" "이명박" ``` **연설 분야 조건 확인** ```r get_field() ``` ``` ## [1] "국정전반" "정치/사회" "산업/경제" "외교/통상" ## [5] "국방" "과학기술정보" "교육" "문화/체육/관광" ## [9] "환경" "기타" ``` --- **연설 유형 확인** ```r get_event() ``` ``` ## [1] "취임사" "신년사" "국회연설" "기념사" "만찬사" ## [6] "환영사" "치사" "성명/담화문" "라디오연설" "기타" ``` **연설 리스트 데이터** ```r library(dplyr) data(spidx) glimpse(spidx) ``` ``` ## Observations: 6,681 ## Variables: 6 ## $ president <chr> "이승만", "이승만", "이승만", "이승만", "이승만", "이승만", "이승만", "이승... ## $ field <chr> "기타", "국정전반", "정치/사회", "국정전반", "정치/사회", "정치/사회", "국정... ## $ event <chr> "성명/담화문", "취임사", "성명/담화문", "기타", "성명/담화문", "성명/담화문",... ## $ title <chr> "학생제군에게", "대통령 취임사(大統領就任辭) ", "민족이 원하는 길을 따를 결심, 국무총... ## $ date <chr> "1948", "1948.07.24", "1948.07.29", "1948.08.09", "1... ## $ link <chr> "http://www.pa.go.kr/research/contents/speech/index.... ``` --- **데이터를 사용한 필터링 예시** ```r library(dplyr) spidx %>% filter(president == "윤보선") ``` ``` ## # A tibble: 3 x 6 ## president field event title date link ## <chr> <chr> <chr> <chr> <chr> <chr> ## 1 윤보선 국정전반~ 취임사 제2대 윤보선 대통령 취임사~ 1960.~ http://www.pa.go.kr/res~ ## 2 윤보선 기타 기타 "윤보선 대통령 부산연설 \"~ 1960.~ http://www.pa.go.kr/res~ ## 3 윤보선 기타 기타 "윤보선 대통령 대구연설 \"~ 1960.~ http://www.pa.go.kr/res~ ``` --- ### 연설문 텍스트 가져오기 ```r library(dplyr) spidx %>% filter(president == "윤보선") %>% top_n(1, wt = desc(date)) %>% pull(link) -> tar get_speech(tar) ``` ``` ## # A tibble: 1 x 9 ## title date president place field event source paragraph content ## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <int> <chr> ## 1 제2대 윤보~ 1960.~ 윤보선 국내 국정전반~ 취임사~ "" 1 "제2공화국의 초대대통~ ``` --- ### 연습문제 1. presidentSpeech 패키지에서 검색할 수 있는 대통령은 총 몇명인가요? 1. **윤보선** 대통령과 **박정희** 대통령은 각각 몇 개의 연설문이 있나요? 1. `nchar()` 함수는 글자수를 세주는 함수입니다. **최규하** 대통령의 취임사는 총 몇 글자 인가요? --- # 단정하게 텍스트를 다루는 tidytext ## tidy text data 단정한 데이터 원칙을 아래 문장과 함께 적용한다. * a table with one-token-per-row * 한 행(row)에 한 토큰(token)으로 테이블을 구성해야 한다. **Token 이란?** 글자 중 의미를 가진 단위를 총칭. tokenization은 가지고 있는 텍스트 자원을 token 단위로 나누는 것을 뜻함. ex> 자소(자음, 모음), 음소(글자), 형태소, 단어, n-gram 등 --- ## tidytext 패키지 .pull-left[ * [tidytext][tidytext] * 한 행(row)에 한 토큰(token)으로 테이블을 구성하기 위한 패키지 * 파이프 연산자를 지원 * 여러 가지 token과 tm 패키지와의 호환 기능을 제공 * 자세히 소개하는 [온라인 사이트(영문)][tidytextmining] * 한글 번역본이 출간됨 **설치하기** ```r if (!requireNamespace("tidytext")) { install.packages("tidytext") } library(tidytext) ``` ] .pull-right[ <img src=https://www.tidytextmining.com/images/cover.png width=70%> ] --- ### token 단위 처리 `unnest_tokens()` 기본값인 단어 단위(특수문자 제거, 띄어쓰기 기준) token으로 동작. **함수 설명** `unnest_tokens()` 함수는 텍스트 데이터를 token 단위로 풀어내는 동작을 수행 ```r unnest_tokens( # 다루고자 하는 텍스트 데이터 객체 tbl = 텍스트 데이터, # token화의 결과가 작성될 열의 이름 output = 결과열의 이름, # 텍스트 데이터 객체 내의 텍스트 열 input = 목표 텍스트 열, # 기본값(words 단위 = 띄어쓰기 단위)이 있어 생략 가능 token = "words", # 기타 옵션들 ... ) ``` --- 예시 코드 ```r tar ``` ``` ## [1] "http://www.pa.go.kr/research/contents/speech/index.jsp?spMode=view&catid=c_pa02062&artid=1310437" ``` ```r # 연설문 중 1개를 가져와서 get_speech(tar) %>% # 대통령 컬럼과 연설문 컬럼만 선택한 후 select(president, content) %>% # 연설문 컬럼을 띄어쓰기 단위로 쪼갠 결과물을 word라는 컬럼으로 출력 unnest_tokens( input = content, output = word ) ``` ``` ## # A tibble: 483 x 2 ## president word ## <chr> <chr> ## 1 윤보선 제 ## 2 윤보선 2 ## 3 윤보선 공화국의 ## 4 윤보선 초대대통령으로 ## 5 윤보선 영예의 ## 6 윤보선 당선을 ## 7 윤보선 얻은 ## 8 윤보선 어제 ## 9 윤보선 나의 ## 10 윤보선 감격은 ## # ... with 473 more rows ``` --- ### 연습문제 1. **김영삼** 대통령의 첫 국무회의 연설문을 띄어쓰기 단위로 자르면 총 몇 단어인가요? --- .pull-left[ * 띄어쓰기 단위로 나눴을 때 문제점 **하다**가 몇 가지 단어가 되는지 ] <https://namu.wiki/w/파일:M4nNWBR.png> .pull-right[ <img src=https://user-images.githubusercontent.com/6179259/45862373-5d2c2980-bdac-11e8-9247-3ebde14583e8.png width=70%> ] --- # 형태소 분석 ## 한글의 특징 형태소 형태소란 의미를 가지는 최소 단위 > 철수가 밥을 먹었다. ``` ## $철수가 ## [1] "철수/ncpa+가/jcc" "철수/ncpa+가/jcs" ## ## $밥을 ## [1] "밥/ncn+을/jco" "밥/ncpa+을/jco" "밥/ncps+을/jco" ## ## $먹었다 ## [1] "먹/pvg+었/ep+다/ef" ## ## $. ## [1] "./sf" "./sy" ``` --- **형태소 분석기의 형태소 품사** / [전체 보기](https://github.com/haven-jeon/KoNLP/blob/master/etcs/figures/konlp_tags.png?raw=true) / [여러 체계의 형태소 품사](https://docs.google.com/spreadsheets/d/1OGAjUvalBuX-oZvZ_-9tEfYD2gQe7hTGsgUpiiBSXI8/edit#gid=0) ![](https://github.com/haven-jeon/KoNLP/blob/master/etcs/figures/konlp_tags.png?raw=true) --- ## 형태소 분석기 ### R의 대표적인 형태소 분석기 .pull-left[ **RcppMeCab** 1. 일본어 형태소 분석기인 mecab 기반 1. C++ 로 작성하여 속도가 매우 빠름 1. 일본어, 중국어 등도 사용 가능 1. 형태소 분석 함수를 제공 1. 띄어쓰기에 덜 민감함 ] .pull-right[ **KoNLP** 1. 가장 유명한 형태소 분석기 1. java로 작성된 한나눔 분석기 기반 1. 우리샘, NiaDIC 등 자체 사전 1. 텍스트 분석을 위한 기능들을 제공 1. 친절한 [설명서](https://github.com/haven-jeon/KoNLP/blob/master/etcs/KoNLP-API.md) ] --- **RcppMeCab 설치 확인** ```r library(RcppMeCab) pos(iconv("롯데마트가 판매하고 있는 흑마늘 양념 치킨이 논란이 되고 있다.", to = "utf8")) ``` ``` ## $`롯데마트가 판매하고 있는 흑마늘 양념 치킨이 논란이 되고 있다.` ## [1] "롯데마트/NNP" "가/JKS" "판매/NNG" "하/XSV" ## [5] "고/EC" "있/VX" "는/ETM" "흑/NNG" ## [9] "마늘/NNG" "양념/NNG" "치킨/NNG" "이/JKS" ## [13] "논란/NNG" "이/JKS" "되/VV" "고/EC" ## [17] "있/VX" "다/EF" "./SF" ``` --- **KoNLP 설치 확인** ```r library(KoNLP) SimplePos09("롯데마트가 판매하고 있는 흑마늘 양념 치킨이 논란이 되고 있다.") ``` ``` ## $롯데마트가 ## [1] "롯데마트가/N" ## ## $판매하고 ## [1] "판매/N+하고/J" ## ## $있는 ## [1] "있/P+는/E" ## ## $흑마늘 ## [1] "흑마늘/N" ## ## $양념 ## [1] "양념/N" ## ## $치킨이 ## [1] "치킨/N+이/J" ## ## $논란이 ## [1] "논란/N+이/J" ## ## $되고 ## [1] "되/P+고/E" ## ## $있다 ## [1] "있/P+다/E" ## ## $. ## [1] "./S" ``` --- ### 생각해보기 1. **노태우** 대통령의 취임사를 `RcppMeCab` 패키지의 `pos()` 함수로 형태소 분석한 결과를 출력하세요. 1. **김대중** 대통령의 취임사를 `KoNLP` 패키지의 `SimplePos09()` 함수로 형태소 분석한 결과를 출력하세요. --- ### 형태소로 token화 #### 데이터 준비 ```r library(tidytext) library(dplyr) library(stringr) library(presidentSpeech) spidx %>% filter(president == "이명박") %>% filter(str_detect(title, "취임사")) %>% pull(link) -> tar ``` --- 연설문 가져와서 word(띄어쓰기) 단위로 나누기 ```r # 연설문 중 1개를 가져와서 get_speech(tar, paragraph = T) %>% # 문단 컬럼과 연설문 컬럼만 선택한 후 select(paragraph, content) %>% # 연설문 컬럼을 word 단위로 쪼갠 결과물을 word라는 컬럼으로 출력 unnest_tokens( input = content, output = word ) ``` ``` ## # A tibble: 2,019 x 2 ## paragraph word ## <int> <chr> ## 1 1 존경하는 ## 2 1 국민 ## 3 1 여러분 ## 4 2 700 ## 5 2 만 ## 6 2 해외동포 ## 7 2 여러분 ## 8 3 이 ## 9 3 자리에 ## 10 3 참석하신 ## # ... with 2,009 more rows ``` --- 형태소 분석기와 함께 사용하기 ```r unnest_tokens( tbl = 텍스트 데이터, input = 목표 텍스트 열, output = 결과열의 이름, token = "words", <- 여기에 형태소 분석 함수를 적용 ... ) ``` --- KoNLP의 `SimplePos09()` 함수를 활용해서 형태소 단위로 쪼갠 데이터를 만듭니다. ```r library(KoNLP) # 연설문 중 1개를 가져와서 get_speech(tar, paragraph = T) %>% # 문단 컬럼과 연설문 컬럼만 선택한 후 select(paragraph, content) %>% # 연설문 컬럼을 형태소 단위로 쪼개 # pos라는 컬럼으로 출력 unnest_tokens(pos, content, token = SimplePos09) %>% # pos 결과물의 순서 보장을 위해 순서 값을 추가 mutate(pos_order = 1:n()) -> pos_res pos_res ``` ``` ## # A tibble: 2,216 x 3 ## paragraph pos pos_order ## <int> <chr> <int> ## 1 1 존경/n+하/x+는/e 1 ## 2 1 국민/n 2 ## 3 1 여러분/n 3 ## 4 1 !/s 4 ## 5 2 700/n+만/j 5 ## 6 2 해외동포/n 6 ## 7 2 여러분/n 7 ## 8 2 !/s 8 ## 9 3 이/m 9 ## 10 3 자리/n+에/j 10 ## # ... with 2,206 more rows ``` --- ### 불용어 제거 #### 필요한 형태소 정보만 선택 신뢰할 수 있는 stop word 사전 등이 없기 때문에, 형태소 분석 후 필요한 형태소만 활용. ```r library(stringr) pos_res %>% # 우선 `filter()` 와 `str_detect()` 함수를 활용하여 명사(n)만 추출 filter(str_detect(pos, "/n")) %>% # 형태소 정보를 제거 mutate(pos_done = str_remove(pos, "/.*$")) -> n_done n_done ``` ``` ## # A tibble: 1,325 x 4 ## paragraph pos pos_order pos_done ## <int> <chr> <int> <chr> ## 1 1 존경/n+하/x+는/e 1 존경 ## 2 1 국민/n 2 국민 ## 3 1 여러분/n 3 여러분 ## 4 2 700/n+만/j 5 700 ## 5 2 해외동포/n 6 해외동포 ## 6 2 여러분/n 7 여러분 ## 7 3 자리/n+에/j 10 자리 ## 8 3 참석/n+하/x+시ㄴ/e 11 참석 ## 9 3 노무현ㆍ김대중ㆍ김영삼ㆍ전두환/n~ 12 노무현ㆍ김대중ㆍ김영삼ㆍ전두환~ ## 10 3 전/n 13 전 ## # ... with 1,315 more rows ``` --- #### 명사, 형용사, 동사 가져오기 명사는 n, 동사/형용사는 p로 표시. 형태소 분석 후 한 글자는 전후 맥락 없이 의미를 파악하기 어렵기 때문에 제거 .pull-left[ ```r pos_res %>% filter(str_detect(pos, "/p")) %>% mutate( pos_done = str_replace_all(pos, "/.*$", "다") ) -> p_done bind_rows(n_done, p_done) %>% arrange(pos_order) %>% filter(nchar(pos_done) > 1) %>% select(paragraph, pos_done) -> pos_done pos_done ``` ] .pull-right[ ``` ## # A tibble: 1,647 x 2 ## paragraph pos_done ## <int> <chr> ## 1 1 존경 ## 2 1 국민 ## 3 1 여러분 ## 4 2 700 ## 5 2 해외동포 ## 6 2 여러분 ## 7 3 자리 ## 8 3 참석 ## 9 3 노무현ㆍ김대중ㆍ김영삼ㆍ전두환 ## 10 3 대통령 ## # ... with 1,637 more rows ``` ] --- #### 함께 사용한 함수 설명 **`str_detect()`** `str_detect()` 함수는 글자 데이터 내에 찾고자 하는 글자가 있는 지를 T/F로 알려줌 ```r str_detect( string = 글자 데이터, pattern = 찾고자 하는 글자, negate = FALSE # 조건에 맞는 경우 or 그 반대의 결과를 받을 것을 지정 ) ``` **`str_replace_all()`** `str_replace_all()` 함수는 찾고자 하는 글자를 원하는 글자로 바꿔줌. ```r str_replace_all( string = 글자 데이터, pattern = 찾고자 하는 글자, replacement = 찾은 글자가 바뀌게 될 글자 ) ``` --- **`str_length()`** `str_length()`는 글자 데이터를 받아서 글자수를 알려줌 ```r str_count( string = 세고자 하는 글자 ) ``` --- ### 정규 표현식 글자를 다루는데 유용한 기능을 제공 - `^` : 이걸로 시작함 - `$` : 이걸로 끝남 - `.` : 임의의 글자 하나 - `?` : 앞에 있는 문자가 없거나 하나 - `+` : 앞에 있는 문자가 하나 이상 - `*` : 앞에 있는 문자가 없거나 하나 이상 참고 : <https://mrchypark.github.io/dabrp_classnote3/class4> --- ### 연습문제 1. 아래 코드로 이명박 대통령 연설문 중 10개 를 가져오세요. *stringr 패키지의 함수를 연습합니다.* 1. `str_detect()` 함수를 사용해서 제목(title 컬럼)에 `나눔` 글자가 포함되어 있는 연설문을 찾으세요. 1. `str_replace_all()` 함수를 사용해서 제목(title 컬럼)에 `인터넷 연설` 글자를 없애보세요. *아래 문제부터는 다시 tar를 사용해주세요. 명사만 가져오는 과정을 연습합니다.* 1. `mutate()` 함수를 사용해서 연설문id를 1~10까지 id 컬럼으로 추가하세요. 1. `id`와 `content` 컬럼만 선택하세요. 1. 형태소 분석을 하여 결과를 pos 컬럼으로 추가하세요. 1. `str_detect()` 함수를 사용해서 명사만 남겨보세요. 1. `str_replace_all()` 함수를 사용해서 POS 정보를 지우고 한글만 남기세요. ```r library(presidentSpeech) library(magrittr) library(tidyverse) try_speech <- insistently(get_speech) spidx %>% filter(president == "이명박") %>% arrange(date) %>% top_n(10) %$% map_dfr(link, try_speech) -> tar ``` ``` ## Selecting by link ``` ```r tar ``` ``` ## # A tibble: 10 x 9 ## title date president place field event source paragraph content ## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <int> <chr> ## 1 제98차 라디오~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 2 제99차 라디오~ "" 이명박 국내 기타 라디오연~ "" 1 안녕하십니까, 대~ ## 3 제100차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 사회자(KBS 아~ ## 4 제101차 라디~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 5 제102차 라디~ "" 이명박 국내 과학기술/~ 라디오연~ "" 1 안녕하십니까, 대~ ## 6 제103차 라디~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 7 제104차 라디~ "" 이명박 국내 정치/사회~ 라디오연~ "" 1 안녕하십니까, 대~ ## 8 제105차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ## 9 제106차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ## 10 제107차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ``` --- # 텍스트 마이닝 지표 1. 단어 출현 빈도 : 단순히 단어가 나타난 횟수를 세서 확인 1. 동시 출현 빈도 : 기준 단어와 함께 나타난 단어들과 그 횟수를 세서 확인 1. tf-idf : 전체 문서에서 나타난 횟수와 개별 문서에서 나타난 횟수로 만든 지표 <img src=https://user-images.githubusercontent.com/6179259/47669547-78c9f180-dbee-11e8-85e8-e01cb4cbe46d.png class=center width=100%> 1. 감성 분석 : 단어를 점수화한 감성사전을 사용하여 점수를 합산하여 만든 지표 --- ## 단어 출현 빈도 ### 단어 출현 빈도 계산 `count()` 함수는 데이터에서 총 몇 번 나왔는지 세어주는 집계함수. `group_by()`와 함께 사용하여 각 연설문별 출현 횟수 등을 구할 수 있음. ```r library(dplyr) pos_done %>% count(pos_done, sort = T) -> wn wn ``` ``` ## # A tibble: 883 x 2 ## pos_done n ## <chr> <int> ## 1 하다 94 ## 2 있다 38 ## 3 국민 30 ## 4 우리 26 ## 5 여러분 18 ## 6 대한민국 17 ## 7 되다 16 ## 8 만들다 13 ## 9 가다 12 ## 10 사회 12 ## # ... with 873 more rows ``` --- ### 사용예 : 워드클라우드 `count()` 함수로 단어와 그 빈도 테이블을 만들었다면, `{wordcloud}` 패키지를 사용해서 워드클라우드를 만들 수 있음 `{showtext}` 패키지를 출력 결과물의 폰트를 설정하기 위한 패키지로 [Google Fonts](https://fonts.google.com/)에서 폰트 데이터를 받아와서 출력물에 사용할 수 있음. ```r library(wordcloud) library(showtext) font_add_google("Noto Sans", "notosans") showtext_auto() wn %>% with(wordcloud(pos_done, n, family = "notosans")) ``` ![](index_files/figure-html/unnamed-chunk-32-1.png)<!-- --> --- **빈도에 따른 색 입히기** <https://github.com/EmilHvitfeldt/r-color-palettes> 에 R에서 사용할 수 있는 색 테마 패키지들을 소개하고 있음. ```r # install.packages("Redmonder") library(Redmonder) pal = redmonder.pal(6, "sPBIRdPu") wn %>% with(wordcloud(pos_done, n, family = "notosans", colors = pal)) ``` ![](index_files/figure-html/unnamed-chunk-33-1.png)<!-- --> --- ### 연습문제 ```r library(presidentSpeech) library(magrittr) library(tidyverse) try_speech <- insistently(get_speech) spidx %>% filter(president == "이명박") %>% arrange(date) %>% top_n(10) %$% map_dfr(link, try_speech) -> tar ``` ``` ## Selecting by link ``` ```r tar ``` ``` ## # A tibble: 10 x 9 ## title date president place field event source paragraph content ## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <int> <chr> ## 1 제98차 라디오~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 2 제99차 라디오~ "" 이명박 국내 기타 라디오연~ "" 1 안녕하십니까, 대~ ## 3 제100차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 사회자(KBS 아~ ## 4 제101차 라디~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 5 제102차 라디~ "" 이명박 국내 과학기술/~ 라디오연~ "" 1 안녕하십니까, 대~ ## 6 제103차 라디~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 7 제104차 라디~ "" 이명박 국내 정치/사회~ 라디오연~ "" 1 안녕하십니까, 대~ ## 8 제105차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ## 9 제106차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ## 10 제107차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ``` --- ### 연습문제 1. `tar`의 `content` 컬럼을 `pos()` 함수로 형태소 분석을 진행해주세요. 1. 그 중 명사만 남기고, 형태소 정보는 지워주세요. 한글자 명사도 지워주세요. 1. `count()` 함수를 이용해서 단어 출현 빈도를 계산해 주세요. 1. wordcloud를 만들어 주세요. 1. 다른 색 조합으로 시도해 주세요. 1. `group_by()`를 활용하여 각 연설문 별로 단어 출현 빈도를 계산해주세요. 1. 각 연설문에서 "우리"가 몇 번 사용되었는지 확인해주세요. --- ## 동시 출현 빈도 ### 동시 출현 빈도 계산 `pairwise_count()` 함수는 그룹 단위 내에서 단어가 동시에 출현한 횟수를 세어주는 함수. 보통 문장 단위를 그룹으로 처리 .pull-left[ ```r # install.packages("widyr",dependencies = T) library(widyr) library(KoNLP) tar %>% unnest_tokens(sent, content, token = "sentences") %>% mutate(id = as.numeric(1:n())) %>% unnest_tokens(pos, sent, token = SimplePos09) %>% select(id, pos) %>% filter(str_detect(pos, "/n|/v(v|a)")) %>% mutate(pos = str_remove_all(pos, "/.*$")) %>% filter(nchar(pos) > 1)%>% pairwise_count(pos, id, sort = T, upper = F) -> pw ``` ] .pull-right[ ``` ## # A tibble: 15,967 x 3 ## item1 item2 n ## <chr> <chr> <dbl> ## 1 국민 여러분 50 ## 2 국민 사랑 22 ## 3 여러분 사랑 22 ## 4 국민 우리 19 ## 5 생각 우리 14 ## 6 우리 세계 14 ## 7 우리 나라 12 ## 8 안녕 대통령 10 ## 9 우리 사실 10 ## 10 우리 정부 10 ## # ... with 15,957 more rows ``` ] --- ### 패키지 설치 및 함수 사용법 ```r install.packages("widyr" ,dependencies = T) pairwise_count( tbl = 대상 데이터, item = 갯수를 새어야 할 컬럼, feature = 함께 출현했다고 판단할 단위 그룹, sort = 출현 횟수 단위로 정렬할지 ) ``` --- ### 기준 단어로 데이터 탐색 `filter()` 함수로 기준 단어를 조회하면 함께 자주 나오는 단어와 그 빈도를 확인할 수 있음 ```r pw %>% filter(item1 == "우리") ``` ``` ## # A tibble: 648 x 3 ## item1 item2 n ## <chr> <chr> <dbl> ## 1 우리 세계 14 ## 2 우리 나라 12 ## 3 우리 사실 10 ## 4 우리 정부 10 ## 5 우리 이번 8 ## 6 우리 대한민국 8 ## 7 우리 경제 8 ## 8 우리 개발 7 ## 9 우리 모두 6 ## 10 우리 국가 6 ## # ... with 638 more rows ``` --- ```r library(forcats) library(ggplot2) # bar plot pw %>% filter(item1 %in% c("우리")) %>% top_n(15) %>% mutate(item2 = fct_reorder(item2, n, .desc = TRUE)) %>% ggplot(aes(x = item2, y = n, fill = item1)) + geom_bar(stat = "identity") ``` ``` ## Selecting by n ``` ![](index_files/figure-html/unnamed-chunk-39-1.png)<!-- --> --- ### 연습문제 ```r library(presidentSpeech) library(magrittr) library(tidyverse) try_speech <- insistently(get_speech) spidx %>% filter(president == "이명박") %>% arrange(date) %>% top_n(10) %$% map_dfr(link, try_speech) -> tar ``` ``` ## Selecting by link ``` ```r tar ``` ``` ## # A tibble: 10 x 9 ## title date president place field event source paragraph content ## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <int> <chr> ## 1 제98차 라디오~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 2 제99차 라디오~ "" 이명박 국내 기타 라디오연~ "" 1 안녕하십니까, 대~ ## 3 제100차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 사회자(KBS 아~ ## 4 제101차 라디~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 5 제102차 라디~ "" 이명박 국내 과학기술/~ 라디오연~ "" 1 안녕하십니까, 대~ ## 6 제103차 라디~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 7 제104차 라디~ "" 이명박 국내 정치/사회~ 라디오연~ "" 1 안녕하십니까, 대~ ## 8 제105차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ## 9 제106차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ## 10 제107차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ``` --- ### 연습문제 1. 앞장의 코드를 이용해서 `tar` 데이터를 만들어주세요. 1. `tar`의 `content` 컬럼을 문장 단위로 나누어 주세요. 1. 새롭게 문장별 id를 `id` 컬럼으로 추가해주세요. 1. 문장별 id를 유지한 채로 `pos()` 함수를 사용하여 형태소 분석을 진행해 주세요. 1. 명사(`/n`), 동사(`/vv`), 형용사(`/va`)인 형태소만 가져와 주세요. 1. 형태소 정보는 제거하지 말고 그대로 두세요. 1. 동시 출현 빈도 테이블을 만들어 주세요. (컬럼이 item1, item2, n으로 구성됩니다.) 1. `우리/np`와 함께 출현한 단어들과 그 빈도를 확인하세요. 1. 명사는 형태소 정보를 제거하고, 형용사와 동사는 형태소 정보를 제거한후 뒤에 `다`를 붙여주세요. 1. 한 글자는 제거해 주세요. 1. 동시 출현 빈도 테이블을 만들어 주세요. (컬럼이 item1, item2, n으로 구성됩니다.) 1. `사랑`과 함께 출현한 단어들과 그 빈도를 확인하세요. --- ## 예시 답안 ```r tar %>% mutate(speech_id = 1:nrow(.)) %>% unnest_tokens(senten, content, token="sentences") %>% select(speech_id, senten) %>% mutate(sentence_id = 1:nrow(.)) %>% unnest_tokens(morph, senten, token = pos) %>% filter(str_detect(morph, "/n|/v(v|a)")) %>% mutate( morph = if_else( str_detect(morph, "/n"), str_replace_all(morph, "/.*$", ""), str_replace_all(morph, "/.*$", "다") ) ) %>% filter(str_length(morph) > 1) %>% pairwise_count(morph, sentence_id, sort=T, upper=F) %>% filter(item1 == "사랑") ``` --- ### 사용예 : 네트워크 시각화 ```r library(igraph) pw %>% filter(n > 5) %>% graph_from_data_frame() -> pw_graph pw_graph ``` ``` ## IGRAPH 2063ffb DN-- 34 35 -- ## + attr: name (v/c), n (e/n) ## + edges from 2063ffb (vertex names): ## [1] 국민 ->여러분 국민 ->사랑 ## [3] 여러분 ->사랑 국민 ->우리 ## [5] 생각 ->우리 우리 ->세계 ## [7] 우리 ->나라 안녕 ->대통령 ## [9] 우리 ->사실 우리 ->정부 ## [11] 때문 ->우리 사회 ->우리 ## [13] 국민 ->생각 미래 ->우리 ## [15] 우리 ->이번 국민 ->모두 ## + ... omitted several edges ``` --- 네트워크 데이터는 node, edge로 구성됨 ```r library(ggraph) set.seed(2018) a <- grid::arrow(type = "closed", length = unit(.1, "inches")) ggraph(pw_graph) + geom_edge_link(aes(edge_alpha = n), show.legend = FALSE, arrow = a, end_cap = circle(.07, 'inches')) + geom_node_point(color = "lightblue", size = 3) + geom_node_text(aes(label = name), vjust = 1, hjust = 1) + theme_void() ``` ``` ## Using `nicely` as default layout ``` ![](index_files/figure-html/unnamed-chunk-43-1.png)<!-- --> --- ## tf-idf * tf : 전체 문서내의 단어 빈도 * idf : 단어를 가지는 문서 비율의 역수 <img src=https://user-images.githubusercontent.com/6179259/47669547-78c9f180-dbee-11e8-85e8-e01cb4cbe46d.png class=center width=100%> --- ### tf-idf 계산 `bind_tf_idf()` 함수가 `tf`, `idf`, `tf-idf` 점수 모두를 제공하며 문서 단위의 정의가 매우 중요함. 보통 각 연설문, 개별 뉴스 본문 등을 하나의 문서로 정의함. `tf-idf` 가 높을 수록 각 문서에서 특별한 의미를 지니는 것으로 판단할 수 있음. ```r tar %>% mutate(id = as.numeric(1:n())) %>% unnest_tokens(pos, content, token = pos) %>% select(id, pos) %>% filter(str_detect(pos, "/n|/v(v|a)")) %>% mutate(pos = str_remove_all(pos, "/.*$")) %>% filter(nchar(pos) > 1) %>% group_by(id) %>% count(pos) -> tfidf_tar tfidf_tar %>% bind_tf_idf(pos, id, n) %>% arrange(desc(tf_idf)) ``` ``` ## # A tibble: 0 x 6 ## # Groups: id [0] ## # ... with 6 variables: id <dbl>, pos <chr>, n <int>, tf <dbl>, idf <dbl>, ## # tf_idf <dbl> ``` --- ### 연습문제 ```r library(presidentSpeech) library(magrittr) library(tidyverse) try_speech <- insistently(get_speech) spidx %>% filter(president == "이명박") %>% arrange(date) %>% top_n(10) %$% map_dfr(link, try_speech) -> tar ``` ``` ## Selecting by link ``` ```r tar ``` ``` ## # A tibble: 10 x 9 ## title date president place field event source paragraph content ## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <int> <chr> ## 1 제98차 라디오~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 2 제99차 라디오~ "" 이명박 국내 기타 라디오연~ "" 1 안녕하십니까, 대~ ## 3 제100차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 사회자(KBS 아~ ## 4 제101차 라디~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 5 제102차 라디~ "" 이명박 국내 과학기술/~ 라디오연~ "" 1 안녕하십니까, 대~ ## 6 제103차 라디~ "" 이명박 국내 외교/통상~ 라디오연~ "" 1 안녕하십니까, 대~ ## 7 제104차 라디~ "" 이명박 국내 정치/사회~ 라디오연~ "" 1 안녕하십니까, 대~ ## 8 제105차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ## 9 제106차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ## 10 제107차 라디~ "" 이명박 국내 국정전반~ 라디오연~ "" 1 안녕하십니까, 대~ ``` --- ### 연습문제 1. 새롭게 연설문별 id를 `id` 컬럼으로 추가해주세요. 1. 문장별 id를 유지한 채로 `pos()` 함수를 사용하여 형태소 분석을 진행해 주세요. 1. 명사(`/n`), 동사(`/vv`), 형용사(`/va`)인 형태소만 가져와 주세요. 1. 명사는 형태소 정보를 제거하고, 형용사와 동사는 형태소 정보를 제거한후 뒤에 `다`를 붙여주세요. 1. 한 글자는 제거해 주세요. 1. 연설문 별로 형태소 단위 빈도를 계산해 주세요. 1. `bind_tf_idf()` 함수를 사용해서 `tf`, `idf`, `tf-idf` 를 계산해주세요. 1. 각 연설문 별로 `tf-idf` 점수가 가장 높은 단어를 확인하세요. 1. 각 연설문 별로 `tf-idf` 점수가 가장 높은 3개 단어씩을 확인하세요. --- ## 감성 분석 - 감성 분석은 각 단어의 감성 사전을 구축하여 점수를 주는 방식 - 한글의 특성상, 형태소이며 ngram에 점수를 부여하는 것이 가장 효과적일 것 - 단순한 형태로는 unigram의 형태소에 점수나 종류를 부여하는 것 - 개별 단어의 점수를 부여한 뒤 문장 단위로 합산하여 계산 - 합산으로 0에 가까운 값이 나올 수 도 있으므로 점수를 부여받은 단어의 갯수등 도 고려 필요 - 안정적으로 기구축된 한글 사전을 찾기 어려움 --- ### 사전 소개 [KnuSentiLex](https://github.com/park1200656/KnuSentiLex)는 군산대 [Data Intelligence Lab](http://dilab.kunsan.ac.kr/)에서 기존 사전들을 참조, 활용하여 18년 구축한 감성 사전. 구조가 단순하고 이모티콘 등을 추가한 점이 장점인 반면, 형태소 형식이 아니라 점수의 신뢰도가 낮은 편임. [KOSAC](http://word.snu.ac.kr/kosac/)은 서울대에서 13년에 구축한 감성사전으로 구조가 복잡하고 점수를 내기 어렵지만 12년에 구축한 감성 스키마를 따르고 있어 다양한 감성 정보를 얻을 수 있음. 본 예시에는 구조가 단순한 `KnuSentiLex`을 사용 ```r # remotes::install_github("mrchypark/KnuSentiLexR") library(KnuSentiLexR) tar %>% unnest_tokens(sent, content, token = "sentences") %>% filter(nchar(sent) < 20) %>% select(sent) -> senti_tar ``` --- ### 감성 분석 점수 - `senti_score()` 함수는 문장을 unigram 부터 3-gram 까지 작성한 후, 감성 사전에 점수를 합산하여 문장 점수를 계산 - `senti_magnitude()` 함수는 몇개의 ngram이 점수화되었는지를 계산 - `dic` 객체가 word, polarity 컬럼을 가지고 있는 감성 사전임 ```r senti_tar %>% mutate(score = senti_score(sent), magni = senti_magnitude(sent)) %>% filter(score != 0) ``` ``` ## # A tibble: 38 x 3 ## sent score magni ## <chr> <dbl> <dbl> ## 1 먼저 함께 보시죠. 1 1 ## 2 자랑 좀 해 보세요. -2 1 ## 3 사회자 눈물이 그렁그렁하네요. -1 1 ## 4 이상하게. -1 1 ## 5 이분은 ‘한번 해 보자.’ -2 1 ## 6 대통령 그렇게 해 주세요. -2 1 ## 7 “난 부모 잘 만났어요. 1 1 ## 8 갔더니 정말 친절하게 잘해 줬어요. 4 2 ## 9 그런데 설명을 다 해 주시더라고요. -2 1 ## 10 그거 대단한 거죠. 2 1 ## # ... with 28 more rows ``` [1]: https://github.com/tidyverse/tidyverse [2]: https://mrchypark.github.io/kisa_finR [tidytextmining]: https://www.tidytextmining.com/ [tidytext]: https://juliasilge.github.io/tidytext/