ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 마틴 루터 킹 연설문을 이용한 텍스트 마이닝 및 워드클라우드
    잡다R 2020. 2. 7. 00:23
    마틴 루터 킹 연설문을 이용한 텍스트 마이닝 및 워드클라우드



     2020년 새해가 밝았습니다! 모두 복 많이 받으세요 :)

     올해 첫 글은 텍스트 마이닝과 워드클라우드를 진행하겠습니다. 텍스트 마이닝은 짧게 정의하면 비정형 데이터인 ’텍스트’를 가공하여 분석하는 것입니다. 저는 작년에 학교에서 진행했던 프로젝트 덕분에 텍스트 마이닝을 해본 경험이 있습니다. 복습 차원에서 작성하는 의미도 있고, 최근 각광받기 때문도 있습니다. 요즘 서비스 기업들을 보면 소비자의 리뷰를 중요하게 생각하기 때문에 리뷰에 대한 텍스트 마이닝을 많이 진행하는 것 같습니다. 예를 들어 영화 리뷰나 트위터 분석 이런 것들이죠. R이 있다면 우리도 따라갈 수 있습니다. 그럼 바로 시작해보겠습니다.

     

     텍스트 마이닝의 주제는 역사상 가장 뛰어난 연설문으로 손꼽히는 마틴 루터 킹의 연설문입니다. 나무위키에 ’나에게는 꿈이 있습니다.’라는 문서로 연설문 번역본이 존재합니다. 웹크롤링으로 연설문을 긁어오겠습니다. 웹크롤링은 설명 안 드려도 되겠죠? 우리는 연설문에서 쓰인 단어들의 빈도를 살펴보겠습니다. 그리고 n그램, 의미연결망 같이 좀더 테크니컬한 부분은 다음글로 예약하겠습니다.

    library(rvest)
    
    moonjang <- read_html("https://namu.wiki/w/%EB%82%98%EC%97%90%EA%B2%8C%EB%8A%94%20%EA%BF%88%EC%9D%B4%20%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4")
    a <- moonjang %>% 
      html_nodes('.wiki-quote')
    speech <- a[2] %>% 
      html_text()
    
    speech
    ## [1] "우리 역사에서 자유를 위한 가장 훌륭한 시위가 있던 날로 기록될 오늘 이 자리에 여러분과 함께하게 된 것을 기쁘게 생각합니다.   -중략-   하나님의 모든 자녀들, 흑인이건 백인이건, 유태인이건 비유태인이건, 개신교도이건 가톨릭교도이건, 손을 잡고, 옛 흑인 영가를 함께 부르는 그 날 말입니다.『드디어 자유가, 드디어 자유가! 전능하신 주님 감사합니다, 우리가 마침내 자유로워졌나이다!』[8]"


     데이터 정제 데이터 정제는 KoNLP 라이브러리를 이용합니다. KoNLP 라이브러리의 SimplePoss09() 함수를 이용하면 글의 형태소 분석을 진행할 수 있습니다.

    library(KoNLP)
    SimplePos09("문장의 형태소 분석을 진행합니다.")
    ## $문장의
    ## [1] "문장/N+의/J"
    ## 
    ## $형태소
    ## [1] "형태소/N"
    ## 
    ## $분석을
    ## [1] "분석/N+을/J"
    ## 
    ## $진행합니다
    ## [1] "진행/N+하/X+ㅂ니다/E"
    ## 
    ## $.
    ## [1] "./S"


     SimplePos09()은 글을 9개의 품사로 나눕니다. 크게 /N은 명사, /P는 형용사, /J는 조사 등입니다. 이 때, 띄어쓰기에 유의해야 합니다. 다음의 결과를 보겠습니다.

    SimplePos09("문 장 의 형 태 소 분 석 을 진 행 합 니 다")
    ## $문
    ## [1] "문/N"
    ## 
    ## $장
    ## [1] "장/N"
    ## 
    ## $의
    ## [1] "의/N"
    ## 
    ## $형
    ## [1] "형/N"
    ## 
    ## $태
    ## [1] "트/P+어/E"
    ## 
    ## $소
    ## [1] "소/N"
    ## 
    ## $분
    ## [1] "분/N"
    ## 
    ## $석
    ## [1] "석/M"
    ## 
    ## $을
    ## [1] "을/N"
    ## 
    ## $진
    ## [1] "지/P+ㄴ/E"
    ## 
    ## $행
    ## [1] "행/N"
    ## 
    ## $합
    ## [1] "합/N"
    ## 
    ## $니
    ## [1] "니/N"
    ## 
    ## $다
    ## [1] "다/M"

     띄어쓰기가 제대로 이루어지지 않으면 품사구분이 완전 틀리게 나타나는 것을 확인할 수 있습니다. 와우

     그런데 사실은 애초에 텍스트 마이닝을 할 때 띄어쓰기는 권한 밖인 것 같습니다. 왜냐하면 크롤링할 데이터이기 때문입니다. 다른 사람의 글을 가져오는 건데, 띄어쓰기가 완벽할리가 없겠죠? 당장에 제가 쓰는 이 글도 띄워쓰기나 마춤뻡 오류가 있을 것입니다. 그래서 개인적으로는 띄어쓰기는 아직 완벽한 답이 없는 것 같습니다.


     띄어쓰기를 전부 올바르게 해줄 수는 없어도, 글에 붙은 먼지같은 것들은 제거해줄 수 있습니다. 그것이 전처리 작업입니다.

     우리는 품사분석을 하기 전에 전처리를 해주어야 합니다. 이상한 특수문자나 의미 없는 숫자 등은 제거해서 깔끔한 글을 만들어야 합니다. 그렇게 완성된 글을 SimplePos09()에 넣어서 깔끔한 품사분석을 유도하는 것이 목표입니다.

    전처리를 할 때는 크게 2가지만 기억하세요.


    1. 확인한다.
    2. 바꾼다.

     

     먼저 확인하는 방법입니다. 함수를 만들면서 확인하는 방법입니다. 함수는 ’R을 이용한 텍스트 마이닝(백영민 저)’에서 알려주는 표현을 사용했습니다. lapply와 stringr 라이브러리의 str_extrct_all() 함수를 이용하여 텍스트를 확인하는 방법은 다음과 같습니다.

    library(stringr)
    
    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "나에게는 꿈이 있습니다")
      
    ))

     저는 이 코드를 부르기 쉽게 서칭함수라고 합니다. 서칭함수의 lapply는 apply의 리스트 형식을 나타내는 함수이고, str_extract_all() 함수는 원하는 표현이 어떻게 쓰였는지, 몇 번 쓰였는지 확인할 수 있는 함수입니다. 우리가 ctrl F 를 눌러서 원하는 단어 및 표현 전체 찾기를 진행한 거라고 생각하시면 됩니다. 결과를 한번 볼까요?

    table(unlist(check))
    ## 
    ## 나에게는 꿈이 있습니다 
    ##                      5

     이제 감이 조금 오시나요?

     서칭함수는 정규표현과 같이 쓰면 더 효과적입니다. 가령 이렇게 말이죠.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "[[:print:]]{1,8}있습니다")
      
    ))
    
    table(unlist(check))
    ## 
    ##    것을 알 수 있습니다   , 나는 꿈이 있습니다 계임을 깨닫고 있습니다 
    ##                      1                      1                      1 
    ## 나에게는 꿈이 있습니다 되는 사람들도 있습니다 롭게 살아가고 있습니다 
    ##                      5                      1                      1 
    ## 말씀드릴 것이 있습니다 묻는 사람들이 있습니다 이야기가 하나 있습니다 
    ##                      1                      1                      1 
    ## 프게 살아가고 있습니다 하게 살아가고 있습니다 
    ##                      1                      1

     정규표현과 함께 사용하니 “나에게는 꿈이 있습니다” 외에도 “있습니다”로 끝나는 표현을 다 찾아냈습니다. 어떤 의미에서는 이렇게 찾는게 분명히 효율적일 것입니다. 그렇기 때문에 정규표현을 좀더 살펴보겠습니다.

    • [[:alpha:]] : 문자
    • [[:punct:]] : 특수문자
    • [[:upper:]] : 대문자
    • [[:lower:]] : 소문자
    • [[:digit:]]  : 숫자
    • [[:space:]] : 공백
    • [[:print:]]  : 모든 문자
    • {a,b}       : 선행 표현의 a 이상 b 미만 횟수
    • \\(백슬래쉬 2개) + 특수문자 : 있는 그대로의 특수문자


     다음의 정규표현 뜻을 합쳐서 해석하면 됩니다. 예를 들어, 위에 쓴 [[:print:]]{1,8}은 어떤 모든 텍스트가 1회 이상, 8회 미만 출력이라는 말입니다. 그렇기 때문에 “나에게는 꿈이 있습니다” 외에 “있습니다”로 끝나는 표현들이 나올 수 있었던 것이죠. 문자 정규표현의 [[:alpha:]]가 아닌 [[:print:]]를 쓴 이유는 문자와 공백을 함께 포함해야 제대로 출력할 수 있기 때문입니다. 만약 print를 alpha로 고친다면 결과는 다음과 같이 될 것입니다.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "[[:alpha:]]{1,8}있습니다")
      
    ))
    
    table(unlist(check))
    ## < table of extent 0 >

     따라서 상황에 맞는 표현을 써주는 것이 중요합니다.

     백슬래쉬는 있는 그대로의 특수문자를 나타냅니다. 예를 들어, ’.’이 문자표현으로 쓰이면 ’모든 표현’이라는 의미를 갖습니다.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, ".")
      
    ))
    
    table(unlist(check))
    ## 
    ##    -         !    "    (    )    ,    .    :    ?    [    ]    ~   『   』 
    ##    3 1164    8    6   12   12   69   90    2    1    7    7    1    2    2 
    ##    0    1    2    3    4    5    6    7    8    9    a    c    d    e    f 
    ##    1    1    2    2    4    3    2    1    1    1    3    2    2    2    1 
    ##    h    i    k    l    m    n    o    p    q    r    s    t    u    w   가 
    ##    2    7    1    3    1    5    4    1    1    3    4    4    2    1   75 
    ##   각   간   艱   갈   감   강   갖   개   거   건   걸   겁   것   게   겠 
    ##    2   13    1    6    2    3    1    3    9    8    3    1   30   48    2 
    ##   겨   격   겪   결   경   계   고   곡   곤   곧   골   곳   공   과   관 
    ##    1    1    2    3    4    8   45    4    2    2    1    5    2   13    3 
                                       - 중략 -
    ##   태   터   테   텔   토   톤   톨   통   투   퉁   파   판   퍼   펜   펴 
    ##    4    6    2    2    3    1    1    2    5    1    1    2   12    1    1 
    ##   평   포   폭   표   풍   프   피   필   하   한   할   함   합   항   해 
    ##    7    3    5    9    3    2    7    2   42   45   16   11   19    1   16 
    ##   햄   햇   했   행   향   헌   혀   현   협   형   호   혼   홀   화   환 
    ##    1    1    4    6    4    1    1    8    1    3    2    1    1    2    1 
    ##   활   황   횃   회   후   훌   휩   휴   흐   흑   흔   희   히   힘 
    ##    1    1    1    4    5    3    1    1    1   21    1    5    6    3

     따라서 마침표를 찾기 위해서는 반드시 백슬래쉬를 사용해야 합니다.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "\\.")
      
    ))
    
    table(unlist(check))
    ## 
    ##  . 
    ## 90

     여기까지 표현을 확인하는 방법을 알아봤습니다. 글이 길어질 것 같은 느낌이 드네요. 하지만 계속 달려보도록 하겠습니다.


     원하는 표현을 확인했다면 아마 고치고 싶다는 생각이 들 때도 있습니다. 그 때 stringr의 str_replace_all() 함수를 이용해주면 됩니다. 이건 설명보다 실행을 바로바로 하면서 알려드리는 것이 이해하기 쉽습니다. 우리는 speech라는 연설문 텍스트 자료를 가지고 있습니다. 하지만 자세히 보시면 마침표와 느낌표로 구분된 문장의 띄어쓰기가 잘 이루어지지 않았다는 것을 알 수 있습니다. 그래서 마침표를 마침표 + 띄어쓰기로, 느낌표를 느낌표 + 띄어쓰기로 바꾸면 아래와 같습니다.

    speech <- str_replace_all(speech, "\\.", ". ")
    speech <- str_replace_all(speech, "!", "! ")

     감이 오시리라 믿습니다. str_replace_all()을 써주고 텍스트, 바꾸고 싶은 표현과 바꿀 표현을 차례로 써주면 됩니다.



     자 그럼 이제 연설문을 하나씩 다듬어보겠습니다.

     서칭함수로 표현을 확인하면서 바꿔주는 작업을 할 것입니다. 먼저 특수문자를 살펴봅시다.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "[[:punct:]]{1,}")
      
    ))
    
    table(unlist(check))
    ## 
    ##   -   !   "   (   )  ),   ,  ,[   .   :  ?"   [   ]  ],  『 』[ 
    ##   3   8   5  12  11   1  65   1  90   2   1   4   5   2   2   2

     특수문자 처리의 경우 다양한 방법을 생각할 수 있습니다. 단순하게 제거하던가, 공백을 집어 넣으면서 제거하던가, 다른 표현으로 대체하는 방법들이죠.


     먼저 소괄호의 표현을 살펴봅니다.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "[[:print:]]{1,5}\\([[:print:]]{1,20}\\)")
      
    ))
    
    table(unlist(check))
    ## 
    ##    우선한다(interposition)              .  우리(흑인) 
    ##                          1                          1 
    ##        그 새로운(창의적인)  는 출발점(warm threshold) 
    ##                          1                          1 
    ##     니다.  (이사야 40:4~5) 라는 유사(流沙,quicksands) 
    ##                          1                          1 
    ##     럼 흐르고(아모스 5:24)            이나 간난(艱難) 
    ##                          1                          1 
    ##            인 거주지(게토)          찬 심포니(교향곡) 
    ##                          1                          1 
    ##            처의 여관(모텔)   한 거부권(nullification) 
    ##                          1                          1

     텍스트 문서에서 소괄호는 보통 설명의 표현으로 쓰입니다. ’소확행(소소하지만 확실한 행복)’처럼 바로 앞에 쓰인 단어가 어떤 의미인지 설명해줍니다. 본문에서도 소괄호는 이런 표현으로 쓰인 것을 확인할 수 있습니다. 예를 들면 우리(흑인), 거주지(게토)와 같이 말이죠. 또는 한자어나 영단어를 적어주기도 하고, (아모스 5:24), (이사야 40:4~5)처럼 성경 구절의 인용구라는 것을 확인해주고 있습니다.

     이런 소괄호의 표현들은 삭제해도 큰 영향을 미치지 않을 것입니다.

    speech <- str_replace_all(speech, "\\([[:print:]]{1,20}\\)", "")


     대괄호는 주석표현으로 쓰였습니다.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "[[:print:]]{1,10}\\[[[:print:]]{1,10}\\][[:print:]]{1,10}")
      
    ))
    
    table(unlist(check))
    ## 
    ##     퍼지게 합시다! [6] 뿐만 아니라, 조 
    ##                                      1 
    ##   퍼지게 합시다! [7]모든 산으로부터 자 
    ##                                      1 
    ##     니다.  백 년 전[2], 한 위대한 미국 
    ##                                      1 
    ## 려퍼지게 하라. 』[5]그리고 미국이 위대 
    ##                                      1 
    ##   로, 루이지애나로,[4] 우리 북부 도시  
    ##                                      1 
    ##   를 찾지 못하는 한[3], 우리는 만족할  
    ##                                      1

     주석 모두 문장의 끝에 들어가 있는데, 앞의 문장과 붙어 있는 모습도 볼 수 있습니다. 띄어쓰기가 중요하고, 주석을 공백으로 처리해도 띄어쓰기에 영향이 없으므로 주석을 공백으로 처리합니다.

    speech <- str_replace_all(speech, "\\[[[:print:]]{1,10}\\]", " ")


     다음으로는 큰따옴표를 살펴보겠습니다.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "[[:print:]]{1,10}\"[[:print:]]{1,20}")
      
    ))
    
    table(unlist(check))
    ## 
    ##  하는 사람들에게 "언제야 만족하겠는가?"고 묻는 사람들 
    ##                                                     1 
    ##   다.  이 수표는 "자금이 부족함"이란 도장이 찍혀 돌아 
    ##                                                     1 
    ##  없을 것입니다.  "백인 전용"이라는 간판이 우리의 아이 
    ##                                                     1

     큰따옴표나 작은따옴표는 인용구나 강조하는 말에 주로 쓰입니다. 본문에서는 “백인 전용”, “언제야 만족하겠는가?”, “자금이 부족함”과 같은 표현이 쓰였습니다.

    SimplePos09("자금이 부족함이란 도장이 찍혔다.")
    ## $자금이
    ## [1] "자금/N+이/J"
    ## 
    ## $부족함이란
    ## [1] "부족함/N+이란/J"
    ## 
    ## $도장이
    ## [1] "도장/N+이/J"
    ## 
    ## $찍혔다
    ## [1] "찍히/P+었다/E"
    ## 
    ## $.
    ## [1] "./S"
    SimplePos09("자금이 부족함 이란 도장이 찍혔다.")
    ## $자금이
    ## [1] "자금/N+이/J"
    ## 
    ## $부족함
    ## [1] "부족함/N"
    ## 
    ## $이란
    ## [1] "이란/N"     << 명사..요? 어리둥절..
    ## 
    ## $도장이
    ## [1] "도장/N+이/J"
    ## 
    ## $찍혔다
    ## [1] "찍히/P+었다/E"
    ## 
    ## $.
    ## [1] "./S"

     앞서 말씀드린대로 잘못된 띄어쓰기 표기는 잘못된 형태소분석을 야기합니다. 큰따옴표를 공백으로 치환하면 위와 같은 예기치 못한 형태소 오류가 나타납니다. 컴퓨터는 조사 ’이란’을 명사로 보네요. 누군가 얼핏보면 ’자금이 부족함’과 ’이란 도장이 찍혔다’는 2개의 문장으로 볼 수도 있겠네요. 갑자기 분위기 이란 도장이라니.. 따라서 큰따옴표는 공백으로 치환하지 않겠습니다.


     나머지 특수문자 표현들도 지우는 것으로 마무리하겠습니다.

    speech <- str_replace_all(speech, "[[:punct:]]{1,}", "")


     다음으로는 숫자 표현을 살펴봅시다.

    check <- lapply(speech, function(x) (
      
      str_extract_all(x, "[[:print:]]{1,10}[[:digit:]]{1,}[[:print:]]{1,10}")
      
    ))
    
    table(unlist(check))
    ## 
    ## 계속될 것입니다  1963년은 끝이 아니라  
    ##                                      1

     음.. 1963년.. 하나 밖에 쓰이지 않았습니다. 연설이 이루어진 연도라 의미가 있다고 할 수도 있고, 딱히 없다고 할 수도 있겠습니다. 굳이 지울 필요는 없어보여서 살려주도록 하겠습니다.


     다음으로 띄어쓰기가 2개 이상 나타나는 경우 하나로 함쳐주는 작업입니다. 확인 작업은 생략하겠습니다.

    speech <- str_replace_all(speech, "[[:space:]]{2,}", " ")

     여기까지 했다면 우리는 연설문을 꽤 잘 정제했습니다. 한번 확인해 보아요.

    speech
    ## [1] "우리 역사에서 자유를 위한 가장 훌륭한 시위가 있던 날로 기록될 오늘 이 자리에 여러분과 함께하게 된 것을 기쁘게 생각합니다 백 년 전 한 위대한 미국인이    - 중략 -    하나님의 모든 자녀들 흑인이건 백인이건 유태인이건 비유태인이건 개신교도이건 가톨릭교도이건 손을 잡고 옛 흑인 영가를 함께 부르는 그 날 말입니다 드디어 자유가 드디어 자유가 전능하신 주님 감사합니다 우리가 마침내 자유로워졌나이다 "



     이제 워드클라우드를 만드는 작업을 해보겠습니다. 빠르게 렛츠 고고 해볼게요!

     데이터를 품사분석 후 다음과 같이 정리해주면, 명사로 쓰인 표현들만 뽑아낸 빈도표를 얻으실 수 있을 겁니다. 이 부분은 아래 블로그를 통해서 배웠습니다. 자세한 내용을 알고 싶다면 아래 글을 읽어주세요. 언제나 감사합니다 ^-^


    • 최대한 친절하게 쓴 R로 낱말구름, 의미연결망 그리기(feat. tidyverse, KoNLP) (블로그 바로가기) / 작성자 : kini님


    library(reshape2)
    library(dplyr)
    
    poomsa <- SimplePos09(speech)
    
    dat <- poomsa %>% 
      melt() %>% 
      as_tibble() %>% 
      mutate(noun = str_match(value, '([가-힣]+)/N')[,2]) %>% 
      na.omit() %>% 
      count(noun, sort = TRUE) 
    
    dat %>% head
    ## # A tibble: 6 x 2
    ##   noun      n
    ##   <chr> <int>
    ## 1 우리     39
    ## 2 자유     27
    ## 3 것       26
    ## 4 수       19
    ## 5 때       15
    ## 6 꿈       14

     빈도표를 살펴본바, 2가지 정리가 필요해보입니다. 하나는 불용단어 제거이고, 다른 하나는 단어를 취합하는 일입니다. 특이하게도 오늘은 2개씩 할 일이 굉장히 많이 나오는 것 같습니다. 하나씩 보도록 할게요.


    1. 불용단어 제거

     먼저 한 글자 단어를 보겠습니다. 기본적으로 많은 글들에서 한 글자 단어는 아예 제거하고 시작하는 경우가 있습니다.

    dat %>% 
      filter(nchar(noun) == 1)
    ## # A tibble: 44 x 2
    ##    noun      n
    ##    <chr> <int>
    ##  1 것       26
    ##  2 수       19
    ##  3 때       15
    ##  4 꿈       14
    ##  5 나       12
    ##  6 백       11
    ##  7 날        9
    ##  8 한        7
    ##  9 년        6
    ## 10 땅        6
    ## # ... with 34 more rows

     이유는 보시는 것처럼 것, 수, 때 등 의미 없는 단어가 많이 포함되어 있기 때문입니다. 하지만 우리에게는 ’꿈’이라는 단어가 있습니다. 마틴 루터 킹의 연설문을 대표하는 한 단어가 바로 꿈인데 꿈을 지워버리면 안 될 것입니다. 무분별하게 단어를 제거하지 말아야 하는 이유가 여기에 있습니다. 추가적으로 ’땅’과 ’산’이라는 단어 역시 문자 그대로의 땅과 산을 나타내는 단어로 쓰였습니다. 빈도가 어느 정도 있는 단어들이니 살려두도록 하겠습니다. 그럼 꿈, 땅, 산이라는 세 단어를 제외한 한 글자 단어를 삭제해봅시다.

    oneword <- c("꿈", "땅", "산") 
    
    dat <- dat %>% 
      filter(noun %in% oneword |
             nchar(noun) != 1)


     다음으로 두 글자 이상의 단어입니다.

     두 글자 이상의 불용단어를 정할 때 도움이 되는 사전이 있습니다. 영문 표현에서의 불용단어, 그러니까 의미가 없다고 판단하며 굳이 빈도표 카운트를 안해줘도 되는 단어를 정해리 놓은 사전입니다. tm 라이브러리의 'en'과 'SMART'가 그것입니다.

    library(tm)
    stopwords("en")
    ##   [1] "i"          "me"         "my"         "myself"     "we"        
    ##   [6] "our"        "ours"       "ourselves"  "you"        "your"      
    ##  [11] "yours"      "yourself"   "yourselves" "he"         "him"       
    ##  [16] "his"        "himself"    "she"        "her"        "hers"      
    ##  [21] "herself"    "it"         "its"        "itself"     "they"      
                                      - 중략 -    
    ## [156] "any"        "both"       "each"       "few"        "more"      
    ## [161] "most"       "other"      "some"       "such"       "no"        
    ## [166] "nor"        "not"        "only"       "own"        "same"      
    ## [171] "so"         "than"       "too"        "very"
    stopwords("SMART")
    ##   [1] "a"             "a's"           "able"          "about"        
    ##   [5] "above"         "according"     "accordingly"   "across"       
    ##   [9] "actually"      "after"         "afterwards"    "again"        
    ##  [13] "against"       "ain't"         "all"           "allow"        
                                      - 중략 -   
    ## [557] "x"             "y"             "yes"           "yet"          
    ## [561] "you"           "you'd"         "you'll"        "you're"       
    ## [565] "you've"        "your"          "yours"         "yourself"     
    ## [569] "yourselves"    "z"             "zero"

     저 같은 경우 처음 제거단어를 선택할 때, en과 SMART를 살펴보면서 진행했습니다. 여러분도 초보자라면 처음에는 이미 정리해둔 불용단어 사전을 이용하시는게 좋습니다.

     하지만 무분별한 제거는 역시 좋지 않습니다. 언어의 차이를 고려하면서 작업해야 합니다. 가령, SMART사전에서 ‘생각’이라는 단어 ’think’가 불용단어로 포함되어 있습니다. 만약 우리말로’~라고 생각한다’와 같은 표현에서 ’생각’이라는 단어의 빈도가 많이 나왔다면, ’think’로 해석해서 삭제하는 것이 바람직해 보입니다. 하지만 ’나는 생각이 없다’처럼 ’idea’나 다른 의미를 갖는 ’생각’으로 해석된다면, 삭제하지 않는 편이 좋아보입니다.

     이런 식으로 고려해서 dat 빈도표의 단어 중 ’우리’와 ’이것’은 불용단어로 제거해도 될 듯 합니다. 어느 정도 빈도가 높은 단어에서 골랐으며, 빈도가 낮은 표현들은 무시해버리기로 하겠습니다.

    except <- c("우리", "이것")
      
    dat <- dat %>% 
      filter(!noun %in% except)

     이렇게 하면 불용단어 제거가 끝이 납니다.


    1. 단어 취합

     불용단어를 제거한 빈도표는 꽤 정리가 잘 되어 보입니다. 마지막으로 단어 취합을 해줍니다. 빈도를 살펴보면 어떤 단어를 취합해도 되겠다는 생각이 저절로 드실 것입니다.

    dat %>% head(15)
    ## # A tibble: 15 x 2
    ##    noun         n
    ##    <chr>    <int>
    ##  1 자유        27
    ##  2 꿈          14
    ##  3 흑인         9
    ##  4 나라         8
    ##  5 정의         7
    ##  6 땅           6
    ##  7 산           6
    ##  8 여러분       6
    ##  9 오늘         6
    ## 10 흑인들이     6
    ## 11 만족할       5
    ## 12 미국         5
    ## 13 자리         5
    ## 14 하나님       5
    ## 15 사람         4

     10행의 ‘흑인들이’는 품사분석이 잘못 이루어져서 나타난 것으로 보입니다. ’흑인’으로 고쳐 취합하면 좋을 것 같습니다. 아울러 11행의 ’만족할’ 역시 ’만족’이라는 단어로 쓰이는 것이 바람직해 보입니다. 빈도가 상대적으로 높은 단어 중 고려해야할 부분이 이 2개 단어 뿐이어서 이것만 고쳐주고 나머지 단어는 무시하겠습니다. 해당 행에 고치고 싶은 단어를 삽입해서 고치도록 하겠습니다. 이 때 주의할 점은, 삽입해서 고친다면 ’흑인’과 ’만족’이라는 단어가 표에서 2개씩 나타나게 됩니다. 따라서 dplyr의 group_by()을 이용하여 하나로 묶어주는 작업이 필요합니다.

    dat[10,]$noun <- "흑인"
    dat[11,]$noun <- "만족"
    
    dat <- dat %>% 
      group_by(noun) %>% 
      summarise(n = sum(n)) %>% 
      arrange(desc(n))

     여기까지 진행했다면 워드 클라우드를 뽑을 준비가 모두 완료되었습니다. 이제 dat 데이터와 wordcloud2를 이용하여 연설문을 시각화해보겠습니다.

    *주의* 

     wordcloud2는 애니메이션 효과가 있어 마우스를 가져다 대면 단어 빈도수까지 볼 수 있습니다. 하지만 블로그에 포스팅할 때 워드클라우드가 깨져서 나타나는 현상 때문에 어쩔 수 없이 이미지로 삽입한다는 점 먼저 말씀드립니다.

    library(wordcloud2)
    
    wordcloud2(data = dat, 
               minSize = 5, 
               size = .8,
               rotateRatio = 0,   # 단어의 회전 확률
               gridSize = 10,     # 단어 사이의 공간
               ellipticity = .9   # 편평률
    )
    
    # 처음 접할 만한 부분을 설명해드리자면 rotateRatio는 단어의 회전 확률로 0부터 1사이 숫자를 써주시면 됩니다. gridSize는 단어 사이의 공간으로 간격이 마음에 안 들 때 조절하면 됩니다. ellipticity는 편평률로 0부터 1사이 숫자를 입력해야하며, 0에 가까울수록 타원에, 1에 가까울수록 완벽한 원이 그려집니다.

     와우 그림이 이쁘게 그려졌네요ㅎㅎ 연설문의 주제라고 할 수 있는 ‘자유’가 제일 많이 쓰였고, 자유의 주체인 ’흑인’, 그리고 마틴 루터 킹의 단어라 할 수 있는 ’꿈’이 많이 쓰였다는 것을 한눈에 확인할 수 있습니다. 그 외에 눈에 들어오는 재미있는 단어는 ’수표’인데, 마틴 루터 킹이 연설문에서 흑인들의 권리를 ’수표’라는 단어로 비유했기 때문입니다.

     여기까지 마틴 루터 킹 연설문으로 텍스트 마이닝 및 워드클라우드를 진행해보았습니다. 원래 글을 지난 달에 올렸어야 했는데.. 바쁜 와중에 이것저것 고민하느라 늦어진 것 같아서 아쉽습니다. 다음에는 더 알차고 재미있는 주제로 글을 쓰겠습니다. 그럼 이만 글 마치겠습니다. 감사합니다.


    :)





Designed by Tistory.