Instalando el paquete

Desde que quanteda está disponible en CRAN, lo puedes instalar usando tu instalador de paquetes en R GUI’s o ejecutar las siguientes líneas:

install.packages("quanteda") 

Ver instrucciones (en inglés) en https://github.com/quanteda/quanteda para instalar la versión de GitHub.

Paquetes adicionales recomendados:

Los siguientes paquetes funcionan bien con con quanteda o lo complementan y por eso recomendamos que también los instaleis:

Creando un corpus

Cargas el paquete para acceder a funciones y data en el paquete.

library(quanteda)

Fuentes disponibles de corpus

quanteda tiene un simple y poderoso paquete adicional para cargar textos: readtext. La función principal en este paquete, readtext(), toma un archivo o set de archivos de un disco o una dirección de URL y devuelve un tipo de data.frame que puede ser usado directamente con la función de construcción de corpus (corpus()) para crear un objeto corpus en quanteda. readtext() funciona con:

  • archivos de texto (.txt);
  • archivos de valores separados por comas (.csv);
  • data en formato XML;
  • data del API de Facebook API, en formato JSON;
  • data de la API de Twitter, en formato JSON; y
  • data en formato JSON en general.

El comando constructor de corpus llamado corpus() funciona directamente sobre:

  • un vector de objetos de tipo character, por ejemplo aquellos que ya has cargado al workspace usando otras herramientas;
  • un objeto corpus VCorpus del paquete tm.
  • un data.frame que contenga una columna de texto y cualquier otro documento de metadata.

Construyendo un corpus de un vector de tipo character

El caso más simple sería crear un corpus de un vector de textos que ya estén en la memoria en R. De esta manera, el usuario avanzado de R obtiene completa flexibilidad con su elección de textos dado que hay virtualmente infinitas posibilidades de obtener un vector de textos en R.

Si ya se disponen de textos en este formato es posible llamar a la función de constructor de corpus directamente. Es posible demostrarlo en el objeto de tipo character integrado de los textos sobre políticas de inmigración extraídos de los manifiestos de partidos políticos compitiendo en la elección del Reino Unido en 2010 (llamado data_char_ukimmig2010).

myCorpus <- corpus(data_char_ukimmig2010)  # construye un corpus nuevo a partir de los textos
summary(myCorpus)
## Corpus consisting of 9 documents:
## 
##          Text Types Tokens Sentences
##           BNP  1125   3280        88
##     Coalition   142    260         4
##  Conservative   251    499        15
##        Greens   322    679        21
##        Labour   298    683        29
##        LibDem   251    483        14
##            PC    77    114         5
##           SNP    88    134         4
##          UKIP   346    723        27
## 
## Source: /home/kohei/packages/quanteda/vignettes/pkgdown/* on x86_64 by kohei
## Created: Mon Apr 16 12:52:32 2018
## Notes:

Si quisiéramos, también podríamos incorporar también a este corpus algunas variables a nivel documento – lo que quanteda llama docvars.

Esto lo hacemos utilizando la función de R llamada names() para obtener los nombres del vector de tipo character de data_char_ukimmig2010 y asignárselos a una variable de documento (docvar).

docvars(myCorpus, "Party") <- names(data_char_ukimmig2010)
docvars(myCorpus, "Year") <- 2010
summary(myCorpus)
## Corpus consisting of 9 documents:
## 
##          Text Types Tokens Sentences        Party Year
##           BNP  1125   3280        88          BNP 2010
##     Coalition   142    260         4    Coalition 2010
##  Conservative   251    499        15 Conservative 2010
##        Greens   322    679        21       Greens 2010
##        Labour   298    683        29       Labour 2010
##        LibDem   251    483        14       LibDem 2010
##            PC    77    114         5           PC 2010
##           SNP    88    134         4          SNP 2010
##          UKIP   346    723        27         UKIP 2010
## 
## Source: /home/kohei/packages/quanteda/vignettes/pkgdown/* on x86_64 by kohei
## Created: Mon Apr 16 12:52:32 2018
## Notes:

Si quisiéramos etiquetar cada documento con meta-data adicional no considerada como una variable de documento de interés para el análisis, sino como algo que precisamos conocer como un atributo del documento también sería posible añadirla al corpus.

metadoc(myCorpus, "language") <- "english"
metadoc(myCorpus, "docsource")  <- paste("data_char_ukimmig2010", 1:ndoc(myCorpus), sep = "_")
summary(myCorpus, showmeta = TRUE)
## Corpus consisting of 9 documents:
## 
##          Text Types Tokens Sentences _language              _docsource
##           BNP  1125   3280        88   english data_char_ukimmig2010_1
##     Coalition   142    260         4   english data_char_ukimmig2010_2
##  Conservative   251    499        15   english data_char_ukimmig2010_3
##        Greens   322    679        21   english data_char_ukimmig2010_4
##        Labour   298    683        29   english data_char_ukimmig2010_5
##        LibDem   251    483        14   english data_char_ukimmig2010_6
##            PC    77    114         5   english data_char_ukimmig2010_7
##           SNP    88    134         4   english data_char_ukimmig2010_8
##          UKIP   346    723        27   english data_char_ukimmig2010_9
##         Party Year
##           BNP 2010
##     Coalition 2010
##  Conservative 2010
##        Greens 2010
##        Labour 2010
##        LibDem 2010
##            PC 2010
##           SNP 2010
##          UKIP 2010
## 
## Source: /home/kohei/packages/quanteda/vignettes/pkgdown/* on x86_64 by kohei
## Created: Mon Apr 16 12:52:32 2018
## Notes:

