WhatsApp analyzer - WhatStat
Published: 2021-02-08 14:24:33

# Whatsapp analyzer

Pareciera que la crisis del coronavirus esta más cerca de su fin que de su comienzo. Cierto es que transcurrió casi un año desde el primer caso en nuestro país. Durante la cuarentena más estricta, muchas personas se aburrieron. Algunas, aprovecharon para buscar cosas para hacer. Yo era una de las personas aburridas y allá por Junio y me surgieron algunas dudas de cómo había sido mi interacción con la gente mientras estuve guardado. Quién mandó más mensajes? Quién inició más conversaciones? En qué momento del día estoy más activo? Podía hacerlo, ya que la gran mayoría de mis interacciones en ese período, además de las llamadas, fue por WhatsApp. Y sabía que ofrece la posibilidad de exportar los chats en formato TXT, porque ya lo había hecho hacía algunos años. Además, yo ya había usado `pandas` y `matplotlib`, por lo que arranqué por ahí. Después de hacer todo el análisis en `matplotlib` conocí `plotly`, que a diferencia de `matplotlib` es más interactivo. Apenas unos días después conocí **Dash**, un framework que sirve realizar reportes dinámicos en forma de web apps. **Dash** está construido con Plotly.js, React y Flask, pero se programa en Python. Ofrece la posiblidad de publicar gráficos con diferentes rotadores y filtros. Empecé a leer su documentación todos los días después de trabajar. Una vez que más o menos entendí cómo funcionaba **Dash**, comencé a tratar de entender cómo leer los chats.

Para empezar, exporté un chat, con el objetivo de saber cómo era para poder parsearlo. Lo primero que vi fue que tenía la siguiente estructura, separado por saltos de línea para delimitar cada mensaje. Vamos a ver únicamente 4 mensajes, para facilitar la lectura.


>11/30/20, 15:45 - Lucas Monzón created group "Guardia modo "

> 11/30/20, 17:17 - Lucas Monzón changed the subject from "Guardia modo " to "Fiesta MODO🔥"

> 11/30/20, 17:23 - Nano Vázquez: He hecho mi trabajo

> 11/30/20, 20:59 - Nano Vázquez: Estuvo bien el cambio de nombre

Al principio viene la fecha, luego una coma, luego la hora y después quién envió el mensaje, con un guión antes. Después de todo esto, vienen dos puntos para dar lugar al contenido del mensaje. Lo primero que hice fue sacarle esa coma que no sumaba nada. Decidí hacerlo con una regex. Una regex o expresión regular es una sucesión de caracteres que sirve para formar un patrón de búsqueda. Es un tema que da para largo, e inclusive alguien podría dar una lightning o hasta una charla del tema!

```regex
(?<=\/[0-9]{2}),
```

