Cuantifica el amor: Analiza tus conversaciones de WhatsApp usando Python - parte 1

Cuantifica el amor: Analiza tus conversaciones de WhatsApp usando Python  - parte 1

La inspiración de esta historia comienza con un reto de mi pareja: “Yo te quiero más”. Tal aseveración merece estar respaldada por hechos y, siendo yo un científico de datos, me propuse parametrizar nuestra relación, de modo que no cupiera duda de quién quiere más a quién.

A continuación, les compartiré una pequeña parte de esta aventura: el análisis de una conversación de WhatsApp. Un proyecto que nos llevará desde exportar un archivo de texto hasta contestar preguntas como: ¿quién responde más rápido?, ¿quién utiliza emojis de amor con más frecuencia?, ¿quien empieza las conversaciones? y, con suerte, conseguir los argumentos necesarios para responder la gran pregunta: ¿quién quiere más a quién?

Para seguir este tutorial necesitarás conocimiento básico de Python y Regex. Si no son expertos/as en Regex, ¡no se preocupen! Repasaremos lo básico en el paso número 3.

Esta serie de blog-tutoriales consistirá de 2 partes:

  1. Importa y estructura los datos con Pandas y Regex.
  2. Análisis exploratorio con Pandas y Seaborn.

En esta primera entrada, aprenderemos a importar conversaciones de WhatsApp desde nuestro smartphone hasta un un objeto DataFrame en Python.

Paso 1- Importar la conversación a un .txt

Comencemos abriendo nuestra aplicación de WhatsApp y seleccionemos nuestra conversación de interés. Luego, hagamos click en el menú de la parte superior derecha y seleccionemos la opción de “más”.


Después, seleccionemos la opción de exportar chat y asegurémosnos de seleccionar la opción de guardar sin media. Finalmente, escojamos el método que queramos para respaldar la información y listo. Yo escogí la opción de guardar el archivo directamente en mi Google Drive.

Una vez terminado este proceso, notaremos que tiene un archivo .txt con toda la conversación adentro. Ese archivo es justo lo que necesitaremos para comenzar a trabajar. ¡Guardemos el archivo en el disco local, abramos el IDE favorito de Python y pongamos manos a la obra!

Paso 2 - Leer el archivo de texto con Python

Comenzaremos cargando las librerías que necesitaremos: Re y Pandas.

import pandas as pd
import re

Durante la ejecución del tutorial usaré las versiones:

2.2.1 de Re

1.4.2 de Pandas

Para quienes no hayan trabajado antes con estas librerías, Re es el motor de Regex built-in de Python. Regex (Regular Expressions) es una colección de caracteres y operadores que nos permiten buscar patrones en texto. Por ejemplo, podemos usar Regex para encontrar patrones genéricos de números, fechas, nombres o similares dentro de un documento. En este proyecto, usaremos Regex para convertir un archivo de texto en datos estructurados. Adicionalmente, nos apoyaremos de Pandas para manipular y realizar análisis básicos sobre nuestros datos estructurados.

Una vez importadas las librerías, usaremos un with statement para leer nuestro archivo de texto y cerrarlo una vez terminado. En esta ocasión, estoy corriendo el código de Python en el mismo directorio donde tengo mi .txt, por lo que no es necesario declarar el path completo hacia el archivo.

with open('chat.txt') as f:
    chat_crudo = f.read()


Utilizando el método .read(), vaciamos todo el contenido en nuestro archivo de texto en la variable chat_crudo. Si revisan el tipo de variable, notarán que es un str (posiblemente muy largo). En mi caso particular, ¡chat_crudo contiene 1,885,419 caracteres! Ustedes también pueden revisarlo usando len(chat_crudo).

Seguido, les recomendaría imprimir aunque sea algunos cuantos caracteres para familiarizarse con la estructura de la conversación.

Ahora que tienen su conversación guardada como una variable dentro de Python, deberíamos analizarla fácilmente, ¿no? La respuesta es: ¡por supuesto! Justo después de parsear o procesar los datos.

Aunque un str por su cuenta ya nos puede dar algo de información importante sobre la conversación como el número de caracteres o el número de veces que alguna palabra aparece en el texto, la realidad es que un str no es el formato adecuado para responder nuestras preguntas. Para analizarlas adecuadamente, debemos pasarlas a un formato estructurado de datos. Por ejemplo, a una tabla donde cada renglón sea un mensaje y cada renglón tenga columnas como la fecha, el emisor y el contenido del mensaje tal cual.

Paso 3 - Parsear la conversación usando Regex

Aquí muestro un snippet de la conversación:


Es evidente que hay patrones en la forma en la que se guardaron los datos. Por ejemplo, puedo ver que cada renglón comienza con la fecha, luego el emisor, luego el mensaje. También puedo ver que la fecha siempre viene en un formato dd/mm/aa y la hora viene en formato de am/pm, además de que todo viene dentro de brackets. Para extraer toda la información, necesitaría generar un patrón como ‘[fecha en dd/mm/aa y hora en am/pm]’ y tomar todos los pedazos del texto que cumplan ese patrón. Precisamente de eso se trata Regex. Solo requerimos saber cómo alimentar ese patrón al motor de Regex.

Para ir tomando un feel por la manera en la que funciona Regex, les recomiendo comenzar por el siguiente cheat sheet. Ténganlo a la mano, será útil más adelante. Además de un portal interactivo para ir poniendo a prueba lo que aprendamos de Regex, yo usaré el siguiente para el resto del tutorial: https://regex101.com/.