El último comando, metadoc, permite definir tu propios campos de meta-data del documento. Nota que al asignar solo el valor "english", R ha reciclado el valor hasta que coincide con el número de documentos en el corpus. Creando una simple etiqueta para nuestro campo de metadocumento docsource, hemos usado la función de quanteda llamada ndoc() para recuperar el número de documentos en nuestro corpus. Esta función es deliberadamente designada para trabajar en una manera similar a las funciones que probablemente ya utilices en R, como nrow() y ncol().

Cargando archivos usando el paquete readtext

require(readtext)

# Twitter json
mytf1 <- readtext("~/Dropbox/QUANTESS/social media/zombies/tweets.json")
myCorpusTwitter <- corpus(mytf1)
summary(myCorpusTwitter, 5)
# generic json - needs a textfield specifier
mytf2 <- readtext("~/Dropbox/QUANTESS/Manuscripts/collocations/Corpora/sotu/sotu.json",
                 textfield = "text")
summary(corpus(mytf2), 5)
# text file
mytf3 <- readtext("~/Dropbox/QUANTESS/corpora/project_gutenberg/pg2701.txt", cache = FALSE)
summary(corpus(mytf3), 5)
# multiple text files
mytf4 <- readtext("~/Dropbox/QUANTESS/corpora/inaugural/*.txt", cache = FALSE)
summary(corpus(mytf4), 5)
# multiple text files with docvars from filenames
mytf5 <- readtext("~/Dropbox/QUANTESS/corpora/inaugural/*.txt", 
                 docvarsfrom = "filenames", sep = "-", docvarnames = c("Year", "President"))
summary(corpus(mytf5), 5)
# XML data
mytf6 <- readtext("~/Dropbox/QUANTESS/quanteda_working_files/xmlData/plant_catalog.xml", 
                 textfield = "COMMON")
summary(corpus(mytf6), 5)
# csv file
write.csv(data.frame(inaugSpeech = texts(data_corpus_inaugural), 
                    docvars(data_corpus_inaugural)),
         file = "/tmp/inaug_texts.csv", row.names = FALSE)
mytf7 <- readtext("/tmp/inaug_texts.csv", textfield = "inaugSpeech")
summary(corpus(mytf7), 5)

Cómo funciona un corpus de quanteda

Principios del Corpus

Un corpus está diseñado para ser una “librería” original de documentos que han sido convertidos a formato plano, texto codificado en UTF-8, y guardado junto con meta-data en a nivel de corpus y a nivel de documento. Tenemos un nombre especial para meta-data a nivel de documento: docvars. Estas son variables o características que describen atributos de cada documento.

Un corpus está diseñado para ser un contenedor de textos más o menos estático en lo que respecta a su procesamiento y análisis. Esto significa que los textos en el corpus no están disenado para ser cambiados internamente a través de (por ejemplo) limpieza o preprocesamiento, como stemming o removiendo la puntuación. Más que nada, los textos pueden ser extraídos del corpus como parte del procesamiento y asignados a objetos nuevos, pero la idea es que los corpus se conserven como una copia de referencia original para que otros análisis, por ejemplo aquellos en que stems y puntuación son necesarios, como analizar un índice, pueden ser realizados sobre el mismo corpus.

Para extraer texto de un corpus, es posible utilizar el extractor llamado texts().

texts(data_corpus_inaugural)[2]
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              1793-Washington 
## "Fellow citizens, I am again called upon by the voice of my country to execute the functions of its Chief Magistrate. When the occasion proper for it shall arrive, I shall endeavor to express the high sense I entertain of this distinguished honor, and of the confidence which has been reposed in me by the people of united America.\n\nPrevious to the execution of any official act of the President the Constitution requires an oath of office. This oath I am now about to take, and in your presence: That if it shall be found during my administration of the Government I have in any instance violated willingly or knowingly the injunctions thereof, I may (besides incurring constitutional punishment) be subject to the upbraidings of all who are now witnesses of the present solemn ceremony.\n\n "

Para obtener la data resumida de textos de un corpus, se puede llamar al método summary() definido para un corpus.

