Capítulo 9 Procesamiento de cadenas y minería de texto

9.1 Funciones básicas

Ya hemos aprendido a importar datos y a consolidarlos. Sin embargo, aun no podemos trabajar con esta data. Tenemos que validar mediante el procesamiento de cadenas y asegurar una calidad mínima para poder realizar nuestros análisis.

Por ejemplo, en el capítulo anterior importamos datos de Wikipedia, sin embargo no nos centramos en si ya podíamos realizar operaciones o visualizaciones con nuestra data.

Tal vez no nos hayamos dado cuenta, pero podemos observar columnas con espacios o comas donde deberían de haber números. Lo podemos validar no solo analizando la clase de la columna, sino también si intentamos calcular el promedio de esa variable.

No podemos hacer tampoco una conversión directa a número porque los espacios en blanco y las comas son caracteres.

Son tan frecuentes y tantas las casuísticas posibles que ya existen múltiples funciones para procesar cadenas incluidas en la librería tidyverse. Así mismo, hay más de una forma de cómo procesar cadenas. Siempre dependerá de cómo se encuentra la data en bruto.

9.1.1 Reemplazando caracteres

Una de las función básicas que más usaremos será el reemplazar caracteres. Aplicamos esta función cuando estamos seguros que ese cambio no va a comprometer al resto de datos. Tenemos espacios y tenemos comas. Entonces podríamos empezar por reemplazar alguno de los dos para normalizarlos utilizando la función str_replace_all(cadena, patron, reemplazo). En el atributo patrón usaremos \\s, que viene de space (espacio en inglés). Vamos a aprender primero a modificar los datos almacenados en un vector y luego replicaremos a toda nuestra tabla.

Hemos llevado a propósito todos los valores a estar separados por comas porque ahora podremos usar fácilmente la función parse_number(vector) que no solo reemplaza las comas por vacío, sino que remueve todo valor no numérico antes del primer número, lo cual nos facilita si tuviésemos valores monetarios, y además nos convierte el valor de tipo caracter a tipo numérico.

Este vector ahora sí nos permite hacer operaciones matemáticas o visualización de la distribución.

Ya sabemos qué funciones utilizar para transformar los campos de nuestro caso. Sin embargo, los hemos aplicado a vectores. Para mutar las columnas de nuestra tabla en bruto usaremos la función mutate_at(columnas, ~funcion) usando el operador pipeline %>%. Apliquemos el primer cambio de los espacios por comas y no solo a la columna 3, población, sino también a la columna 5, cambio medio.

Hemos quitado de la función str_replace_all el atributo cadena y lo hemos reeplazado por un punto .. Y es que ese punto . nos indica que evaluará para cada columna c(3,5) de nuestra tabla.

Ahora, apliquemos la función parse_number que aplicamos anteriormente.

9.2 Expresiones regulares

Una expresión regular10 (o regex como se le conoce en inglés) es un patrón que describe un conjunto de cadenas. Ya hemos utilizado regex en la sección anterior utilizando solo el patrón \\s. Sin embargo, normalmente tendremos muchas más casuísticas que requerirán un patrón que pueda convertir un rango más amplio de casos.

Si bien podríamos analizar todas las casuísticas posibles disponibles en la documentación, aprendemos más rápido por casuísticas. Analicemos un caso que nos permitirá aprender poco a poco algunos patrones.

En la librería dslabs encontramos y utilizamos anteriormente los datos del data frame estaturas, heigths, de alumnos de una universidad expresados en pulgadas.

Estos datos estaban listos para ser analizados. Sin embargo, no fue así como provino de la fuente. Los estudiantes tuvieron que llenar una encuesta y aún cuando se les pidió su estatura en pulgadas, ellos completaron su estatura en pulgadas, pies, centímetros, escribiendo números, letras, etc. Podemos ver la data inicial del formulario en el data frame reported_heights.

Si bien podríamos pensar que ingresaron bien los datos, no tenemos que confiarnos y siempre es mejor validar la calidad de nuestra data. Hay múltiples maneras de validar, como podemos ver a continuación:

estaturas <- reported_heights$height

