Itzulpena: Data Visualization Society-aren SOTI 2023 Challenge-erako proposamena

Propuesta para la SOTI 2023 Challenge de la Data Visualization Society

Herramientas: ggplot2, ggforce, inkscape


ACTUALIZACIÓN (2024-05-30)

Hoy me han comunicado que mi propuesta ha recibido dos menciones, segundo lugar en la categoría “Exploratory” y mención especial “Beauty Award”, así que he aprovechado para actualizar el post original de 2024-03-24 para explicar mejor el proceso que seguí en su día.

The 2023 State of the Industry Survey Challenge winners have arrived!!

Esta es la propuesta que he presentado a challenge de la DVS para visualizar datos de la encuesta sobre el estado de la industria de la visualización de datos.

Challenges for data visualization practice

En febrero se publicó una nueva versión de ggplot2 (la 3.5.0) que incluye nuevas funcionalidades con las que me apetecía trastear, principalmente coord_radial(). Así que comencé a trabajar sin ninguna idea concreta en mente, más allá de probar estas nuevas funcionalidades (que, como se verá, finalmente no he utilizado).

Partiendo de una idea vaga de crear alguna forma parecida a una palmera empecé a salsear con coord_radial() pero los resultados no iban por donde yo quería. Quería crear las hojas de la palmera usando curvas sobre un espacio semicircular, pero ahora mismo coord_radial() y geom_curve() no se entienden, así que pasé a trastear con ggforce.

Warning: geom_curve() is not implemented for non-linear coordinates

coord_radial() y geom_curve() no se llevan bien.

geom_curve() no ofrece demasiado control sobre las curvas que genera, por lo que mi siguiente intento fue con ggforce::geom_bezier(), que permite crear curvas de bezier cúbicas (con un punto de control) o cuadráticas (dos puntos de control). Después de varias pruebas al final he acabado usando ggforce::geom_bspline2.

geom_bspline2() permite crear curvas de todo tipo a partir de una serie de puntos e intercalar aesthetics, por ejemplo para crear degradados de color a lo largo de una línea.

Inicialmente tenía la idea de comparar datos de encuestas de varios años, por lo que pensé en trabajar con ramas enfrentadas (cada rama representaría un tipo de respuesta, y cada racimo los datos de años distintos).

Palmera de bsplines.

Los extremos de las ramas mostrarían un punto con la información sobre el total de respuestas. Ahí era donde quería probar la posibilidad de incluir degradados como relleno en ggplot2 (una de las nuevas funcionalidades de la versión 3.5.0 de ggplot), pero no funciona correctamente con geom_point (o yo no conseguí hacerlo funcionar como deseaba), así que hice algunas pruebas con geom_tile aplicando degradados radiales cuyo color exterior es el mismo que el color de fondo del gráfico. De esta forma, se crea un trampantojo visual gracias al cual parece haber puntos, y no rectángulos.

pruebas con geom_tile aplicando degradados radiales en fill (nueva funcionalidad en ggplot 3.5.0)

Sin embargo, esta aproximación tiene un problema importante, y es que si los puntos están demasiado cerca los rectángulos acaban solapándose unos con otros.

Después de un par de intentos más decidí cambiar el enfoque y trabajar solo con los datos de la última encuesta. Para entonces ya tenía más o menos claro que quería simular algún tipo de elemento vegetal.

Con esa metáfora visual en mente, diseñé una primera versión en la que hay cuatro ramas por encima de una línea, y dos por debajo.

Primer boceto de la idea final

Las ramas superiores representan distintas opciones de respuesta, mientras que las dos inferiores muestran aquellas encuestas que no han respondido a esta pregunta.

Ramificación inicial
En esta fase de bocetado planteé la posibilidad de hacer que el grosor de las ramas fuera proporcional al número de respuestas, aunque tras un par de pruebas deseché la idea porque los resultados no eran los esperados.

Lo que no me convencía de esta propuesta es que la opción No impact aparecía vinculada a las tres opciones que implican algún tipo de impacto, y quería reagruparlas de tal forma que se apreciara mejor la proporción entre respuestas que sí consideran que un determinado challenge les afecta en su trabajo, y las que no.

La versión final tiene dos ramas principales, una que recoge las subramas con respuestas que implican algún tipo de impacto en el trabajo diario, y otra que recoge las que impllican que no tiene impacto (bien porque lo indican expresamente o bien porque no seleccionan ninguna opción para este challenge en concreto).

Boceto en el que se recogen los puntos de paso y de control necesarios para jugar con ggforce::geom_bspline2(). Los datos para poder dominar las ramas están recogidos en su propio conjunto de datos, calculado de forma manual.

También añadí una especie de raíz que representa el número de personas que no han contestado a esta pregunta.