summary(data_corpus_irishbudget2010)
## Corpus consisting of 14 documents:
## 
##                                   Text Types Tokens Sentences year debate
##        2010_BUDGET_01_Brian_Lenihan_FF  1953   8641       374 2010 BUDGET
##       2010_BUDGET_02_Richard_Bruton_FG  1040   4446       217 2010 BUDGET
##         2010_BUDGET_03_Joan_Burton_LAB  1624   6393       307 2010 BUDGET
##        2010_BUDGET_04_Arthur_Morgan_SF  1595   7107       343 2010 BUDGET
##          2010_BUDGET_05_Brian_Cowen_FF  1629   6599       250 2010 BUDGET
##           2010_BUDGET_06_Enda_Kenny_FG  1148   4232       153 2010 BUDGET
##      2010_BUDGET_07_Kieran_ODonnell_FG   678   2297       133 2010 BUDGET
##       2010_BUDGET_08_Eamon_Gilmore_LAB  1181   4177       201 2010 BUDGET
##     2010_BUDGET_09_Michael_Higgins_LAB   488   1286        44 2010 BUDGET
##        2010_BUDGET_10_Ruairi_Quinn_LAB   439   1284        59 2010 BUDGET
##      2010_BUDGET_11_John_Gormley_Green   401   1030        49 2010 BUDGET
##        2010_BUDGET_12_Eamon_Ryan_Green   510   1643        90 2010 BUDGET
##      2010_BUDGET_13_Ciaran_Cuffe_Green   442   1240        45 2010 BUDGET
##  2010_BUDGET_14_Caoimhghin_OCaolain_SF  1188   4044       176 2010 BUDGET
##  number      foren     name party
##      01      Brian  Lenihan    FF
##      02    Richard   Bruton    FG
##      03       Joan   Burton   LAB
##      04     Arthur   Morgan    SF
##      05      Brian    Cowen    FF
##      06       Enda    Kenny    FG
##      07     Kieran ODonnell    FG
##      08      Eamon  Gilmore   LAB
##      09    Michael  Higgins   LAB
##      10     Ruairi    Quinn   LAB
##      11       John  Gormley Green
##      12      Eamon     Ryan Green
##      13     Ciaran    Cuffe Green
##      14 Caoimhghin OCaolain    SF
## 
## Source: /Users/kbenoit/Dropbox (Personal)/GitHub/quanteda/* on x86_64 by kbenoit
## Created: Wed Jun 28 22:04:18 2017
## Notes:

Se puede guardar el output del comando summary como un data frame y graficar algunos estadísticos descriptivos con esta información:

tokenInfo <- summary(data_corpus_inaugural)
if (require(ggplot2))
   ggplot(data=tokenInfo, aes(x = Year, y = Tokens, group = 1)) + geom_line() + geom_point() +
       scale_x_continuous(labels = c(seq(1789,2012,12)), breaks = seq(1789,2012,12) ) 
## Loading required package: ggplot2

# El discurso inaugural más largo: William Henry Harrison
tokenInfo[which.max(tokenInfo$Tokens), ] 
##             Text Types Tokens Sentences Year President     FirstName
## 14 1841-Harrison  1896   9144       210 1841  Harrison William Henry

Herramientas para manejar objetos de corpus

Juntando dos objetos de corpus

El operador + provee un método simple para concatenar dos objetos corpus. Si contenían diferentes sets de variables a nivel documento las unirá de manera que no se pierda nada de información. La meta-data a nivel corpus también queda concatenada.

library(quanteda)
mycorpus1 <- corpus(data_corpus_inaugural[1:5])
mycorpus2 <- corpus(data_corpus_inaugural[53:58])
mycorpus3 <- mycorpus1 + mycorpus2
summary(mycorpus3)
## Corpus consisting of 11 documents:
## 
##             Text Types Tokens Sentences
##  1789-Washington   625   1538        23
##  1793-Washington    96    147         4
##       1797-Adams   826   2578        37
##   1801-Jefferson   717   1927        41
##   1805-Jefferson   804   2381        45
##     1997-Clinton   773   2449       111
##        2001-Bush   621   1808        97
##        2005-Bush   773   2319       100
##       2009-Obama   938   2711       110
##       2013-Obama   814   2317        88
##       2017-Trump   582   1660        88
## 
## Source: Combination of corpuses mycorpus1 and mycorpus2
## Created: Mon Apr 16 12:52:34 2018
## Notes:

Armando subsets dentro de objetos corpus

Hay un método de la función corpus_subset() definida por objetos corpus, donde un nuevo corpus puede ser extraído en base a condiciones lógicas aplicadas a docvars:

