Crear gráficos de burbujas empaquetadas con Python

Beginner

This tutorial is from open-source community. Access the source code

Introducción

En este laboratorio, aprenderemos a crear un gráfico de burbujas empaquetadas utilizando Matplotlib en Python. El gráfico de burbujas empaquetadas es un tipo de gráfico que muestra datos en burbujas de diferentes tamaños, donde el tamaño de la burbuja representa la magnitud de los datos. Este gráfico es útil para mostrar datos escalares, donde los datos son un solo valor numérico asociado con un elemento.

Consejos sobre la VM

Una vez finalizada la inicialización de la VM, haga clic en la esquina superior izquierda para cambiar a la pestaña Cuaderno y acceder a Jupyter Notebook para practicar.

A veces, es posible que tenga que esperar unos segundos a que Jupyter Notebook termine de cargarse. La validación de las operaciones no se puede automatizar debido a las limitaciones de Jupyter Notebook.

Si tiene problemas durante el aprendizaje, no dude en preguntar a Labby. Deje sus comentarios después de la sesión y lo resolveremos rápidamente para usted.

Importar las bibliotecas necesarias

Para crear un gráfico de burbujas empaquetadas, necesitamos importar las bibliotecas matplotlib.pyplot y numpy. La biblioteca numpy se utiliza para realizar operaciones matemáticas en matrices, lo que es útil para calcular los tamaños de las burbujas.

import matplotlib.pyplot as plt
import numpy as np

Definir los datos

Los datos que utilizaremos para este ejemplo son las cuotas de mercado de diferentes navegadores de escritorio. Definiremos los datos como un diccionario que contiene los nombres de los navegadores, la cuota de mercado y el color para cada burbuja.

browser_market_share = {
    'browsers': ['firefox', 'chrome','safari', 'edge', 'ie', 'opera'],
   'market_share': [8.61, 69.55, 8.36, 4.12, 2.76, 2.43],
    'color': ['#5A69AF', '#579E65', '#F9C784', '#FC944A', '#F24C00', '#00B825']
}

Definir la clase BubbleChart

La clase BubbleChart se utiliza para crear el gráfico de burbujas empaquetadas. La clase recibe una matriz de áreas de burbujas y un valor de espaciado entre burbujas. El método __init__ configura las posiciones iniciales de las burbujas y calcula la distancia máxima de paso, que es la distancia que cada burbuja puede moverse en una sola iteración.

class BubbleChart:
    def __init__(self, area, bubble_spacing=0):
        """
        Configuración para el colapso de las burbujas.

        Parámetros
        ----------
        area : array-like
            Área de las burbujas.
        bubble_spacing : float, valor predeterminado: 0
            Espaciado mínimo entre las burbujas después del colapso.

        Notas
        -----
        Si "area" está ordenado, los resultados pueden verse extraños.
        """
        area = np.asarray(area)
        r = np.sqrt(area / np.pi)

        self.bubble_spacing = bubble_spacing
        self.bubbles = np.ones((len(area), 4))
        self.bubbles[:, 2] = r
        self.bubbles[:, 3] = area
        self.maxstep = 2 * self.bubbles[:, 2].max() + self.bubble_spacing
        self.step_dist = self.maxstep / 2

        ## calcular el diseño de cuadrícula inicial para las burbujas
        length = np.ceil(np.sqrt(len(self.bubbles)))
        grid = np.arange(length) * self.maxstep
        gx, gy = np.meshgrid(grid, grid)
        self.bubbles[:, 0] = gx.flatten()[:len(self.bubbles)]
        self.bubbles[:, 1] = gy.flatten()[:len(self.bubbles)]

        self.com = self.center_of_mass()

Definir métodos de movimiento de burbujas

La clase BubbleChart también contiene métodos para mover las burbujas hacia el centro de masa y comprobar si chocan con otras burbujas. El método center_of_mass calcula el centro de masa de todas las burbujas, y el método center_distance calcula la distancia entre una burbuja y el centro de masa. El método outline_distance calcula la distancia entre el contorno de una burbuja y los contornos de otras burbujas, y el método check_collisions comprueba si una nueva posición de burbuja choca con otras burbujas.

    def center_of_mass(self):
        return np.average(
            self.bubbles[:, :2], axis=0, weights=self.bubbles[:, 3]
        )

    def center_distance(self, bubble, bubbles):
        return np.hypot(bubble[0] - bubbles[:, 0],
                        bubble[1] - bubbles[:, 1])

    def outline_distance(self, bubble, bubbles):
        center_distance = self.center_distance(bubble, bubbles)
        return center_distance - bubble[2] - \
            bubbles[:, 2] - self.bubble_spacing

    def check_collisions(self, bubble, bubbles):
        distance = self.outline_distance(bubble, bubbles)
        return len(distance[distance < 0])

Definir el método de colisión de burbujas

