library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.3 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ ggplot2 3.4.3 ✔ tibble 3.2.1
## ✔ lubridate 1.9.2 ✔ tidyr 1.3.0
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidytext)
Un problema muy importante en NLP es tratar de generar alguna forma de input sobre el contenido de un texto sin leerlo en su totalidad (“lectura distante”). Esto supone intentar cuantificar los contenidos de un texto. Vamos a ir viendo unas cuantas técnicas relativamente sofisticadas vinculadas al modelado de tópicos y a la constitución de embeddings de palabras más adelante, pero por ahora vamos a seguir contando palabras pero de forma más inteligente. ¿Podemos a partir del conteo de palabras llegar a tener una idea del contenido de un texto? Como vimos, una primera métrica de la importancia de una palabra es la llamada term frequency (\(tf\)): la frecuencia con la que aparece una palabra en un documento.
Sin embargo, hay palabras en un documento que aparecen muchas veces pero que no son importantes; en castellano, probablemente palabras como “el”, “es”, “de”, etc. Podríamos adoptar el enfoque de agregar palabras como estas a una lista de palabras vacías (stopwords) y eliminarlas antes del análisis, pero es posible que algunas de estas palabras sean más importantes en algunos documentos que en otros. Una lista de stopwords puede ser un primer paso pero no es el único ni, necesariamente, el más importante para ajustar la frecuencia de los términos para palabras de uso común.
Vamos a trabajar con dos métricas. Podemos analizar la “informatividad” de un término observando la inverse document frequency (\(idf\)). Esta métrica disminuye el peso de las palabras de uso común y aumenta el peso de las palabras que no se usan mucho en una colección de documentos.
Finalmente, podemos calcular la \(tf-idf\) que combina ambas métricas multiplicándolas: es la frecuencia de cada término, ajustada o ponderada por la importancia que tiene en el corpus.
Vamos a seguir con los 3 tomos de los diarios de Renzi. Examinemos
primero \(tf\) y luego \(tf-idf\). Lo interesante de todo esto es
que si nos mantenemos en nuestro formato “tidy” vamos a poder comenzar
simplemente usando verbos dplyr como group_by()
y
join()
. ¿Cuáles son las palabras más utilizadas en los
diarios? (También vamos a calcular el total de palabras en cada novela
aquí, para uso posterior).
renzi <- read_csv('../data/renzi.csv') %>%
mutate(tomo = case_when(
tomo == '1_diarios_renzi_años_de_formacion.txt' ~ 'I-Años de formación',
tomo == '2_diarios_renzi_los_años_felices.txt' ~ 'II-Los años felices',
tomo == '3_diarios_renzi_un_dia_en_la_vida.txt' ~ 'III-Un día en la vida',
))
## Rows: 1997 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): tomo, entry
## dbl (1): chapter
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
book_words <- renzi %>%
mutate(entry_number = row_number()) %>%
unnest_tokens(output = word,
input = entry) %>%
group_by(tomo, word) %>%
summarise(n = n()) %>%
arrange(desc(n)) %>%
ungroup()
## `summarise()` has grouped output by 'tomo'. You can override using the
## `.groups` argument.
total_words <- book_words %>%
group_by(tomo) %>%
summarize(total = sum(n))
book_words <- book_words %>%
left_join(total_words) %>%
ungroup() %>%
arrange(desc(n))
## Joining with `by = join_by(tomo)`
book_words
## # A tibble: 42,541 × 4
## tomo word n total
## <chr> <chr> <int> <int>
## 1 II-Los años felices de 8362 151724
## 2 I-Años de formación de 6811 133476
## 3 II-Los años felices la 6606 151724
## 4 I-Años de formación la 5792 133476
## 5 III-Un día en la vida de 5561 101377
## 6 II-Los años felices que 4998 151724
## 7 I-Años de formación que 4541 133476
## 8 II-Los años felices en 4440 151724
## 9 III-Un día en la vida la 4368 101377
## 10 II-Los años felices el 4248 151724
## # ℹ 42,531 more rows
Esta tibble book_words
presenta una fila para cada
combinación de tomo y palabras. Tiene dos columnas (además de
tomo
y word
que ya las conocemos):
n
es el número de veces que se usa esa palabra en ese
tomototal
es el total de palabras en ese libro.Vemos las palabras que eliminaríamos mediante una lista como stopwords: “de”, “la”, “que”, etc.
En el siguiente gráfico veamos la distribución porcentual (la
calculamos como n / total
) para cada tomo, el número de
veces que aparece una palabra en un tomo dividido por el número total de
términos (palabras) en ese tomo. Hemos cálculado la term
frequency (\(tf\)).
book_words %>%
mutate(tf = n/total) %>%
ggplot(aes(tf, fill = tomo)) +
geom_histogram(show.legend = FALSE) +
xlim(NA, 0.0002) +
facet_wrap(~tomo) +
theme_minimal()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## Warning: Removed 1606 rows containing non-finite values (`stat_bin()`).
## Warning: Removed 3 rows containing missing values (`geom_bar()`).
Hay colas muy largas a la derecha para esos tomos (son muy raras) que no hemos mostrado en estos tramas. En general, los gráficos son similares en los tres tomos. Pocas palabras que ocurren mucho y muchas palabras que ocurrren poco.
bind_tfi_idf()
Repitámoslo una vez más: la idea de la métrica \(tf-idf\) es encontrar las palabras importantes para el contenido de cada documento disminuyendo el peso de las palabras de uso común y aumentando el peso de las palabras que no se usan mucho en una colección o corpus de documentos. El cálculo de \(tf-idf\) intenta encontrar las palabras que son importantes (es decir, comunes) en un texto, pero informativas (no demasiado comunes).
La función bind_tf_idf()
en el paquete
tidytext
toma un dataset de texto tidy como entrada con una
fila por token (término), por documento. Una columna (word
en nuestro caso) contiene los términos / tokens, otra columna contiene
los documentos (tomo
, en este caso) y la última columna
necesaria contiene los recuentos, cuántas veces cada documento contiene
cada término (n
en este ejemplo). Calculamos un total para
cada tomo para nuestras exploraciones en las secciones anteriores, pero
no es necesario para la función bind_tf_idf ()
; la tabla
solo debe contener todas las palabras de cada documento.
book_tf_idf <- book_words %>%
bind_tf_idf(word, tomo, n)
head(book_tf_idf)
## # A tibble: 6 × 7
## tomo word n total tf idf tf_idf
## <chr> <chr> <int> <int> <dbl> <dbl> <dbl>
## 1 II-Los años felices de 8362 151724 0.0551 0 0
## 2 I-Años de formación de 6811 133476 0.0510 0 0
## 3 II-Los años felices la 6606 151724 0.0435 0 0
## 4 I-Años de formación la 5792 133476 0.0434 0 0
## 5 III-Un día en la vida de 5561 101377 0.0549 0 0
## 6 II-Los años felices que 4998 151724 0.0329 0 0
Es importante notar que idf
y, por lo tanto,
tf-idf
son cero para estas palabras extremadamente comunes
(que en el ejercicio anterior, habíamos eliminado como stopwords). Todas
estas son palabras que aparecen en los tres tomos de los diarios, por lo
que el término idf
(que entonces será el logaritmo natural
de 1) es cero.
La frecuencia inversa del documento (y por tanto tf-idf
)
es muy baja (cercana a cero) para las palabras que aparecen en muchos de
los documentos de una colección; así es como este enfoque reduce el peso
de las palabras comunes. La idf
será un número mayor para
las palabras que aparecen en una menor cantidad de documentos de la
colección.
Ordenemos la tabla de forma diferente para identificar las palabras más importantes:
book_tf_idf %>%
select(-total) %>%
arrange(desc(tf_idf))
## # A tibble: 42,541 × 6
## tomo word n tf idf tf_idf
## <chr> <chr> <int> <dbl> <dbl> <dbl>
## 1 III-Un día en la vida carola 41 0.000404 1.10 0.000444
## 2 II-Los años felices schmucler 42 0.000277 1.10 0.000304
## 3 II-Los años felices toto 39 0.000257 1.10 0.000282
## 4 III-Un día en la vida érica 25 0.000247 1.10 0.000271
## 5 III-Un día en la vida pomaire 20 0.000197 1.10 0.000217
## 6 III-Un día en la vida lafuente 19 0.000187 1.10 0.000206
## 7 I-Años de formación rovel 25 0.000187 1.10 0.000206
## 8 II-Los años felices lola 27 0.000178 1.10 0.000196
## 9 III-Un día en la vida tardewski 17 0.000168 1.10 0.000184
## 10 I-Años de formación capitulo 20 0.000150 1.10 0.000165
## # ℹ 42,531 more rows
Aquí vemos que varios nombres y apodos (carola, schucler, toto, etc.) son importantes en los tres tomos, aunque no se repiten a lo largo de los tomos.
Visualicemos estos términos. Vamos a quedarnos (para cada tomo) con los principales 20 términos según tf-idf.
library(forcats)
book_tf_idf %>%
group_by(tomo) %>%
slice_max(tf_idf, n = 20) %>%
ungroup() %>%
ggplot(aes(tf_idf, fct_reorder(word, tf_idf), fill = tomo)) +
geom_col(show.legend = FALSE) +
facet_wrap(~tomo, ncol = 2, scales = "free") +
labs(x = "tf-idf", y = NULL) +
theme_minimal()
Es muy interesante ver como los nombres aparecen pero son diferentes en cada etapa de la vida de Renzi. A su vez, en los años de formación se ve como la vida académica de Renzi (o sea, Piglia) tenía su importancia: términos como “concursos”, “sinóptico” o “monografía” rankean entre los más imporantes.
A medida que avanza la vida de Piglia/Renzi vemos como otros personajes como “Toto Schmucler” toma importancia en su vida y es en el segundo tomo en el que viaja a China y por eso palabras como “china” o “mao” resultan relevantes.
Finalmente, en el período final toman importancia los años “1976” y “1977”, los años de encierro de la dictadura. También su migración a Estados Unidos aparece capturada por el término “princeton”, la universidad en la que enseñó muchos años. Es la época en que termina de escribir y publica la novela [Respiración Artificial](https://es.wikipedia.org/wiki/Respiraci%C3%B3n_artificial_(novela)) en la que “arocena” es un personaje importante (el censor y el espía que lee las cartas).
A partir del corpus de textos de Marx y Engels trabajado la clase pasada, identificar y graficar las 20 principales palabras en las cartas, las notas y los libros.
###
El uso de tf-idf nos permite encontrar palabras que son
características de un documento dentro de una colección de documentos,
ya sea que ese documento sea una novela, un diario un texto físico o una
página web. Explorar la frecuencia de los términos por sí solo puede
darnos una idea de cómo se usa el lenguaje en una colección de lenguaje
natural, y los verbos dplyr como count()
y
rank()
nos brindan herramientas para razonar sobre la
frecuencia de los términos. El paquete tidytext
utiliza una
implementación de tf-idf consistente con los principios de tidy data que
nos permite ver cómo diferentes palabras son importantes en los
documentos dentro de una colección o corpus de documentos.