summary(corpus_subset(data_corpus_inaugural, Year > 1990))
## Corpus consisting of 7 documents:
## 
##          Text Types Tokens Sentences Year President FirstName
##  1993-Clinton   642   1833        81 1993   Clinton      Bill
##  1997-Clinton   773   2449       111 1997   Clinton      Bill
##     2001-Bush   621   1808        97 2001      Bush George W.
##     2005-Bush   773   2319       100 2005      Bush George W.
##    2009-Obama   938   2711       110 2009     Obama    Barack
##    2013-Obama   814   2317        88 2013     Obama    Barack
##    2017-Trump   582   1660        88 2017     Trump Donald J.
## 
## Source: Gerhard Peters and John T. Woolley. The American Presidency Project.
## Created: Tue Jun 13 14:51:47 2017
## Notes: http://www.presidency.ucsb.edu/inaugurals.php
summary(corpus_subset(data_corpus_inaugural, President == "Adams"))
## Corpus consisting of 2 documents:
## 
##        Text Types Tokens Sentences Year President   FirstName
##  1797-Adams   826   2578        37 1797     Adams        John
##  1825-Adams  1003   3152        74 1825     Adams John Quincy
## 
## Source: Gerhard Peters and John T. Woolley. The American Presidency Project.
## Created: Tue Jun 13 14:51:47 2017
## Notes: http://www.presidency.ucsb.edu/inaugurals.php

Explorando textos de corpus

La función kwic (keywords-in-context) realiza una búsqueda de una palabra y permite visualizar los contextos en los que aparece:

kwic(data_corpus_inaugural, "terror")
##                                                                     
##     [1797-Adams, 1325]              fraud or violence, by | terror |
##  [1933-Roosevelt, 112] nameless, unreasoning, unjustified | terror |
##  [1941-Roosevelt, 287]      seemed frozen by a fatalistic | terror |
##    [1961-Kennedy, 866]    alter that uncertain balance of | terror |
##     [1981-Reagan, 813]     freeing all Americans from the | terror |
##   [1997-Clinton, 1055]        They fuel the fanaticism of | terror |
##   [1997-Clinton, 1655]  maintain a strong defense against | terror |
##     [2009-Obama, 1632]     advance their aims by inducing | terror |
##                                   
##  , intrigue, or venality          
##  which paralyzes needed efforts to
##  , we proved that this            
##  that stays the hand of           
##  of runaway living costs.         
##  . And they torment the           
##  and destruction. Our children    
##  and slaughtering innocents, we
kwic(data_corpus_inaugural, "terror", valuetype = "regex")
##                                                                           
##     [1797-Adams, 1325]                   fraud or violence, by |  terror  
##  [1933-Roosevelt, 112]      nameless, unreasoning, unjustified |  terror  
##  [1941-Roosevelt, 287]           seemed frozen by a fatalistic |  terror  
##    [1961-Kennedy, 866]         alter that uncertain balance of |  terror  
##    [1961-Kennedy, 990]               of science instead of its |  terrors 
##     [1981-Reagan, 813]          freeing all Americans from the |  terror  
##    [1981-Reagan, 2196]        understood by those who practice | terrorism
##   [1997-Clinton, 1055]             They fuel the fanaticism of |  terror  
##   [1997-Clinton, 1655]       maintain a strong defense against |  terror  
##     [2009-Obama, 1632]          advance their aims by inducing |  terror  
##     [2017-Trump, 1117] civilized world against radical Islamic | terrorism
##                                     
##  | , intrigue, or venality          
##  | which paralyzes needed efforts to
##  | , we proved that this            
##  | that stays the hand of           
##  | . Together let us explore        
##  | of runaway living costs.         
##  | and prey upon their neighbors    
##  | . And they torment the           
##  | and destruction. Our children    
##  | and slaughtering innocents, we   
##  | , which we will eradicate
kwic(data_corpus_inaugural, "communist*")
##                                                                   
##   [1949-Truman, 834] the actions resulting from the | Communist  |
##  [1961-Kennedy, 519]             -- not because the | Communists |
##                            
##  philosophy are a threat to
##  may be doing it,

En el summary de arriba, las variables Year (año) y President (presidente) son variables asociadas a cada documento. Es posible acceder a dichas variables con la función ‘docvars()’

# inspect the document-level variables
head(docvars(data_corpus_inaugural))
##                 Year  President FirstName
## 1789-Washington 1789 Washington    George
## 1793-Washington 1793 Washington    George
## 1797-Adams      1797      Adams      John
## 1801-Jefferson  1801  Jefferson    Thomas
## 1805-Jefferson  1805  Jefferson    Thomas
## 1809-Madison    1809    Madison     James
# inspect the corpus-level metadata
metacorpus(data_corpus_inaugural)
## $source
## [1] "Gerhard Peters and John T. Woolley. The American Presidency Project."
## 
## $notes
## [1] "http://www.presidency.ucsb.edu/inaugurals.php"
## 
## $created
## [1] "Tue Jun 13 14:51:47 2017"

Más corpora están disponibles en el repositorio quanteda.corpora.

Extrayendo atributos de un corpus