# Validación opción 1: Muestra aleatoria
sample(estaturas, 100)
#>   [1] "69"              "67"              "5' 11\""         "71"             
#>   [5] "6"               "69"              "68"              "63"             
#>   [9] "72"              "6"               "73"              "6"              
#>  [13] "74"              "69"              "69"              "74"             
#>  [17] "71.5"            "6'1\""           "69"              "184"            
#>  [21] "65"              "69"              "69"              "68.5"           
#>  [25] "170 cm"          "68.5"            "72"              "74"             
#>  [29] "75"              "74"              "64"              "157"            
#>  [33] "71"              "67.72"           "5.57"            "70"             
#>  [37] "70"              "63"              "67"              "74"             
#>  [41] "5 feet 6 inches" "68"              "5'12"            "61.32"          
#>  [45] "73.2"            "70"              "71"              "67"             
#>  [49] "72"              "6.2"             "72.83"           "60"             
#>  [53] "68"              "70"              "73"              "5'6"            
#>  [57] "64"              "5'5\""           "68.11"           "72"             
#>  [61] "75.98"           "70"              "60"              "68.5"           
#>  [65] "71"              "61"              "5' 4\""          "69"             
#>  [69] "72.83"           "72"              "66"              "68.89"          
#>  [73] "68.5"            "210"             "69"              "yyy"            
#>  [77] "86"              "70"              "68.5"            "65"             
#>  [81] "68.89"           "5.4"             "180"             "70.866"         
#>  [85] "180"             "72"              "62"              "62"             
#>  [89] "69.29"           "5.7"             "66"              "167"            
#>  [93] "63"              "70"              "177"             "65"             
#>  [97] "76"              "67"              "68"              "5.51"

# Validación opción 2: convertir a números y contar si hay NAs
x <- as.numeric(estaturas)
#> Warning: NAs introduced by coercion
sum(is.na(x))
#> [1] 81

# Validación opción 3: agregar columna de los que no pueden convertirse en número:
reported_heights %>% 
  mutate(estatuta_numero = as.numeric(height)) %>% 
  filter(is.na(estatuta_numero)) %>% 
  head(10)
#> Warning: NAs introduced by coercion
#>             time_stamp    sex                 height estatuta_numero
#> 1  2014-09-02 15:16:28   Male                  5' 4"              NA
#> 2  2014-09-02 15:16:37 Female                  165cm              NA
#> 3  2014-09-02 15:16:52   Male                    5'7              NA
#> 4  2014-09-02 15:16:56   Male                  >9000              NA
#> 5  2014-09-02 15:16:56   Male                   5'7"              NA
#> 6  2014-09-02 15:17:09 Female                   5'3"              NA
#> 7  2014-09-02 15:18:00   Male 5 feet and 8.11 inches              NA
#> 8  2014-09-02 15:19:48   Male                   5'11              NA
#> 9  2014-09-04 00:46:45   Male                  5'9''              NA
#> 10 2014-09-04 10:29:44   Male                 5'10''              NA

Podríamos querer optar por eliminar estos 81 datos NAs al no ser significativos respecto al total de 1,095 datos. Sin embargo, hay varios de estos datos que siguen un patrón determinado y en vez de ser descartados podrían ser convertidos a la escala que tenemos en el resto de la data. Por ejemplo, hay personas que ingresaron su estatura como 5’7", que, para quienes recuerdan la conversión, se puede convertir porque 1 pie son 12 pulgadas. Entonces \(5*12+7=67\). Y así, como ese caso, podemos detectar patrones, pero tenemos, nuevamente, que ser cuidadosos en detectar el patrón exacto y no uno muy genérico que pueda cambiar otras casuísticas. Si todos siguiesen el mismo patrón \(x'y''\) o \(x'y\) sería mucho más fácil convertirlo a pulgadas al calcular \(x*12+y\).

Empecemos extrayendo nuestra columna a un solo vector de caracteres con todos los valores que no convierten en automático a número o fueron ingresados en pulgadas. Esto lo detectamos si miden más de 5 y hasta 7 pies (de 1.5m a 2.1 metros). Luego de ello iremos creando poco a poco las transformaciones.

Agregando la condición de haber ingresado en pies tenemos 168 errores. No podemos ignorar 15.3% de errores.

Usaremos la función str_detect(cadena, patron) que nos permitirá detectar si una cadena cumple un determinado patrón. El resultado será un valor lógico: TRUE or FALSE que podemos usar como índice para obtener los valores que cumplen en nuestro vector.

9.2.1 Alternación