Ramificación definitiva

La versión final incluye un punto por cada respuesta recogida en cada challenge y opción de respuesta. Los puntos se organizan en una espiral que simula una flor. A más respuestas, mayor es la espiral.

Espirales

Para la composición final (realizada con Inkscape) he destacado el challenge principal y le he añadido detalles con la ayuda de varias extensiones de ggplot2 en una de las facetas para que funcione como leyenda general de toda la composición: ggforce (geom_mark_hull para crear las zonas sombreadas), geomtextpath (geom_textpath para trazar texto en curvas), ggtext (geom_richtext para poder controlar más opciones gráficas relacionadas con el texto). También he incluido un par de gráficos de barras apiladas para complementar el análisis con un enfoque más tradicional.

Al añadir elementos que aportan contexto el gráfico destacado funciona a su vez como leyenda que ayuda al usuario a saber cómo tiene que decodificar el gráfico.

Detalles técnicos

La mayor parte del trabajo corre a cargo de paquetes del tydiverse para la preparación de los datos y de ggplot2 y varios paquetes complementarios para crear los gráficos. Puedes consultar el código en mi cuenta de Github.

Preparación de datos

El conjunto de datos original recoge todas las respuestas de cada encuesta en una fila (algo más de 900 encuestas, con 63 variables). Partiendo de esos datos, he creado varios conjuntos de datos para poder dibujar los distintos elementos de los gráficos:

  • Ramas: una para cada tipo de respuesta.
  • Flores: un punto por cada respuesta, organizados en una espiral.

Ramas

Las ramas están creadas con ggforce::geom_bspline2(). Simplificando mucho, las b-splines dibujan curvas a partir de unos puntos de control. Para dibujar las ramas, tuve que crear un conjunto de datos específico con las coordenadas de varios puntos, unos de paso (inicio y final de cada subrama) y otros de control. A partir de ahí, ggforce::geom_bspline2() se encarga de interpolar los puntos y atributos gráficos (color, ancho de línea) para crear las curvas.

geom_bspline() crea las curvas a partir de los puntos. Los puntos de color turquesa son puntos de paso, mientras que los puntos rosados actúan a modo de atractores de las líneas.

Espirales

Para crear las espirales primero hay que calcular el punto de origen de cada una de ellas, cuyas coordenadas coinciden con el último punto de cada rama por tipo de respuetas. A partir de ahí, creamos otro conjunto de datos densificado usando una función que tiene en cuenta el número de respuestas y genera unas coordenadas para cada uno de los puntos/respuesta. Además, y para librar el centro de la espiral (para que los primeros puntos no estén demasiado juntos unos de otros), se añaden N “respuestas” a la función de densificación y luego se eliminan los primeros N puntos del conjunto de datos.

Espirales de puntos creadas a partir de una función de densificación

Dibujo

Estos son los geoms utilizados para crear el gráfico-leyenda:

  • ggforce::geom_mark_hull(): crea áreas a partir de puntos. En este caso, se aplica dos veces, uno para envolver todos los puntos que representan algún tipo de impacto por un lado, y los que no suponen ningún tipo de impacto (los datos se filtran dentro de cada hull).
  • geomtextpath::geom_textpath(): adapta un texto a la forma de una línea. Se usan los datos de las espirales para crear las líneas sobre las que se asientan los textos, y se alinean a la derecha (es decir, cada texto se empieza a dibujar desde el último punto de cada espiral).
  • ggtext::geom_richtext(): permite renderizar código Markdown en un elemento de texto. En este caso, se usa para aplicar negritas y saltos de línea.
  • ggforce::geom_bspline2(): ya explicado.
  • geom_point(): a partir de su propio conjunto de datos.
Geoms utilizados en el gráfico que funciona como leyenda

El resto de gráficos se crean gracias a facet_wrap() (el truco para crear la leyenda en uno solo de los gráficos consiste en filtrar los datos de los geoms que solo queremos dibujar en el gráfico-leyenda).

Colores y tipografía

La paleta de colores es la de la imagen de marca de la Data Visualization Society.

Conclusiones

Lo que empezó siendo una excusa para probar algunas de las nuevas funcionalidades de ggplot ha acabado siendo una ocasión para alejarme de los gráficos más tradicionales y trabajar con formas más orgánicas. De hecho, y debido a los retos profesionales 1 (falta de tiempo) y 12 (falta de habilidad técnica) dejé en el tintero otras ideas para crear formas aún más orgánicas, para que las ramas se curvaran más o menos dependiendo del número de respuestas.

Evidentemente, el objetivo principal de esta propuesta es estético, y el analítico queda relegado. De ahí que me haga especial ilusión la mención Beauty Award que han otorgado a la pieza.


Más posts