Texto como dato
Ir al CapÃtulo anterior.
Ir al CapÃtulo siguiente
Cargar librerÃas
library(dplyr)
library(ggplot2)
library(forcats)
library(tidytext)
Leer datos
<- read.csv(file = "data/climate_text.csv") climate_text
Observar los datos
glimpse(climate_text)
## Rows: 593
## Columns: 4
## $ station <chr> "MSNBC", "MSNBC", "CNN", "CNN", "MSNBC", "MSNBC", "CNN", "CN…
## $ show <chr> "Morning Meeting", "Morning Meeting", "CNN Newsroom", "Ameri…
## $ show_date <chr> "2009-09-22 13:00:00", "2009-10-23 13:00:00", "2009-12-03 20…
## $ text <chr> "the interior positively oozes class raves car magazine slic…
%>% tibble() climate_text
## # A tibble: 593 × 4
## station show show_date text
## <chr> <chr> <chr> <chr>
## 1 MSNBC Morning Meeting 2009-09-22 13:00:00 the interio…
## 2 MSNBC Morning Meeting 2009-10-23 13:00:00 corporation…
## 3 CNN CNN Newsroom 2009-12-03 20:00:00 he says he …
## 4 CNN American Morning 2009-12-07 11:00:00 especially …
## 5 MSNBC Morning Meeting 2009-12-08 14:00:00 lots more c…
## 6 MSNBC Countdown With Keith Olbermann 2009-12-10 06:00:00 so they're …
## 7 CNN Sanjay Gupta MD 2009-12-12 12:30:00 let me ask …
## 8 CNN The Situation Room With Wolf Blitzer 2009-12-16 21:00:00 other impor…
## 9 MSNBC Countdown With Keith Olbermann 2009-12-19 01:00:00 let democra…
## 10 MSNBC The Rachel Maddow Show 2010-01-08 04:00:00 you know th…
## # ℹ 583 more rows
Procesado datos
<- climate_text %>%
climate_text as_tibble()
%>%
climate_text filter(station == "CNN") %>%
count(show, sort = T)
## # A tibble: 44 × 2
## show n
## <chr> <int>
## 1 CNN Newsroom 18
## 2 New Day 17
## 3 Anderson Cooper 360 9
## 4 Early Start With John Berman and Christine Romans 9
## 5 Fareed Zakaria GPS 7
## 6 Piers Morgan Tonight 7
## 7 The Situation Room With Wolf Blitzer 7
## 8 Erin Burnett OutFront 6
## 9 CNN Newsroom With Brooke Baldwin 5
## 10 CNN Tonight With Don Lemon 4
## # ℹ 34 more rows
%>%
climate_text filter(station == "CNN") %>%
count(station, show, sort = T) %>%
summarize(total_shows = n())
## # A tibble: 1 × 1
## total_shows
## <int>
## 1 44
<- climate_text %>%
by_station group_by(station)
%>%
by_station group_keys() #-> ver las agrupaciones
## # A tibble: 3 × 1
## station
## <chr>
## 1 CNN
## 2 FOX News
## 3 MSNBC
%>%
by_station select(station, show) %>%
distinct() %>%
summarize(total = n())
## # A tibble: 3 × 2
## station total
## <chr> <int>
## 1 CNN 44
## 2 FOX News 40
## 3 MSNBC 51
%>%
by_station select(station, show) %>%
distinct() %>%
summarize(total = n()) %>%
summarize(station = n(),
mean_shows = mean(total),
max_shows = max(total),
min_shows = min(total))
## # A tibble: 1 × 4
## station mean_shows max_shows min_shows
## <int> <dbl> <int> <int>
## 1 3 45 51 40
%>%
by_station select(station, show) %>%
distinct() %>%
count()
## # A tibble: 3 × 2
## # Groups: station [3]
## station n
## <chr> <int>
## 1 CNN 44
## 2 FOX News 40
## 3 MSNBC 51
%>%
by_station select(station, show) %>%
distinct() %>%
count(sort = T)
## # A tibble: 3 × 2
## # Groups: station [3]
## station n
## <chr> <int>
## 1 MSNBC 51
## 2 CNN 44
## 3 FOX News 40
%>%
by_station select(station, show) %>%
distinct() %>%
count() %>%
arrange(desc(n))
## # A tibble: 3 × 2
## # Groups: station [3]
## station n
## <chr> <int>
## 1 MSNBC 51
## 2 CNN 44
## 3 FOX News 40
%>%
by_station select(station, show) %>%
distinct() %>%
count() %>%
arrange(n)
## # A tibble: 3 × 2
## # Groups: station [3]
## station n
## <chr> <int>
## 1 FOX News 40
## 2 CNN 44
## 3 MSNBC 51
Los resúmenes agrupados son muy útiles para explorar y trabajar con datos. Normalmente, se agrupan según datos categóricos y se resumen los valores numéricos con funciones como mean
, max
o min
. Pero ¿qué pasa si queremos resumir datos categóricos? Esta es una pregunta clave, ya que los datos de texto son categóricos.
En este momento, no podemos contar datos categóricos porque el texto no está estructurado. Necesitamos una forma de darle estructura, idealmente siguiendo los principios de tidyverse, para poder seguir usando las funciones que conocemos del universo tidyverse
.
Tokenización
Después de indicar el data frame de entrada, se especifica el nombre de la nueva columna donde estarán las palabras obtenidas al tokenizar (word
), seguido del nombre de la columna que contiene el texto original. En lugar de tener una reseña por fila, ahora tenemos una palabra por fila.
<- climate_text %>%
tidy_climate select(-show_date) %>%
unnest_tokens(word, text)
tidy_climate
## # A tibble: 41,076 × 3
## station show word
## <chr> <chr> <chr>
## 1 MSNBC Morning Meeting the
## 2 MSNBC Morning Meeting interior
## 3 MSNBC Morning Meeting positively
## 4 MSNBC Morning Meeting oozes
## 5 MSNBC Morning Meeting class
## 6 MSNBC Morning Meeting raves
## 7 MSNBC Morning Meeting car
## 8 MSNBC Morning Meeting magazine
## 9 MSNBC Morning Meeting slick
## 10 MSNBC Morning Meeting and
## # ℹ 41,066 more rows
Como ventaja extra, unnest_tokens()
también limpia el texto: elimina la puntuación, pone todas las palabras en minúsculas y quita los espacios en blanco. Al tener una palabra por fila, el número de filas en el conjunto de datos ha pasado de 593 a 41076.
Ahora que el texto tiene una estructura ordenada (tidy), podemos contar las palabras usando la función count()
. No deberÃa sorprendernos que las palabras más frecuentes sean muy comunes, como the, que no aportan mucho sobre el tratamiento de la información. Necesitamos hacer una limpieza adicional antes de que el conteo de palabras sea realmente útil.
%>%
tidy_climate count(word, sort = TRUE)
## # A tibble: 4,218 × 2
## word n
## <chr> <int>
## 1 the 1913
## 2 climate 1627
## 3 change 1615
## 4 is 1033
## 5 to 1028
## 6 of 845
## 7 a 823
## 8 and 802
## 9 that 652
## 10 in 535
## # ℹ 4,208 more rows
Estas palabras comunes y poco informativas se llaman stop words, y queremos eliminarlas de nuestro data frame ya ordenado. Para eso, podemos usar funciones del paquete dplyr
conocidas como joins, que sirven para unir dos data frames según una o más columnas en común. En este caso, usaremos anti_join()
. Esta función conserva las filas del data frame de la izquierda siempre que el valor en la columna coincidente no aparezca en el data frame de la derecha.
El paquete tidytext
incluye un data frame llamado stop_words. Si usamos el operador pipe para pasar los datos tokenizados de review_data
a anti_join()
, colocando review_data
como el data frame de la izquierda y stop_words como el de la derecha, eliminaremos esas palabras comunes. Como resultado, el número de filas se reduce notablemente (~13%).
# reconstruir el data frame 'limpio'
%>%
tidy_climate anti_join(stop_words) %>%
count(word, sort = T)
## Joining with `by = join_by(word)`
## # A tibble: 3,699 × 2
## word n
## <chr> <int>
## 1 climate 1627
## 2 change 1615
## 3 people 139
## 4 real 125
## 5 president 112
## 6 global 107
## 7 issue 87
## 8 trump 86
## 9 warming 85
## 10 issues 69
## # ℹ 3,689 more rows
Ahora, tenemos nuevo bag of words donde las palabras más frecuentes son climate, y change, que sobre el tema especÃfico del climate change quizás tampoco sean informativas. Podemos actualizar las stop words a nuestros casos concretos de la siguiente forma :
<- tribble(~ word, ~lexicon,
custom_stop_words "climate", "CUSTOM",
"change", "CUSTOM")
<- stop_words %>%
stop_words2 bind_rows(custom_stop_words)
%>%
tidy_climate anti_join(stop_words2) %>%
count(word, sort = T)
## # A tibble: 3,697 × 2
## word n
## <chr> <int>
## 1 people 139
## 2 real 125
## 3 president 112
## 4 global 107
## 5 issue 87
## 6 trump 86
## 7 warming 85
## 8 issues 69
## 9 talk 68
## 10 world 65
## # ℹ 3,687 more rows
# reconstruir el data frame 'limpio'
<- tidy_climate %>%
tidy_climate anti_join(stop_words2) %>%
tibble()
tidy_climate
## # A tibble: 13,354 × 3
## station show word
## <chr> <chr> <chr>
## 1 MSNBC Morning Meeting interior
## 2 MSNBC Morning Meeting positively
## 3 MSNBC Morning Meeting oozes
## 4 MSNBC Morning Meeting class
## 5 MSNBC Morning Meeting raves
## 6 MSNBC Morning Meeting car
## 7 MSNBC Morning Meeting magazine
## 8 MSNBC Morning Meeting slick
## 9 MSNBC Morning Meeting sensuous
## 10 MSNBC Morning Meeting boasts
## # ℹ 13,344 more rows
En ocasiones nos interesará grabar un objeto que hemos generado durante el análisis para recuperarlo más adelante en otra sesión de trabajo.
save(tidy_climate, file = "res/tidy_climate.rda")
Visualización de palabras
%>%
tidy_climate count(word, sort = T) %>%
ggplot(aes(x = word, y = n)) +
geom_col()
El primer problema es que estamos representando demasiadas palabras a la vez. Normalmente estaremos interesados en las palabras más frecuentes. Vamos a mantener aquellas palabras que superan un umbral establecido. Podemos usar filter()
.
Mejorar la figura
%>%
tidy_climate count(word, sort = T) %>%
filter(n > 50) %>%
arrange(desc(n))
## # A tibble: 16 × 2
## word n
## <chr> <int>
## 1 people 139
## 2 real 125
## 3 president 112
## 4 global 107
## 5 issue 87
## 6 trump 86
## 7 warming 85
## 8 issues 69
## 9 talk 68
## 10 world 65
## 11 time 58
## 12 obama 57
## 13 threat 57
## 14 lot 54
## 15 country 52
## 16 science 51
El segundo problema era que las palabras se superponen y resultan difÃciles de leer en el eje x. Podemos usar coord_flip()
.
%>%
tidy_climate count(word, sort = T) %>%
filter(n > 50) %>%
arrange(desc(n)) %>%
ggplot(aes(x = forcats::fct_reorder(word, n ), y = n)) +
geom_col() +
coord_flip() +
labs(title = "Word counts in News Stations",
x = "",
y = "")
Ejercicio.
# Visualización por station
%>%
tidy_climate filter(station == "MSNBC") %>%
count(word, sort = T) %>%
slice(1:10) %>%
ggplot(aes(x = forcats::fct_reorder(word, n ), y = n)) +
geom_col() +
coord_flip() +
labs(title = "Word counts in MSNBC",
x = "",
y = "")
# Idem para 'CNN' y 'FOX
# Alternativa facet_wrap
%>%
tidy_climate count(station, word, name = "total") %>%
group_by(station) %>%
slice_max(total, n = 10) %>%
ungroup() %>%
ggplot(aes(x = word, y = total, fill = station)) +
geom_col(show.legend = FALSE) +
coord_flip() +
facet_wrap(~station) +
labs(title = "Top 10 Word counts by News Station",
x = "",
y = "")
Podemos observar que entre las palabras más frecuentes por cadena, algunas se repiten en las tres, como ‘people’ o ‘global’. Sin embargo, también hay términos que parecen ser caracterÃsticos de cada cadena (al menos, no aparecen entre las 10 más usadas por las otras dos). Por ejemplo, CNN es la única que incluye en su top 10 palabras como ‘reporter’ o ‘happening’; FOX News, por su parte, destaca con ‘threat’ o ‘bob’ (??*); y MSNBC utiliza términos como ‘talk’, ‘debate’ o ‘country’.
Podemos ver las palabras más usadas por cada cadena ordenando dentro de cada grupo con reorder_within
del paquete tidytext
:
%>%
tidy_climate count(station, word, sort = T) %>%
group_by(station) %>%
slice_max(n, n = 10) %>%
arrange(station) %>%
mutate(word = reorder_within(word, n, station)) %>% #ordena dentro de cada grupo
ungroup() %>%
ggplot(aes(x = word, y = n, fill = station)) +
geom_col(show.legend = FALSE) +
facet_wrap(~ station, scales = "free_y") +
coord_flip() +
scale_x_reordered() +
labs(title = "Top 10 Word counts by News Station",
x = "Words")
(*) Bob Beckel fue un destacado analista polÃtico, comentarista, asesor y colaborador mediático estadounidense. Formó parte del panel original del programa The Five en Fox News desde su inicio en 2011.
library(stringr)
%>%
climate_text filter(station == "FOX News", show == "The Five") %>%
filter(str_detect(text, "bob")) %>%
slice(1)
## # A tibble: 1 × 4
## station show show_date text
## <chr> <chr> <chr> <chr>
## 1 FOX News The Five 2011-07-21 21:00:00 greg never mind bob i want to move on a…
Nube de palabras
Un gráfico de barras es probablemente la forma más efectiva de visualizar los recuentos de palabras. Sin embargo, a veces podemos necesitar algo un poco más sugerente.
if("wordcloud" %in% rownames(installed.packages()) == FALSE) {
install.packages("wordcloud") }
library(wordcloud)
<- tidy_climate %>%
word_counts count(word)
set.seed(2025)
wordcloud(words = word_counts$word,
freq = word_counts$n,
max.words = 25,
color = "steelblue")
# size of each word is based on relative word count
# location of each word in the cloud is random
#probar esta syntaxis :
# wc = word_counts %>%
# with(wordcloud(word, n, min.freq = 1, max.words = 125, colors=brewer.pal(8, "Dark2")))
Combinar varias gráficas en 1 figura :