| es el operador de alternación que elegirá entre uno o más valores posibles. En nuestro caso, hemos indicado que detecte si existe la palabra “feet” (pie en inglés), pero también tenemos “ft” y “foot” para referirse a lo mismo en nuestros datos. Así, podemos crear el patrón “feet” o “ft” o “foot”.

De la misma forma podemos encontrar las variaciones para pulgadas y otros símbolos que podemos remover:

En este caso hemos ingresado '' para detectar a los que ingresaron ese símbolo para denotar pulgadas y \" por si usaron doble comillas. En este último caso hemos utilizado \ para que no nos genere un error al interpretar como cierre de la cadena.

Ya podríamos comenzar reemplazando a partir de los patrones detectados:

Como esfuerzo adicional, podríamos también buscar solucionar que algunas personas han escrito palabras en vez de números. Para ello creamos una función que reemplace cada palabra por un número y aplicamos al vector:

9.2.2 Anclaje

Ahora que está más estandarizado podemos comenzar con regex con características más genéricas. Por ejemplo, hay una persona que ha ingresado 6'. Sería conveniente tener todo de la forma pies más pulgadas. Con lo que deberíamos de tener 6'0. Para lograr ello tenemos que crear un regex acorde a esta situación genérica. Usaremos el símbolo ^ para anclar nuestra validación a que “comience con” y el símbolo $ para hacer coincidir con el fin de la cadena. Antes de reemplazar, primero veamos quiénes cumplen.

Este regex nos indica que comience con 6' y que ahí termine la expresión. Aun podríamos hacerla más genérica para abordar a quienes, en un futuro, escriban 5 pulgadas (1.52m) o 6 pulgadas (1.82m). Para ello usaremos corchete y dentro de ellos pondremos todos los valores que aceptaremos.

Sigue habiendo un solo resultado, pero nuestro regex es más genérico ahora y ya lo podemos usar para reemplazar. Antes de reemplazar en nuestro vector vamos a hacer una prueba para aprender cómo crear lo que necesitamos a partir de un patrón.

Hemos colocado entre paréntesis para indicar que lo que está dentro es nuestro primer valor y usamos \\1 para hacer referencia a ese primer valor. Entones estamos indicando que escriba el primer valor, luego una comilla ', y luego un cero 0.

Ahora ya estamos listos para aplicar a todo nuestro vector. Vamos a hacer el cambio para considerar no solo 5 y 6, sino hasta el valor de 7 pulgadas (2.1m). Así mismo, vamos a tomar los casos en que solo haya un número sin el símbolo de pies '.

9.2.3 Repeticiones

Podemos controlar cuántas veces un patrón coincide utilizando los operadores de repetición:

TABLA 8.1: Operadores de repetición:
Operador Número de veces
? 0 o 1 vez
+ 1 o más veces
* 0 o más veces

Por ejemplo, para encontrar todos los casos donde en vez de usar el símbolo de pies ' ingresaron una coma, un punto, o un espacio usaremos el siguiente patrón:

Leamos el patrón:

  1. La cadena empieza con un dígito que va del 4 al 7.
  2. \\s quiere decir que va seguido de un espacio en blanco, pero usamos * para indicar que ese caracter aparezca 0 o más veces.
  3. Luego de ese espacio buscaremos por cualquiera de los siguientes caracteres: ,, un punto \\. (al cual ponemos doble barra invertida porque el punto solo en un patrón significa “cualquier valor”).
  4. Volvemos a usar \\s* para buscar cero o más espacios en blanco.
  5. Finalmente indicamos que la cadena culmina ahí con un dígito, para denotar que busque cualquier dígito usamos \\d, d de dígito. Y agregamos asterísco para que nos mantenga uno o más dígitos que encuentre.

En resumen: empieza con un número, luego símbolos y luego un dígito. Entre los símbolos podrían haber espacios en blanco. Ese es nuestro patrón.

Ya encontramos los valores que cumplen con el patrón, así que ya estamos listos para reemplazar.

Otro patrón que vemos ahora es cuando antes o después del símbolo de pies ' hay un espacio en blanco. Hagamos el cambio con lo aprendido e incluyamos los casos donde hay decimales:

Así mismo, tenemos el patrón en que ingresaron: pies + espacio + pulgadas sin ningún símbolo. Hagamos el cambio con lo aprendido.

Estamos listos para poner todos los patrones juntos y lo pontente de los patrones es que nos pueden servir para futuros ejercicios. Así, crearemos una función donde colocaremos cada cambio que podemos hacer a un string.