Para realizar análisis estadísticos tales como document scaling, es necesario extraer una matriz asociando valores de ciertos atributos con cada documento. En quanteda, se utiliza la función ‘dfm’ para producir dicha matriz. ‘dfm’, por sus siglas en inglés document-feature matrix o matriz documento-atributo en castellano, siempre se refiere a los documentos como filas y a los atributos como columnas. Se determinó esta orientación de las dimensiones dado que es estándar en el campo de análisis de datos que las unidades de análisis se computen en las filas y los atributos o variables se computen en las columnas. Se denominan “atributos” en vez de términos porque los atributos son más generales que los términos: pueden ser definidos como términos crudos, términos stemmed, términos de partes de discurso, términos luego de la remoción de las stopwords o una clase de diccionario al que pertenece un término. Los atributos pueden ser enteramente generales, como ngrams o dependencias sintácticas y dejamos esto abierto.

Convirtiendo textos en tokens

Para convertir un texto en tokens de manera simple, quanteda provee un poderoso comando denominadotokens(). Produce un objeto intermedio que consiste en una lista de tokens en forma de vectores de caracteres, donde cada elemento de la lista corresponde con un documento de input.

El comando tokens() es deliberadamente conservador, es decir, que no remueve nada del texto excepto que se le especifique explícitamente que lo haga.

txt <- c(text1 = "This is $10 in 999 different ways,\n up and down; left and right!", 
        text2 = "@kenbenoit working: on #quanteda 2day\t4ever, http://textasdata.com?page=123.")
