[R] 1. Tidy text format

반응형

1. The tidy text format

  • tidy text format을 행(row)당 하나의 토큰(token)이 있는 테이블로 정의합니다.
    • 토큰은 분석에서 사용하고자 하는 “단어”와 같이 의미가 있는 텍스트 단위를 의미합니다.
    • 물론 토큰은 단일 단어 뿐만 아니라 n-gram, 문장 또는 단락 등이 될 수도 있습니다.
  • 행당 하나의 토큰 구조를 만들기 위해서는 토큰화(tokenization)를 거쳐야 합니다.

 

1. 1. Contrasting tidy text with other data structures

  • 문자열(String):
    • 문자열, 즉 문자 벡터
  • 말뭉치(Corpus):
    • 이러한 유형은 일반적으로 메타데이터 및 세부정보로 주석이 달린 원시 문자열 등이 포함
  • 문서-단어 행렬(Document-term matrix):
    • 각 문서들이 행(row)을 이루고 문서에 포함된 단어들이 열(column)을 이루는 희소 행렬(sparse matrix)
    • 행렬을 구성하는 값은 단어의 빈도 수 또는 TF-IDF가 구성됩니다.



1. 2. unnest_tokens() function

  • 아래 유명한 나태주 시인님의 시 몇가지를 예시로 들겠습니다.