Así, hemos creado dos funciones que nos podrían ser útiles si fuésemos a volver a trabajar con encuestas del mismo tipo.

Antes de aplicarlo a toda nuestra tabla volvamos a extraer los valores a un vector para aplicar las funciones creadas.

Ahora apliquemos las funciones creadas:

Hemos logrado reducir de 168 errores de 1095 registros, 15.3% de errores, a 12 errores de 1095, 1% de errores. Ya podemos aplicar a nuestra tabla inicial.

Aun tenemos que hacer unas conversiones. Sin embargo, como ya siguen un patrón determinado podemos utilizar la función extract(columna_origen, nuevas_columnas, patron, remover_origen) para poder crear nuevas columnas por cada valor de nuestro patrón.

Ahora que ya tenemos los datos que cumplen con el patrón en otras dos columnas, y sabemos que son números, podemos convertir todo a número.

Ahora que nuestras columnas ya son numéricas podemos hacer operaciones para calcular la estatura.

Finalmente, haremos una validación de si la estatura está en un intervalo y/o si estuvo expresado en centímetros o metros.

Ya tenemos nuestra muestra validada, solo tendríamos que tomar las columnas que necesitamos y comenzar a utilizar el objeto para los análisis que necesitemos.

9.3 De cadenas a fechas

Regularmente cuando importamos datos, no solo vamos a querer transformar los datos numéricos. También vamos a tener múltiples casos donde necesitamos transformar nuestra cadena a una fecha en algún formato en particular. Para ello, utilizaremos la librería lubridate, incluida en tidyverse, la cual nos provee con diversas funciones para hacer el tratamiento de fechas más accesible.

Cuando la cadena de texto se encuentra en el formato de fecha ISO 8601 (AAAA-MM-DD), podemos utilizar directamente la función month(), day(), year().

Sin embargo, no siempre tenemos la fecha en ese formato y lubridate() de otras funciones que son más flexibles al momento de coercionar datos. Miremos este ejemplo:

El primer dato ingresado fue un número, pero ya sabemos que lo coerciona a texto. Luego, tenemos diferentes valores ingresados, pero todos siguen un mismo patrón. Primero está el año, luego el mes y luego el día. Cuando sabemos que primero está el año, luego mes y luego día usaremos la función ymd() para convertir todas las fechas a formato ISO 8601.

De la misma forma, tendremos las siguientes funciones que podemos utilizar dependiendo de la forma en que tengamos la fecha de nuestra fuente. En todos los casos nos va a convenir convertir a formato ISO 8601. Por ejemplo aquí podemos ver cuándo reconoce correctamente el formato y cuánto el formateo falla.

Finalmente, de la misma forma en que podemos utilizar estas funciones de días, meses y años, también podemos usar par referirnos a horas, minutos y segundos.

9.4 Ejercicios

Antes de resolver el siguiente ejercicio ejecuta este Script:

  1. Del objeto ventas convierte las columnas de ventas y ganancias a valores numéricos.

Solución

  1. Dado el vector universidades:

Limpia la data para obtener el nombre completo como figura a continuación:

#> [1] "Universidad Católica de Chile"          
#> [2] "Universidad Nacional Autónoma de México"
#> [3] "Universidad Nacional de Ingeniería"     
#> [4] "Universidad de los Andes"               
#> [5] "Universidad de Barcelona"               
#> [6] "California State University"

Solución

Para los siguientes ejercicios, vamos a trabajar sobre los datos de las encuestas realizadas previas al Brexit en Reino Unio. Ejecuta primero el Script:

  1. Actualiza el objeto encuestas con los siguientes nombres c("fecha", "permanecer", "salir", "no_decide", "spread", "muestra", "encuestadora", "tipo", "notas"). No todas las encuestas tienen en la columna permanecer un valor porcentual. Filtra las columnas para que se muestren solo los valores que contengan el símbolo de %.

Solución

  1. Almacena los valores de la columna permanecer al vector permanecer y convierte los valores al valor numérico del porcentaje. Es decir, valores de 0 a 1 (0.5 en vez de 50%).

Solución

  1. Encontramos en la columna no_decide el valor de “N/A” cuando por porcentajes de permanecer más salir suman 100%. Por ende, no_decide debería de ser cero en esos casos y no “N/A”. Almacena los valores de no_decide en el vector no_decide y transforma los valores “N/A” a 0%