tokens(txt)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "$"         "10"        "in"       
##  [6] "999"       "different" "ways"      ","         "up"       
## [11] "and"       "down"      ";"         "left"      "and"      
## [16] "right"     "!"        
## 
## text2 :
##  [1] "@kenbenoit"     "working"        ":"              "on"            
##  [5] "#quanteda"      "2day"           "4ever"          ","             
##  [9] "http"           ":"              "/"              "/"             
## [13] "textasdata.com" "?"              "page"           "="             
## [17] "123"            "."
tokens(txt, remove_numbers = TRUE,  remove_punct = TRUE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "in"        "different" "ways"     
##  [6] "up"        "and"       "down"      "left"      "and"      
## [11] "right"    
## 
## text2 :
## [1] "@kenbenoit"     "working"        "on"             "#quanteda"     
## [5] "2day"           "4ever"          "http"           "textasdata.com"
## [9] "page"
tokens(txt, remove_numbers = FALSE, remove_punct = TRUE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "10"        "in"        "999"      
##  [6] "different" "ways"      "up"        "and"       "down"     
## [11] "left"      "and"       "right"    
## 
## text2 :
##  [1] "@kenbenoit"     "working"        "on"             "#quanteda"     
##  [5] "2day"           "4ever"          "http"           "textasdata.com"
##  [9] "page"           "123"
tokens(txt, remove_numbers = TRUE,  remove_punct = FALSE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "$"         "in"        "different"
##  [6] "ways"      ","         "up"        "and"       "down"     
## [11] ";"         "left"      "and"       "right"     "!"        
## 
## text2 :
##  [1] "@kenbenoit"     "working"        ":"              "on"            
##  [5] "#quanteda"      "2day"           "4ever"          ","             
##  [9] "http"           ":"              "/"              "/"             
## [13] "textasdata.com" "?"              "page"           "="             
## [17] "."
tokens(txt, remove_numbers = FALSE, remove_punct = FALSE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "$"         "10"        "in"       
##  [6] "999"       "different" "ways"      ","         "up"       
## [11] "and"       "down"      ";"         "left"      "and"      
## [16] "right"     "!"        
## 
## text2 :
##  [1] "@kenbenoit"     "working"        ":"              "on"            
##  [5] "#quanteda"      "2day"           "4ever"          ","             
##  [9] "http"           ":"              "/"              "/"             
## [13] "textasdata.com" "?"              "page"           "="             
## [17] "123"            "."
tokens(txt, remove_numbers = FALSE, remove_punct = FALSE, remove_separators = FALSE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      " "         "is"        " "         "$"        
##  [6] "10"        " "         "in"        " "         "999"      
## [11] " "         "different" " "         "ways"      ","        
## [16] "\n"        " "         "up"        " "         "and"      
## [21] " "         "down"      ";"         " "         "left"     
## [26] " "         "and"       " "         "right"     "!"        
## 
## text2 :
##  [1] "@kenbenoit"     " "              "working"        ":"             
##  [5] " "              "on"             " "              "#quanteda"     
##  [9] " "              "2day"           "\t"             "4ever"         
## [13] ","              " "              "http"           ":"             
## [17] "/"              "/"              "textasdata.com" "?"             
## [21] "page"           "="              "123"            "."

También existe la opción de convertir en token los caracteres:

tokens("Great website: http://textasdata.com?page=123.", what = "character")
## tokens from 1 document.
## text1 :
##  [1] "G" "r" "e" "a" "t" "w" "e" "b" "s" "i" "t" "e" ":" "h" "t" "t" "p"
## [18] ":" "/" "/" "t" "e" "x" "t" "a" "s" "d" "a" "t" "a" "." "c" "o" "m"
## [35] "?" "p" "a" "g" "e" "=" "1" "2" "3" "."
tokens("Great website: http://textasdata.com?page=123.", what = "character", 
        remove_separators = FALSE)
## tokens from 1 document.
## text1 :
##  [1] "G" "r" "e" "a" "t" " " "w" "e" "b" "s" "i" "t" "e" ":" " " "h" "t"
## [18] "t" "p" ":" "/" "/" "t" "e" "x" "t" "a" "s" "d" "a" "t" "a" "." "c"
## [35] "o" "m" "?" "p" "a" "g" "e" "=" "1" "2" "3" "."

y las oraciones:

# sentence level         
tokens(c("Kurt Vongeut said; only assholes use semi-colons.", 
          "Today is Thursday in Canberra:  It is yesterday in London.", 
          "En el caso de que no puedas ir con ellos, ¿quieres ir con nosotros?"), 
         what = "sentence")
## tokens from 3 documents.
## text1 :
## [1] "Kurt Vongeut said; only assholes use semi-colons."
## 
## text2 :
## [1] "Today is Thursday in Canberra:  It is yesterday in London."
## 
## text3 :
## [1] "En el caso de que no puedas ir con ellos, ¿quieres ir con nosotros?"

Construyendo una matriz de documentos y atributos

Convertir los textos en tokens es una opción intermedia y la mayoría de los usuarios querrán directamente construir la matriz de documentos y atributos. Para hacer esto existe la función de navaja suiza llamada dfm(), que realiza la tokenización y tabula los atributos extraídos dentro de una matriz de documentos por atributos. A diferencia del enfoque conservador de tokens(), la función dfm() aplica ciertas opciones por default, como tolower() – una función separada para transformar textos a minúsculas – y remueve puntuación. De todos modos, todas las opciones de tokens() se pueden pasar a dfm().

myCorpus <- corpus_subset(data_corpus_inaugural, Year > 1990)

# make a dfm
myDfm <- dfm(myCorpus)
myDfm[, 1:5]
## Document-feature matrix of: 7 documents, 5 features (0% sparse).
## 7 x 5 sparse Matrix of class "dfm"
##               features
## docs           my fellow citizens   , today
##   1993-Clinton  7      5        2 139    10
##   1997-Clinton  6      7        7 131     5
##   2001-Bush     3      1        9 110     2
##   2005-Bush     2      3        6 120     3
##   2009-Obama    2      1        1 130     6
##   2013-Obama    3      3        6  99     4
##   2017-Trump    1      1        4  96     4

Otras opciones para incluyen remover las stopwords y realizar stemming de los tokens.

# make a dfm, removing stopwords and applying stemming
myStemMat <- dfm(myCorpus, remove = stopwords("english"), stem = TRUE, remove_punct = TRUE)
myStemMat[, 1:5]
## Document-feature matrix of: 7 documents, 5 features (17.1% sparse).
## 7 x 5 sparse Matrix of class "dfm"
##               features
## docs           fellow citizen today celebr mysteri
##   1993-Clinton      5       2    10      4       1
##   1997-Clinton      7       8     6      1       0
##   2001-Bush         1      10     2      0       0
##   2005-Bush         3       7     3      2       0
##   2009-Obama        1       1     6      2       0
##   2013-Obama        3       8     6      1       0
##   2017-Trump        1       4     5      3       1

La opción ‘remove’ provee una lista de tokens a ser ignorados. La mayoría de los usuarios proveerán una lista de ‘stop words’ predefinidas para varios idiomas, accediendo a través de la función stopwords():

head(stopwords("english"), 20)
##  [1] "i"          "me"         "my"         "myself"     "we"        
##  [6] "our"        "ours"       "ourselves"  "you"        "your"      
## [11] "yours"      "yourself"   "yourselves" "he"         "him"       
## [16] "his"        "himself"    "she"        "her"        "hers"
head(stopwords("russian"), 10)
##  [1] "и"   "в"   "во"  "не"  "что" "он"  "на"  "я"   "с"   "со"
head(stopwords("arabic"), 10)
## Warning: 'stopwords(language = "ar")' is deprecated.
## Use 'stopwords(language = "ar", source = "misc")' instead.
## See help("Deprecated")
##  [1] "فى"  "في"  "كل"  "لم"  "لن"  "له"  "من"  "هو"  "هي"  "قوة"

Visualizando la matriz de documentos y atributos

El dfm puede ser inspeccionado en el panel de ‘Environment’ en Rstudio o llamando la función View en R. Llamando la función plot en un dfm se presentará una nube de palabras usando el paquete wordcloud package

mydfm <- dfm(data_char_ukimmig2010, remove = stopwords("english"), remove_punct = TRUE)
mydfm
## Document-feature matrix of: 9 documents, 1,547 features (83.8% sparse).

Para acceder a la lista de los atributos más frecuentes es posible utilizar topfeatures():

topfeatures(mydfm, 20)  # 20 top words
## immigration     british      people      asylum     britain          uk 
##          66          37          35          29          28          27 
##      system  population     country         new  immigrants      ensure 
##          27          21          20          19          17          17 
##       shall citizenship      social    national         bnp     illegal 
##          17          16          14          14          13          13 
##        work     percent 
##          13          12

Para un objeto dfm se puede graficar una nube de palabras usando textplot_wordcloud(). Esta función pasa argumentos a wordcloud() del paquete wordcloud y puede embellecer el gráfico usando los mismos argumentos:

set.seed(100)
textplot_wordcloud(mydfm, min.freq = 6, random.order = FALSE,
                  rot.per = .25, 
                  colors = RColorBrewer::brewer.pal(8,"Dark2"))

Agrupando documentos por variable

Frecuentemente estamos interesados en analizar cómo textos difieren según factores sustantivos que pueden estar codificados en las variables de documento en vez de simplemente por los límites de los archivos. En esos casos es posible agrupar los documentos que comparten el mismo valor por variable de documento cuando se crea un dfm:

byPartyDfm <- dfm(data_corpus_irishbudget2010, groups = "party", 
                  remove = stopwords("english"), remove_punct = TRUE)

We can sort this dfm, and inspect it:

dfm_sort(byPartyDfm)[, 1:10]
## Document-feature matrix of: 5 documents, 10 features (0% sparse).
## 5 x 10 sparse Matrix of class "dfm"
##        features
## docs    people budget government public minister tax economy pay jobs
##   FF        23     44         47     65       11  60      37  41   41
##   FG        78     71         61     47       62  11      20  29   17
##   Green     15     26         19      4        4  11      16   4   15
##   LAB       69     66         36     32       54  47      37  24   20
##   SF        81     53         73     31       39  34      50  24   27
##        features
## docs    billion
##   FF         32
##   FG         21
##   Green       3
##   LAB        34
##   SF         29

Agrupando palabras por diccionario o clase de equivalencia

Para algunas aplicaciones se tiene conocimiento previo del conjunto de palabras que son indicativas de rasgos que quisiéramos medir. Por ejemplo, una lista general de palabras positivas puede indicar sentimiento positivo en un reseña de una película tal tenemos un diccionario de términos políticos asociados a una tendencia ideológica en particular. En estos casos, a veces es útil tratar estos grupos de palabras como equivalentes para los propósitos del análisis y sumar las veces en que se utiliza agregándolas por clase.

Por ejemplo, observemos cómo palabras asociadas al terrorismo y palabras asociadas con la economía varían por presidente en el corpus de discursos inaugurales de presidentes de Estados Unidos. Del corpus original seleccionamos los presidentes desde Clinton:

recentCorpus <- corpus_subset(data_corpus_inaugural, Year > 1991)

Ahora definimos un diccionario de muestra:

myDict <- dictionary(list(terror = c("terrorism", "terrorists", "threat"),
                         economy = c("jobs", "business", "grow", "work")))

Se puede usar el diccionario cuando creamos el dfm:

byPresMat <- dfm(recentCorpus, dictionary = myDict)
byPresMat
## Document-feature matrix of: 7 documents, 2 features (14.3% sparse).
## 7 x 2 sparse Matrix of class "dfm"
##               features
## docs           terror economy
##   1993-Clinton      0       8
##   1997-Clinton      1       8
##   2001-Bush         0       4
##   2005-Bush         1       6
##   2009-Obama        1      10
##   2013-Obama        1       6
##   2017-Trump        1       5

El constructor de la función dictionary() también funciona con el formato de dos diccionarios externos comunes: los formatos LIWC y Provalis Research’s Wordstat. Por ejemplo, es posible cargar el LIWC y aplicarlo al corpus de discursos inaugurales de presidentes:

liwcdict <- dictionary(file = "~/Dropbox/QUANTESS/dictionaries/LIWC/LIWC2001_English.dic",
                       format = "LIWC")
liwcdfm <- dfm(data_corpus_inaugural[52:58], dictionary = liwcdict)
liwcdfm[, 1:10]

Más ejemplos

Similitudes entre textos

presDfm <- dfm(corpus_subset(data_corpus_inaugural, Year > 1980), 
              remove = stopwords("english"), stem = TRUE, remove_punct = TRUE)
obamaSimil <- textstat_simil(presDfm, c("2009-Obama" , "2013-Obama"), 
                            margin = "documents", method = "cosine")
obamaSimil
##              2009-Obama 2013-Obama
## 1981-Reagan   0.6229949  0.6376412
## 1985-Reagan   0.6434472  0.6629428
## 1989-Bush     0.6253944  0.5784290
## 1993-Clinton  0.6280946  0.6265428
## 1997-Clinton  0.6593018  0.6466030
## 2001-Bush     0.6018113  0.6193608
## 2005-Bush     0.5266249  0.5867178
## 2009-Obama    1.0000000  0.6815711
## 2013-Obama    0.6815711  1.0000000
## 2017-Trump    0.5192075  0.5160104
# dotchart(as.list(obamaSimil)$"2009-Obama", xlab = "Cosine similarity")

Se puede utilizar estas distancias para graficar un dendrograma, armando clusters por presidente:

data(data_corpus_SOTU, package = "quanteda.corpora")
presDfm <- dfm(corpus_subset(data_corpus_SOTU, Date > as.Date("1980-01-01")), 
              stem = TRUE, remove_punct = TRUE,
              remove = stopwords("english"))
presDfm <- dfm_trim(presDfm, min_count = 5, min_docfreq = 3)

# Clustering jerárquico- obtener distancias en dfm normalizado
presDistMat <- textstat_dist(dfm_weight(presDfm, "prop"))
# Clustering jerárquico de los objetos de distancia
presCluster <- hclust(presDistMat)
# etiqueta con nombres de documentos
presCluster$labels <- docnames(presDfm)
# graficar como dendrograma
lot(presCluster, xlab = "", sub = "", main = "Euclidean Distance on Normalized Token Frequency")

También se puede observar similitudes de los términos:

sim <- textstat_simil(presDfm, c("fair", "health", "terror"), method = "cosine", margin = "features")
lapply(as.list(sim), head, 10)
## $fair
##   economi     begin jefferson    author     faith      call   struggl 
## 0.9080252 0.9075951 0.8981462 0.8944272 0.8866586 0.8608285 0.8451543 
##      best     creat    courag 
## 0.8366600 0.8347300 0.8326664 
## 
## $health
##     shape   generat     wrong    common  knowledg    planet      task 
## 0.9045340 0.8971180 0.8944272 0.8888889 0.8888889 0.8819171 0.8728716 
##    demand       eye     defin 
## 0.8666667 0.8660254 0.8642416 
## 
## $terror
##    potenti  adversari commonplac     miracl     racial     bounti 
##  0.9036961  0.9036961  0.8944272  0.8944272  0.8944272  0.8944272 
##     martin      dream      polit   guarante 
##  0.8944272  0.8624394  0.8500000  0.8485281

Escalamiento de posiciones de documentos

Aquí realizamos una demostración de escalamiento de documentos unsupervised comparado con el modelo “wordfish”:

# make prettier document names
ieDfm <- dfm(data_corpus_irishbudget2010)
textmodel_wordfish(ieDfm, dir = c(2, 1))
## 
## Call:
## textmodel_wordfish.dfm(x = ieDfm, dir = c(2, 1))
## 
## Dispersion: poisson; direction: 2 < 1; 14 documents; 5140 features.

Topic models

quanteda hace muy sencillo ajustar topic models también. Por ejemplo:

quantdfm <- dfm(data_corpus_irishbudget2010, 
               remove_punct = TRUE, remove_numbers = TRUE, remove = stopwords("english"))
quantdfm <- dfm_trim(quantdfm, min_count = 4, max_docfreq = 10)
## Warning in dfm_trim.dfm(quantdfm, min_count = 4, max_docfreq = 10):
## min_count is deprecated, use min_termfreq
quantdfm
## Document-feature matrix of: 14 documents, 1,263 features (64.5% sparse).
if (require(topicmodels)) {
   myLDAfit20 <- LDA(convert(quantdfm, to = "topicmodels"), k = 20)
   get_terms(myLDAfit20, 5)
}
## Loading required package: topicmodels
##      Topic 1     Topic 2      Topic 3   Topic 4   Topic 5       
## [1,] "taoiseach" "support"    "levy"    "might"   "enterprising"
## [2,] "employees" "million"    "million" "world"   "society"     
## [3,] "rate"      "investment" "carbon"  "comes"   "sense"       
## [4,] "referred"  "welfare"    "change"  "wealthy" "equal"       
## [5,] "debate"    "back"       "welfare" "others"  "nation"      
##      Topic 6     Topic 7  Topic 8  Topic 9      Topic 10   Topic 11     
## [1,] "system"    "fianna" "fáil"   "measures"   "national" "kind"       
## [2,] "failed"    "fáil"   "fianna" "welfare"    "irish"    "imagination"
## [3,] "real"      "side"   "even"   "level"      "fianna"   "policies"   
## [4,] "vision"    "level"  "needs"  "million"    "support"  "wit"        
## [5,] "ministers" "third"  "never"  "investment" "families" "create"     
##      Topic 12  Topic 13    Topic 14    Topic 15   Topic 16      Topic 17  
## [1,] "child"   "taoiseach" "care"      "welfare"  "development" "spending"
## [2,] "benefit" "fine"      "welfare"   "system"   "projects"    "review"  
## [3,] "day"     "gael"      "per"       "taxation" "welfare"     "million" 
## [4,] "today"   "may"       "allowance" "fianna"   "scheme"      "scheme"  
## [5,] "bank"    "irish"     "hit"       "fáil"     "reduction"   "details" 
##      Topic 18     Topic 19      Topic 20  
## [1,] "national"   "alternative" "strategy"
## [2,] "cutting"    "citizenship" "confront"
## [3,] "spending"   "wealth"      "seen"    
## [4,] "banks"      "adjustment"  "needed"  
## [5,] "experience" "breaks"      "failed"