text <- c("자세히 보아야 예쁘다
          오래 보아야 사랑스럽다
          너도 그렇다",
          "꽃이 피고 새잎 나는 날
          마음아 너도 거기서
          꽃 피우고 새잎 내면서
          놀고 있거라",
          "두 셋이서 피어 있는 꽃보다
          오직 혼자서 피어있는 꽃이
          더 당당하고 아름다울 때 있다
          너 오늘 혼자서 외롭게
          꽃으로 서있음을 너무
          힘들어 하지 말아라")

text
## [1] "자세히 보아야 예쁘다\n          오래 보아야 사랑스럽다\n          너도 그렇다"                                                                                                                         
## [2] "꽃이 피고 새잎 나는 날\n          마음아 너도 거기서\n          꽃 피우고 새잎 내면서\n          놀고 있거라"                                                                                          
## [3] "두 셋이서 피어 있는 꽃보다\n          오직 혼자서 피어있는 꽃이\n          더 당당하고 아름다울 때 있다\n          너 오늘 혼자서 외롭게\n          꽃으로 서있음을 너무\n          힘들어 하지 말아라"
  • 위 텍스트 데이터는 우리가 알고있는 일반적인 문자형 벡터가 됩니다.
    (\n 의미는 행바꿈을 의미하는 정규표현식 입니다.)
  • 이를 tidy text format으로 바꾸려면 tibble() 함수를 사용하여 프레임안에 넣을 수 있습니다.
text_df <- tibble(
  seq = 1:length(text),
  text = text
)

text_df
## # A tibble: 3 x 2
##     seq text                                                                    
##   <int> <chr>                                                                   
## 1     1 "자세히 보아야 예쁘다\n          오래 보아야 사랑스럽다\n          너도…
## 2     2 "꽃이 피고 새잎 나는 날\n          마음아 너도 거기서\n          꽃 피… 
## 3     3 "두 셋이서 피어 있는 꽃보다\n          오직 혼자서 피어있는 꽃이\n     …
  • 하지만 문자열을 포함하는 이러한 데이터 프레임을 가지고 텍스트 분석을 하기에는 아직 부족합니다.
  • 각 행이 여러 개의 결합된 단어나 문장 등으로 구성되어 있기 때문에 가장 자주 발생하는 단어 등을 필터링할 수 없습니다.
  • 이 것을 행당 문서당 하나의 토큰(one token per document per row)을 갖도록 변환해야 합니다.
  • 이 때 쓰이는 함수는 tidytext 라이브러리의 unnest_tokens() 함수 입니다.
  • unnest_tokens() 함수에 사용되는 두 가지 기본 파라미터는 아래와 같습니다.
    • output: unnest되어 새로 생성될 변수명
    • input: 텍스트가 들어있는 입력값의 변수명
text_df %>% 
  unnest_tokens(
    input = text,
    output = "word"
  )
## # A tibble: 46 x 2
##      seq word      
##    <int> <chr>     
##  1     1 자세히    
##  2     1 보아야    
##  3     1 예쁘다    
##  4     1 오래      
##  5     1 보아야    
##  6     1 사랑스럽다
##  7     1 너도      
##  8     1 그렇다    
##  9     2 꽃이      
## 10     2 피고      
## # … with 36 more rows
  • 여기에 token 이라는 argument가 별도로 있는데 기본 값은 token = "words" 입니다.
  • 만일 토큰을 단어 2개로 묶은 n-gram 형태로 뽑고 싶다면 아래와 같이 코드를 입력하시면 됩니다.
text_df %>% 
  unnest_tokens(
    input = text,
    output = "ngram_2",
    token = "ngrams",
    n = 2
  )
## # A tibble: 43 x 2
##      seq ngram_2          
##    <int> <chr>            
##  1     1 자세히 보아야    
##  2     1 보아야 예쁘다    
##  3     1 예쁘다 오래      
##  4     1 오래 보아야      
##  5     1 보아야 사랑스럽다
##  6     1 사랑스럽다 너도  
##  7     1 너도 그렇다      
##  8     2 꽃이 피고        
##  9     2 피고 새잎        
## 10     2 새잎 나는        
## # … with 33 more rows



1. 3. Tidying the works of Jane Austen & Word frequencies

  • 메뉴얼에 소개된 방식대로 한 번 따라해보겠습니다.
  • janeaustenr 라이브러리에 있는 출판 소설의 텍스트 데이터를 예시로 사용하겠습니다.
library(janeaustenr)
austen_books()
## # A tibble: 73,422 x 2
##    text                    book               
##  * <chr>                   <fct>              
##  1 "SENSE AND SENSIBILITY" Sense & Sensibility
##  2 ""                      Sense & Sensibility
##  3 "by Jane Austen"        Sense & Sensibility
##  4 ""                      Sense & Sensibility
##  5 "(1811)"                Sense & Sensibility
##  6 ""                      Sense & Sensibility
##  7 ""                      Sense & Sensibility
##  8 ""                      Sense & Sensibility
##  9 ""                      Sense & Sensibility
## 10 "CHAPTER 1"             Sense & Sensibility
## # … with 73,412 more rows
  • austen_books()의 출력 결과물에 대한 설명은 아래와 같습니다.
    • text, book 두 개의 컬럼으로 구성되어 있는 데이터
    • book 컬럼은 6개의 책 이름을 의미
    • text 컬럼은 최대 약 70자 정도로 분할된 소설의 텍스트
#cumsum(): 누적합을 구하는 함수
#str_detect(): 문자열에서 특정 조건을 만족하는 문자열을 찾는 함수
#regex(): 정규표현식 관련 함수

original_books <- austen_books() %>% 
  group_by(book) %>% 
  mutate(
    line_number = row_number(),
    chapter = cumsum(str_detect(string = text, pattern = regex("^chapter [\\divclx]", ignore_case = TRUE)))
  ) %>% 
  ungroup()

original_books
## # A tibble: 73,422 x 4
##    text                    book                line_number chapter
##    <chr>                   <fct>                     <int>   <int>
##  1 "SENSE AND SENSIBILITY" Sense & Sensibility           1       0
##  2 ""                      Sense & Sensibility           2       0
##  3 "by Jane Austen"        Sense & Sensibility           3       0
##  4 ""                      Sense & Sensibility           4       0
##  5 "(1811)"                Sense & Sensibility           5       0
##  6 ""                      Sense & Sensibility           6       0
##  7 ""                      Sense & Sensibility           7       0
##  8 ""                      Sense & Sensibility           8       0
##  9 ""                      Sense & Sensibility           9       0
## 10 "CHAPTER 1"             Sense & Sensibility          10       1
## # … with 73,412 more rows
  • 이를 tidy text format으로 활용하기 위해서 똑같이 unnest_tokens() 함수를 적용하겠습니다.
tidy_books <- original_books %>% 
  unnest_tokens(
    input = text,
    output = "word"
  )

tidy_books
## # A tibble: 725,055 x 4
##    book                line_number chapter word       
##    <fct>                     <int>   <int> <chr>      
##  1 Sense & Sensibility           1       0 sense      
##  2 Sense & Sensibility           1       0 and        
##  3 Sense & Sensibility           1       0 sensibility
##  4 Sense & Sensibility           3       0 by         
##  5 Sense & Sensibility           3       0 jane       
##  6 Sense & Sensibility           3       0 austen     
##  7 Sense & Sensibility           5       0 1811       
##  8 Sense & Sensibility          10       1 chapter    
##  9 Sense & Sensibility          10       1 1          
## 10 Sense & Sensibility          13       1 the        
## # … with 725,045 more rows
  • 텍스트 데이터를 정제하는 과정에서 종종 불필요하거나 유용하지 않는 단어는 제거할 필요가 있습니다.
  • 해당 라이브러리에서는 stop_words라는 데이터 안에 단어들이 포함되어 있습니다.
data(stop_words)
stop_words
## # A tibble: 1,149 x 2
##    word        lexicon
##    <chr>       <chr>  
##  1 a           SMART  
##  2 a's         SMART  
##  3 able        SMART  
##  4 about       SMART  
##  5 above       SMART  
##  6 according   SMART  
##  7 accordingly SMART  
##  8 across      SMART  
##  9 actually    SMART  
## 10 after       SMART  
## # … with 1,139 more rows
  • 이를 anti_join() 함수를 사용하여 제거하겠습니다.
    • tidyverse에서 anti_join() 함수의 의미는? 여기!
tidy_books <- tidy_books %>% anti_join(stop_words, by = "word")
tidy_books
## # A tibble: 217,609 x 4
##    book                line_number chapter word       
##    <fct>                     <int>   <int> <chr>      
##  1 Sense & Sensibility           1       0 sense      
##  2 Sense & Sensibility           1       0 sensibility
##  3 Sense & Sensibility           3       0 jane       
##  4 Sense & Sensibility           3       0 austen     
##  5 Sense & Sensibility           5       0 1811       
##  6 Sense & Sensibility          10       1 chapter    
##  7 Sense & Sensibility          10       1 1          
##  8 Sense & Sensibility          13       1 family     
##  9 Sense & Sensibility          13       1 dashwood   
## 10 Sense & Sensibility          13       1 settled    
## # … with 217,599 more rows
  • 이를 가지고 count() 함수를 사용하여 빈도가 높은 순으로 단어들을 체크해볼 수도 있습니다.
tidy_books %>% 
  count(word, sort = TRUE)
## # A tibble: 13,914 x 2
##    word       n
##    <chr>  <int>
##  1 miss    1855
##  2 time    1337
##  3 fanny    862
##  4 dear     822
##  5 lady     817
##  6 sir      806
##  7 day      797
##  8 emma     787
##  9 sister   727
## 10 house    699
## # … with 13,904 more rows
  • 이 결과를 ggplot2 라이브러리를 활용하여 시각화해볼 수도 있습니다.
tidy_books %>% 
  count(word, sort = TRUE) %>% 
  filter(n >= 600) %>%  # 빈도가 600회 이상인 단어만 추출
  ggplot(aes(x = reorder(word, n), y = n)) +
  geom_bar(stat = "identity", colour = "white", width = 0.8) +
  coord_flip() +
  labs(x = "words", y = "frequency")

반응형
TAGS.

Comments