Solución

  1. Crea la función formato_porcentaje(cadena) donde consolides las transformaciones realizadas en los ejercicios anteriores. Luego prueba la función con el vector: c("13.5%", "N/A", "10%")

Solución

  1. Modifica las columnas de la tabla encuestas para cambiar los valores necesarios de texto a números.

Solución

  1. Importa el siguiente archivo que contiene los casos de covid reportados por el Ministerio de Salud de Perú de la siguiente ruta: “https://www.datosabiertos.gob.pe/sites/default/files/DATOSABIERTOS_SISCOVID.csv” en el objeto covidPeru. Convierte a tipo fecha la columna fecha de nacimiento y crea un histograma con las edades de los infectados.

Solución

9.5 Minería de texto: Mapa de palabras

La minería de texto es el descubrimiento por computadora de información nueva, previamente desconocida, mediante la extracción automática de información de diferentes recursos escritos. Los recursos escritos pueden ser sitios web, libros, chats, comentarios, correos electrónicos, reseñas, artículos, etc. Así, la minería de texto, también conocida como minería de datos de texto, aproximadamente equivalente al análisis de texto, es el proceso de derivar información de alta calidad del texto.

La primera técnica de minería de texto que aprenderemos será la construcción de mapas de palabras. Para ello, necesitaremos instalar paquetes desarrollados exclusivamente para la minería de texto (text mining en inglés) y algunas librerías para el tratamiento de texto que ya habíamos utilizado como readr o stringr:

9.5.1 Importando los datos

Los mapas de palabras o nubes de palabras, nos permiten identificar rápidamente cuáles son las palabras que más se repiten en un texto. Son muy útiles cuando tenemos campos que llegan de un formulario, por ejemplo, llenado por los clientes y queremos saber de qué se está hablando más. También nos ayuda para analizar contenido en algún libro, revista, etc.

Vamos a analizar la obra “Niebla” escrita por el autor Miguel de Unamuno. El texto lo obtendremos de la web del Proyecto Gutenberg11, el cual nos da acceso a una librería de alrededor de 60 mil libros gratuitos. Importaremos el texto usando la función get_text_as_string() de la librería syuzhet para importar todo el texto como una cadena. Esta función es muy útil si deseamos importar archivos grandes. Luego, usaremos la función get_sentences(), para crearnos un vector de oraciones a partir del texto inicial.

9.5.2 Limpieza del texto

Como ya hemos aprendido anteriormente, no tenemos que ir directo a analizar. Sino, tenemos que limpiar nuestra data. Lo primero que haremos es eliminar las primeras filas que no corresponden a la obra.

A continuación utilizaremos un regex para detectar caracteres especiales de la codificación, como saltos de línea y tabulaciones. Para ello usaremos el regex [[:cntrl:]]. Así mismo, convertiremos todas las palabras a minúsculas para facilitar las comparaciones entre palabras. Finalmente, como queremos analizar las palabras, eliminamos todos los signos de puntuación.

Por otro lado, la librería “tm”, de text mining en inglés, nos provee funciones y vectores para poder limpiar nuestros datos. Ya usamos la función removePunctuation(). Sin embargo, también tenemos la función stopwords("spanish") nos llama a un vector con palabras vacías, es decir, aquellas con poco valor para el análisis, tales como algunas preposiciones y muletillas. Además, usaremos la función removeWors() para remover todas las palabras que se encuentre en nuestro vector de palabras vacías

Finalmente, eliminamos los vacíos excesivos, algunos de ellos creados por las transformaciones anteriores.

9.5.3 Creación del Corpus

Para poder crear un mapa de palabras necesitamos aplicar la función VectorSource() para convertir cada fila a una documento y la función Corpus() que nos permitirá crear estos documentos como una colección de datos.

Ya estamos listos para crear nuestro mapa de palabras. Para ello usaremos la librería wordcloud() y la función del mismo nombre.

9.5.4 2da Limpieza de datos

En minería de texto frecuentemente vamos a obtener un resultado que aun requiere de limpiar más datos. Por ejemplo, vemos aun palabras como pronombres de poco interés para el análisis. Volveremos a usar la función removeWords(), pero esta vez con un vector personalizado de las palabras que deseamos retirar.

Augusto y Eugenia, como podemos asumir, son los protagonistas de Niebla y gran parte de la acción en este libro ocurre en la “casa” de uno u otro protagonista, discutiendo las relaciones entre “hombre” y “mujer”.