![](https://i.postimg.cc/8cfhmQCx/image.png)


Esta regex va a buscar comas que vengan después de una barra seguida de dos números.

Después de hacer esto, lo siguiente que hice fue obtener la fecha y hora y de los mensajes, también con una regex:

```regex
^\d{2}\/\d{2}\/\d{2} \d{2}:\d{2}
```

![](https://i.postimg.cc/Hn78qwXN/image.png)

Esta regex va a buscar números en el formato `NN/NN/NN NN:NN`. Si bien esta regex parece funcionar, cuando empecé a contarle a distintas personas que había hecho este análisis (originalmente con `matplotlib`, es decir que no era tan fácil de compartir a alguien que no supiera código), algunas me pidieron los gráficos, por lo que me enviaron sus historiales para que les armara los gráficos. Cuando quise correrlo con los nuevos archivos, pasó algo que no había considerado: WhatsApp utiliza la config de cada celular para realizar los exports: la fecha estaba en otro formato, no venía la coma, en vez de barras separadoras había guiones y además de estar en posiciones diferentes, el año tenía cuatro dígitos, por lo que la regex original no servía más. La tuve que modificar por esta otra:

```regex
^\d{1,4}[\/-]\d{1,2}[\/-]\d{1,4} \d{1,2}:\d{1,2}
```
Si bien en la imagen parecieran hacer lo mismo, esta cubrirá más casos. Ahora lo que buscamos son líneas que comiencen con uno a cuatro números, ya que algunos celulares al exportar no incluyen el *trailing zero* de los meses, seguido de una barra o un guión, etc.

#![](https://i.postimg.cc/DyHB46xS/image.png)

Después de esta parte del parseo, ahora podemos atacar directamente el guión que divide horario de quién envió el mensaje. En python, y seguramente en cualquier lenguaje, podemos hacer esto con un `split`, teniendo en cuenta que haga una única división por línea, en el caso del primer guión, porque no queremos generar más elementos en caso de que hubiera un guión en el mensaje . Entonces, hasta ahora, tenemos una tabla con esta pinta:

|  date | msg  |
|---|---|
| 11/30/20 15:45  | Lucas Monzón created group "Guardia modo " |
| 11/30/20 17:17  | Lucas Monzón changed the subject from "Guardia modo " to "Fiesta MODO🔥" |
| 11/30/20 17:23  | Nano Vázquez: He hecho mi trabajo |
| 11/30/20 20:59  | Nano Vázquez: Estuvo bien el cambio de nombre |

De la misma forma que antes, vamos a generar la columna que nos diga el nombre de la persona que envió el mensaje. Nuevamente vamos a tener en cuenta que sean los primeros dos puntos de la línea ya que no queremos tener en cuenta los que se hayan mandado en el mensaje. Pero qué pasa con los primeros dos mensajes, que no tienen un autor, ya que son mensajes generados por WhatsApp? Yo decidí ignorarlos, así que tendríamos una tabla así:

|  date | msg | author  |
|---|---|---|
| 11/30/20 17:23   | He hecho mi trabajo                       | Nano Vázquez |
| 11/30/20 20:59  | Estuvo bien el cambio de nombre | Nano Vázquez |

Con esto podría decirse que la parte del parseo ya está hecha, ahora queda la parte del análisis.

---

Algo que puede resultar muy útil para hacer obtener algún tipo de insight es descomponer la fecha en año, mes, día, etc.

| date                | msg                                                                                          | author                |   year |   month |   day |   hour |   weekofyear |   quarter |   dayofweek | year-week   | year-month   |
|:--------------------|:---------------------------------------------------------------------------------------------|:----------------------|-------:|--------:|------:|-------:|-------------:|----------:|------------:|:------------|:-------------|
| 2020-11-30 17:23:00 | He hecho mi trabajo                                                                          | Nano Vázquez          |   2020 |      11 |    30 |     17 |           49 |         4 |           0 | 20-49       | 20-11        |
| 2020-11-30 20:59:00 | Estuvo bien el cambio de nombre                                                              | Nano Vázquez          |   2020 |      11 |    30 |     20 |           49 |         4 |           0 | 20-49       | 20-11        |

Esta tabla ya pareciera ser una buena base para hacer un análisis. Podemos empezar a contar cuantas apariciones tiene `Nano Vázquez` y todas las personas que aparezcan en la columna `author`, lo que representaría cuantos mensajes envió cada una. De esta misma forma podemos contar cuántas veces aparece `2020` o `2021` en la columna `year`, para saber cuántos mensajes se enviaron en esos años.

Para poder hacer un análisis un poco más complejo vamos a necesitar presentar algunas definiciones; por ejemplo, si yo quiero saber quién inicia conversaciones, tengo que definir qué es iniciar una conversación. A mi se me ocurrió definir, por mis experiencias previas, que una nueva conversación está comenzando si llega un nuevo mensaje después de al menos 6 horas de inactividad, aunque esta definición es completamente debatible. Otro analisis interesante puede ser saber si alguien envía pocos mensajes porque en cada mensaje envía mucho, o simplemente es porque participa poco. Esto lo vamos a poder saber contando cuántas palabras contiene cada mensaje.

Un ejemplo sería ver los mensajes distribuidos por hora del día:

![](https://i.postimg.cc/L66m2HTt/image.png)

O también, verlo por año, por persona:

![](https://i.postimg.cc/W1QP7h46/image.png)

Después de analizar mis propias conversaciones con `pandas`, cuando quise pasar a Dash, lo que tenía en la cabeza era generar mostrar un *upload component* en el que una persona pudiera cargar sus propias conversaciones, y todo el mundo pudiera hacer obtener estos insights sin necesitar tener conocimientos de programación. Volviendo al tema de las fechas y los formatos, me surgió un nuevo problema: para poder parsearlas, tenía que saber en qué formato estaban; sin el correcto, no iba a poder hacerlo. Se me ocurrió agarrar los primeros 10 mensajes, obtener su fecha, y mostrárselos a la persona que quería utilizar la página y que la misma ingresara cuál era el formato correcto entre un listado de formatos, con la siguiente pinta:

| date                |
|:--------------------|
| 2020-11-30 17:23:00 |
| 2020-11-30 17:23:00 |
| 2020-11-30 17:23:00 |
| 2020-11-30 17:25:00 |
| 2020-11-30 18:34:00 |
| 2020-11-30 20:55:00 |
| 2020-11-30 20:59:00 |
| 2020-11-30 20:59:00 |
| 2020-11-30 21:00:00 |
| 2020-11-30 22:15:00 |

En principio esta idea no parecía mala, así que después de implementarla procedí a las pruebas de usabilidad. 
Primero, padre: pudo hacerlo sin problemas. *Disclamer*: es developer.
Luego, madre: ni siquiera entendió la pregunta. 
Con las pruebas concluidas, el resultado fue un fallo rotundo. Lo siguiente que se me ocurrió fue hacer una inferencia del formato de las fechas, a partir de las mismas. Después de buscar durante casi un día sin éxito y justo antes de ponerme a hacer un modelo que pudiera hacer eso, finalmente encontré un repo en Github con una última actualización bastante lejana, pero que hacía lo que yo necesitaba. Lo probé y efectivamente me ahorraba la fricción de que la o el usuario tuviera que introducir el formato, brindando una experiencia mucho más fluída.

Superado el primer escollo, quise publicar la página, por lo que compré un dominio y un amigo me recomendó abrirme una cuenta en Digital Ocean, por un tema de costos.

Después de publicar la página, me topé con un nuevo problema. Originalmente yo había hecho esta página tal como me hubiera gustado encontrarla si fuese un usuario, haciendo foco en la privacidad. A mi, como usuario, no me gustaría subir mis conversaciones a ningún lado y de la pequeña investigación de mercado que había hecho, me di cuenta que cuando yo subía una conversación si hacía un refresh esta quedaba persistida, lo cual no me gustaba nada. Entonces me incliné por utilizar el `sessionStorage` del explorador. Cuando lo corría localmente no había ningún problema, pero cuando corría en el server, se generaban demoras eternas al tener que enviar todo el historial en cada request. Este problema no supe cómo solucionarlo, así que tomé una decisión un poco más laxa en cuanto a privacidad: decidí generar un file en el server con la conversación ya convertida en tabla. Dash, al estar hecho en React, permite agregarle componentes, creándolos en React. Como me había quedado la espina de la privacidad, me cree un componente que escuchara el evento de *unload*, para poder borrar el archivo generado ni bien la persona cerrara la página. Aun así, y muy a mi pesar, este listener funcionaba a veces sí y a veces no. Finalmente, tuve que resignar lo que quería hacer, y a pesar de que le deje el listener para que funcionara cuando fuese posible, le agregué un script croneado, de forma tal que borrara todos los archivos que tuvieran más de 2 horas de antigüedad.

Finalmente, logré publicarlo con un nivel de servicio con el que estuve relativamente conforme. Pueden analizar sus conversaciones desde [la página](https://whatstat.herokuapp.com/). Igualmente, el [repo](https://www.github.com/ignacioreyna/whatsapp-dash) quedó publicado para que cualquiera pueda analizarlas de forma local! Les adelanto algo: la UI no es lo mío.