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
climate_text <- read.csv(file = "data/climate_text.csv")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…
climate_text %>% tibble()## # 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
by_station <- climate_text %>%
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, maxo 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.
tidy_climate <- climate_text %>%
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 :
custom_stop_words <- tribble(~ word, ~lexicon,
"climate", "CUSTOM",
"change", "CUSTOM")
stop_words2 <- stop_words %>%
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)
word_counts <- tidy_climate %>%
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 :