La clase BubbleChart también contiene un método para comprobar las colisiones de burbujas y moverse alrededor de las burbujas en colisión. El método collides_with calcula la distancia entre una nueva posición de burbuja y las posiciones de otras burbujas. Si la distancia es menor que cero, significa que hay una colisión y el método devuelve el índice de la burbuja en colisión. El método collapse mueve las burbujas hacia el centro de masa y alrededor de las burbujas en colisión, y el método plot dibuja las burbujas en el gráfico.

    def collides_with(self, bubble, bubbles):
        distance = self.outline_distance(bubble, bubbles)
        idx_min = np.argmin(distance)
        return idx_min if type(idx_min) == np.ndarray else [idx_min]

    def collapse(self, n_iterations=50):
        """
        Mueve las burbujas hacia el centro de masa.

        Parámetros
        ----------
        n_iterations : int, valor predeterminado: 50
            Número de movimientos a realizar.
        """
        for _i in range(n_iterations):
            moves = 0
            for i in range(len(self.bubbles)):
                rest_bub = np.delete(self.bubbles, i, 0)
                ## intenta moverse directamente hacia el centro de masa
                ## vector de dirección desde la burbuja hasta el centro de masa
                dir_vec = self.com - self.bubbles[i, :2]

                ## acorta el vector de dirección para que tenga una longitud de 1
                dir_vec = dir_vec / np.sqrt(dir_vec.dot(dir_vec))

                ## calcula la nueva posición de la burbuja
                new_point = self.bubbles[i, :2] + dir_vec * self.step_dist
                new_bubble = np.append(new_point, self.bubbles[i, 2:4])

                ## comprueba si la nueva burbuja choca con otras burbujas
                if not self.check_collisions(new_bubble, rest_bub):
                    self.bubbles[i, :] = new_bubble
                    self.com = self.center_of_mass()
                    moves += 1
                else:
                    ## intenta moverse alrededor de una burbuja con la que chocas
                    ## encuentra la burbuja en colisión
                    for colliding in self.collides_with(new_bubble, rest_bub):
                        ## calcula el vector de dirección
                        dir_vec = rest_bub[colliding, :2] - self.bubbles[i, :2]
                        dir_vec = dir_vec / np.sqrt(dir_vec.dot(dir_vec))
                        ## calcula el vector ortogonal
                        orth = np.array([dir_vec[1], -dir_vec[0]])
                        ## prueba en qué dirección ir
                        new_point1 = (self.bubbles[i, :2] + orth *
                                      self.step_dist)
                        new_point2 = (self.bubbles[i, :2] - orth *
                                      self.step_dist)
                        dist1 = self.center_distance(
                            self.com, np.array([new_point1]))
                        dist2 = self.center_distance(
                            self.com, np.array([new_point2]))
                        new_point = new_point1 if dist1 < dist2 else new_point2
                        new_bubble = np.append(new_point, self.bubbles[i, 2:4])
                        if not self.check_collisions(new_bubble, rest_bub):
                            self.bubbles[i, :] = new_bubble
                            self.com = self.center_of_mass()

            if moves / len(self.bubbles) < 0.1:
                self.step_dist = self.step_dist / 2

    def plot(self, ax, labels, colors):
        """
        Dibuja el gráfico de burbujas.

        Parámetros
        ----------
        ax : matplotlib.axes.Axes
        labels : list
            Etiquetas de las burbujas.
        colors : list
            Colores de las burbujas.
        """
        for i in range(len(self.bubbles)):
            circ = plt.Circle(
                self.bubbles[i, :2], self.bubbles[i, 2], color=colors[i])
            ax.add_patch(circ)
            ax.text(*self.bubbles[i, :2], labels[i],
                    horizontalalignment='center', verticalalignment='center')

Crear un objeto BubbleChart y graficar el gráfico

Para crear el gráfico de burbujas empaquetadas, necesitamos crear un objeto BubbleChart y llamar al método collapse para mover las burbujas hacia el centro de masa. Luego podemos crear una figura de matplotlib y agregar un objeto de ejes a ella. Finalmente, llamamos al método plot para dibujar las burbujas en el gráfico.

bubble_chart = BubbleChart(area=browser_market_share['market_share'],
                           bubble_spacing=0.1)

bubble_chart.collapse()

fig, ax = plt.subplots(subplot_kw=dict(aspect="equal"))
bubble_chart.plot(
    ax, browser_market_share['browsers'], browser_market_share['color'])
ax.axis("off")
ax.relim()
ax.autoscale_view()
ax.set_title('Browser market share')

plt.show()

Resumen

En este laboratorio, aprendimos cómo crear un gráfico de burbujas empaquetadas utilizando Matplotlib en Python. Definimos los datos como un diccionario que contiene los nombres de los navegadores, la participación en el mercado y el color para cada burbuja. Luego creamos una clase BubbleChart para configurar las posiciones iniciales de las burbujas, calcular la distancia máxima de paso, mover las burbujas hacia el centro de masa y comprobar si chocan con otras burbujas. Finalmente, graficamos el gráfico utilizando matplotlib.