9.5.5 Frecuencia de palabras

Ya tenemos una idea visual de las palabras más utilizadas. Sin embargo, también podríamos saber exactamente cuántas veces apareció una determinada palabra. Para ello tenemos que convertir nuestra colección a una matrix. Para ello usamos las funciones juntas TermDocumentMatrix(), as.matrix() y rowSums() que nos dejarán con un vector con la frecuencia de palabras.

Con este vector ya es fácil convertirlo a data frame, dado que tenemos los nombres y los valores, y visualizarlo.

9.6 Minería de texto: Análisis de sentimientos

Cuando analizamos textos no solo vamos a querer saber cuáles son las palabras que más se utilizan en textos, sean estos comentarios dejados por nuestros clientes, solicitudes de reclamo, etc. También es muy útil el saber el tono de los mensajes. Esta técnica es conocida como análisis de sentimientos, la cual se puede hacer muy fácilmente con la librería que ya hemos usado syuzhet.

Y qué mejor lugar para analizar tonos de mensajes que en Twitter. Para ello, vamos a descargar un historial de Tweets de algún personaje Hispanohablante de la página vicinitas.io. Esta página nos permite descargar un excel dada una cuenta pública:

https://www.vicinitas.io/free-tools/download-user-tweets.

Para nuestro ejemplo, utilizaremos tweets de la cuenta de la abogada Rosa María Palacios12. Para este ejemplo ya se ha cargado el excel a Github. Descargaremos ese excel directamente desde ahí a nuestra computadora a un archivo temporal y luego lo leeremos usando read_excel().

Hemos creado nuestro objeto publicaciones, el cual tiene en la columna Text los diferentes tweets, retweets y replies, realizados. Si bien podríamos hacer un análisis de datos utilizando las otras columnas, nos vamos a centrar en el contenido y tono de los Tweets. Para ello, vamos a eliminar los Retweets y las repuestas, quedándonos solo con los Tweets.

Con lo aprendido haciendo mapas de palabras, creemos un mapa con el contenido de las publicaciones.

Ella es una abogada, con lo que hace mucho sentido que postee contenido de lo que se puede o no se puede. Podríamos ser más rigurosos y buscar conseguir esta combinación agregando guiónes bajo si se detecta el patrón, pero por el momento nos vamos a enfocar en el tono.

Con nuestro objeto tuits_limpio podemos obtener cuál es el tono utilizando la función get_nrc_sentiment(), la cual nos da un score por cada fila del vector de acuerdo al Léxico de Emociones NRC13. El Léxico de Emociones NRC es una lista de palabras y sus asociaciones con ocho emociones básicas (ira, miedo, anticipación, confianza, asombro, tristeza, alegría y aversión) y dos sentimientos (negativo y positivo).

Podemos realizar algunas transformaciones a este data frame, pero antes vamos a crear una función de traducción. Dado que, aun tenemos que traducir las cabeceras.

Ahora sí, con nuestra función lista, podemos transformar nuestro objeto resultado para obtener las frecuencias de cada emocion y sentimiento.

Vemos que tenemos las 8 emociones más los 2 sentimientos. Obtengamos los índices de los sentimientos positivos y negativos:

Este vector nos servirá para poder visualizar de forma separada las emociones y los sentimientos.

Esta técnica nos es muy útil como punto de partida de futuros análisis sobre la tonalidad de algún determinado texto.

9.7 Ejercicios

Para estos ejercicios utilizaremos más libros del proyecto Project Gutenberg. Sin embargo, extraeremos el texto con una librería en R, la librería gutenbergr.

  1. La función gutenberg_download(id) nos descarga el texto en un objeto de tipo tibble con una fila por cada línea. Descarga el libro “El ingenioso hidalgo don Quijote de la Mancha” al objeto descarga. Almacena la columna text en el objeto obra y reporta las primeras 50 líneas del objeto.

Solución

  1. Por la cantidad de líneas de esta obra, tomemos una muestra aleatoria de 1,000 líneas y almacénalo en el objeto muestra. Remueve las palabras, los signos de puntuación, los saltos de línea y demás elementos aprendidos durante este capítulo. Almacena esta transformación en el objeto muestra_limpia.

Solución

  1. Crea un mapa de palabras a partir del objeto muestra_limpia.

Solución

  1. Realiza un análisis de emociones de la muestra obtenida.

Solución