Una vez en regex101, es importante asegurarse de que configuramos correctamente el uso del motor de Regex en Python. A pesar de que Regex es agnóstico a los lenguajes, hay ciertas diferencias en las implementaciones específicas en diferentes lenguajes. Dado que trabajaremos con Python, configuremos esta herramienta haciendo click en la siguiente casilla.


Ahora solo es cuestión de pegar un segmento de nuestra conversación en el campo de Test String y probar nuestros patrones.

El primer patrón que trabajaremos es el de la fecha. Como mencionamos anteriormente, nuestras fechas vienen como ‘dd/mm/aa’. Si Regex reconociera las fechas, podría ser tan sencillo como eso. Sin embargo, nos limitamos a caracteres como letras, números y espacios. Si tuviéramos que generalizar el patrón de fecha, entonces diríamos algo como:

bracket número número diagonal número número diagonal número número. Revisando el cheat sheet podemos ver que precisamente hay un operador en Regex para capturar números. Podemos generar ese patrón genérico de la siguiente manera:


Usando “\d” acabamos de hacer match a cualquier dígito. Al usar “\d\d” hicimos match a cualquier dígito seguido de otro dígito. La herramienta de regex101 nos muestra gráficamente cuáles patrones se capturarían.

Ahora, ¿cómo podríamos capturar la hora? Habría que generalizar el patrón en algo como:

número dos puntos número número dos puntos número número espacio p punto m punto. En Regex esto se vería así:

Noten cómo los puntos no se capturan directamente con un “.”, sino que hacemos un escape “\.”. Si revisan el cheat sheet, verán que los “.” son un caracter especial y representa cualquier detalle, excepto una línea nueva. Para capturar específicamente un punto, es necesario pasar un “\” como si dijéramos: captura literalmente un punto.

Ahora usemos grupos para capturar la fecha y la hora con paréntesis de la siguiente manera:

Ahora podemos ver que se capturan dos grupos separados, uno verde y uno naranja. El verde corresponde al primer set de paréntesis y el naranja al segundo set de paréntesis. ¡Ya estamos parseando nuestro documento!

Una manera más elegante de trabajar el patrón \d\d es agregar un cuantificador. Esto puede ser especialmente útil  en caso de que exista la posibilidad de que haya variaciones en el formato. Por ejemplo, en vez del 07 del 10 ver el 7 del 10. Eso rompería nuestro patrón. Para mitigarlo, usaremos el cuantificador exacto declarando que esperamos ver 1 o 2 dígitos de la siguiente manera:


El siguiente elemento es el nombre del emisor. En mi caso, puedo ver Persona1 y Persona 2 y podría tratar de capturar esos nombres explícitamente o intentaría generalizar aún más. Dado que conozco la estructura de mi texto, podría decir algo como: Después de una fecha sigue una hora, después de la hora captura todo lo que esté hasta antes del “:”. Para eso, utilizaremos un cuantificador perezoso, es decir, capturará la menor cantidad de caracteres entre la fecha, hora y los dos puntos.

Esto se ve de la siguiente manera:

Noten cómo esta vez incluí los brackets. Sin embargo, al ser éstos especiales en

Regex, también requieren que se pase antes un “\” para capturar literalmente como brackets.

Antes de capturar el último componente de interés, el mensaje, es importante que nos adelantemos a un posible problema. ¿Qué sucederá cuando en vez de “p.m.” veamos nuestra hora en “a.m.”? En Regex podemos establecer opciones dentro un patrón, tal que, en vez de capturar literalmente “p.m.”, podamos especificar capturar literalmente “p.m.” o “a.m.” usando la notación de brackets y pipe de la siguiente manera:

Solo falta capturar la última parte: el mensaje. Esto quizá será lo más fácil: solo debemos pedir todo lo que siga al emisor hasta el siguiente corte de línea, algo que podemos hacer con punto y un cuantificador.


¡Listo! Ya tenemos un patrón que puede segmentar mi conversación en piezas individuales. Ahora que terminé de probarlo en regex101, es hora de volver a Python para hacer el parseo.

Nota: El patrón que muestro tendría problemas para leer mensajes multilínea. Para propósitos de este tutorial, nos conformaremos con este patrón. Si quisieran capturar mensajes multilínea, les recomiendo revisar lookaheads para capturar todo hasta que se repita el inicio del patrón.

Paso 4 - Convertir la conversación en un DataFrame

Para aplicar nuestro patrón al texto y posteriormente convertirlo a una tabla debemos volver a Python.

patron = re.compile(r'\[(\d{1,2}/\d{1,2}/\d{1,2}) (\d{1,2}:\d{1,2}:\d{1,2} (a.m.|p.m.))\] (.+?): (.*)')
coincidencias = patron.findall(chat_crudo)


Usamos re.compile() para convertir un string de Python en un patrón de Regex. Luego usamos el método .findall() sobre el patrón y pasamos nuestro chat como parámetro. Findall tomará todos los grupos que pusimos en paréntesis y los convertirá a una lista de tuplas donde tendremos cada pedazo de string que cumpla el patrón.

Cuando imprimo coincidencias veo:

Ahora que partimos la conversación, podemos pasarla a un formato tabular como DataFrame usando:

chat_procesado = pd.DataFrame(coincidencias, columns = ['Fecha', 'Hora', 'ampm', 'Emisor', 'Mensaje'])


Al imprimir chat_procesado vemos:


¡Excelente! Ahora que estructuramos nuestra conversación, hacer los análisis será mucho más sencillo y nos dará la flexibilidad que necesitamos para contestar preguntas.

En mi siguiente artículo, veremos herramientas gráficas, métricas y algunas ideas de análisis para responder la gran pregunta: ¿quién quiere más a quién?

⚠️
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Revelo.

Revelo Content Network da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.