¿Cómo puedo hacer un diagrama de dispersión coloreado por densidad?

8 minutos de lectura

Avatar de usuario de 2964502
2964502

Me gustaría hacer un diagrama de dispersión donde cada punto esté coloreado por la densidad espacial de los puntos cercanos.

Me encontré con una pregunta muy similar, que muestra un ejemplo de esto usando R:

Gráfica de dispersión R: el color del símbolo representa el número de puntos superpuestos

¿Cuál es la mejor manera de lograr algo similar en python usando matplotlib?

Avatar de usuario de Joe Kington
jose kington

Además de hist2d o hexbin como sugirió @askewchan, puede usar el mismo método que usa la respuesta aceptada en la pregunta a la que se vinculó.

Si quieres hacer eso:

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde

# Generate fake data
x = np.random.normal(size=1000)
y = x * 3 + np.random.normal(size=1000)

# Calculate the point density
xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)

fig, ax = plt.subplots()
ax.scatter(x, y, c=z, s=100)
plt.show()

ingrese la descripción de la imagen aquí

Si desea que los puntos se representen en orden de densidad para que los puntos más densos estén siempre en la parte superior (similar al ejemplo vinculado), simplemente ordénelos por los valores z. También voy a usar un tamaño de marcador más pequeño aquí, ya que se ve un poco mejor:

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde

# Generate fake data
x = np.random.normal(size=1000)
y = x * 3 + np.random.normal(size=1000)

# Calculate the point density
xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)

# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]

fig, ax = plt.subplots()
ax.scatter(x, y, c=z, s=50)
plt.show()

ingrese la descripción de la imagen aquí

  • @Leszek – Llamada de éter plt.colorbar()o si prefiere ser más explícito, haga cax = ax.scatter(...) y luego fig.colorbar(cax). Tenga en cuenta que las unidades son diferentes. Este método estima la función de distribución de probabilidad de los puntos, por lo que los valores estarán entre 0 y 1 (y normalmente no se acercarán mucho a 1). Puede volver a convertir a algo más cercano a los recuentos de histogramas, pero requiere un poco de trabajo (necesita conocer los parámetros que gaussian_kde estimado a partir de los datos).

    –Joe Kington

    28 de junio de 2014 a las 13:58

  • ¿Por qué el núcleo gaussiano se llama dos veces con (xy)?

    – Arjan Groen

    31 de agosto de 2017 a las 5:24

  • @ArjanGroen La primera llamada crea un nuevo objeto gaussian_kde y la segunda llamada evalúa el pdf estimado en el conjunto de puntos (acceso directo para llamar al método de evaluación).

    – qRTPCR

    31 de octubre de 2017 a las 18:29

  • Tenga en cuenta que esta solución se vuelve increíblemente lenta para muestras grandes. Otras respuestas brindan alternativas más rápidas (consulte la respuesta de np8 para obtener una comparación de velocidad).

    – Skippy el Gran Gourou

    4 de diciembre de 2020 a las 17:07

  • En las versiones más recientes de matplotlib, el uso de este fragmento puede generar el error ValueError: Expected 2-dimensional array, got 1. La solución es cambiar edgecolor='' a edgecolor=None.

    – caballero

    31 de enero de 2021 a las 11:23

Avatar de usuario de Niko Pasanen
Niko Pasanen

¿Trazando >100k puntos de datos?

La respuesta aceptada, usando gaussian_kde() llevará mucho tiempo. En mi máquina, 100k filas tomaron alrededor 11 minutos. Aquí agregaré dos métodos alternativos (mpl-dispersión-densidad y sombreador de datos) y compare las respuestas dadas con el mismo conjunto de datos.

A continuación, utilicé un conjunto de datos de prueba de 100k filas:

import matplotlib.pyplot as plt
import numpy as np

# Fake data for testing
x = np.random.normal(size=100000)
y = x * 3 + np.random.normal(size=100000)

Comparación de salida y tiempo de cálculo

A continuación se muestra una comparación de diferentes métodos.

1: mpl-scatter-density

Instalación

pip install mpl-scatter-density

Código de ejemplo

import mpl_scatter_density # adds projection='scatter_density'
from matplotlib.colors import LinearSegmentedColormap

# "Viridis-like" colormap with white background
white_viridis = LinearSegmentedColormap.from_list('white_viridis', [
    (0, '#ffffff'),
    (1e-20, '#440053'),
    (0.2, '#404388'),
    (0.4, '#2a788e'),
    (0.6, '#21a784'),
    (0.8, '#78d151'),
    (1, '#fde624'),
], N=256)

def using_mpl_scatter_density(fig, x, y):
    ax = fig.add_subplot(1, 1, 1, projection='scatter_density')
    density = ax.scatter_density(x, y, cmap=white_viridis)
    fig.colorbar(density, label="Number of points per pixel")

fig = plt.figure()
using_mpl_scatter_density(fig, x, y)
plt.show()

Dibujar esto tomó 0.05 segundos:
usando mpl-scatter-density

Y el acercamiento se ve bastante bien:
acercar la densidad de dispersión de mpl

2: datashader

Instalación

pip install datashader

Código (listado de fuentes y parámetros para mostrar):


import datashader as ds
from datashader.mpl_ext import dsshow
import pandas as pd


def using_datashader(ax, x, y):

    df = pd.DataFrame(dict(x=x, y=y))
    dsartist = dsshow(
        df,
        ds.Point("x", "y"),
        ds.count(),
        vmin=0,
        vmax=35,
        norm="linear",
        aspect="auto",
        ax=ax,
    )

    plt.colorbar(dsartist)


fig, ax = plt.subplots()
using_datashader(ax, x, y)
plt.show()
  • Tomó 0.83 s dibujar esto:

ingrese la descripción de la imagen aquí

  • También hay posibilidad de colorear por tercera variable. El tercer parámetro de dsshow controla la coloración. Ver más ejemplos aquí y la fuente de dsshow aquí.

3: scatter_with_gaussian_kde

def scatter_with_gaussian_kde(ax, x, y):
    # https://stackoverflow.com/a/20107592/3015186
    # Answer by Joel Kington

    xy = np.vstack([x, y])
    z = gaussian_kde(xy)(xy)

    ax.scatter(x, y, c=z, s=100, edgecolor="")
  • Tomó 11 minutos dibujar esto:
    dispersión_con_gaussian_kde

4: using_hist2d

import matplotlib.pyplot as plt
def using_hist2d(ax, x, y, bins=(50, 50)):
    # https://stackoverflow.com/a/20105673/3015186
    # Answer by askewchan
    ax.hist2d(x, y, bins, cmap=plt.cm.jet)

  • Tomó 0.021 s dibujar estos contenedores = (50,50):
    usando_hist2d_50
  • Tomó 0.173 s dibujar estos contenedores = (1000,1000):
    usando_hist2d_1000
  • Contras: los datos ampliados no se ven tan bien como con mpl-scatter-density o datashader. También debe determinar el número de contenedores usted mismo.

zoom en hist2d 1000bins

5: density_scatter

  • El código es como en la respuesta de Guillaume.
  • Tomó 0.073 s dibujar esto con bins=(50,50):
    densidad_dispersión_50 contenedores
  • Tomó 0.368 s dibujar esto con bins=(1000,1000):
    densidad_dispersión_1000 contenedores

  • ¡Gran respuesta! FYI, la integración matplotlib-datashader se fusionó a partir de datashader 0.12. Vea más ejemplos aquí: datashader.org/getting_started/…

    – nvictus

    11 de enero de 2022 a las 21:07

  • Gracias, actualicé la respuesta para reflejar la última versión de datashader. ¡Es genial ver soporte nativo para matplotlib!

    – Niko Pasanen

    9 de marzo de 2022 a las 18:54

  • ¡¡Gracias!! ¡Tengo más de 300k muestras y su respuesta fue de gran ayuda!

    – jkr

    9 dic 2022 a las 18:04

  • También puede suavizar el histograma: primero constrúyalo explícitamente con numpy.histogram2d, luego scipy.signal.convolve con una pequeña matriz que, por ejemplo, puede parecer una gaussiana (t2=np.linspace(-4,4,100)**2;g=np.exp(-(t2[:,None]+t2[None,:]))), y use plt.imshow para mostrar el resultado.

    – Marc Glisse

    9 de marzo a las 18:39

Avatar de usuario de Guillaume
Guillaume

Además, si el número de puntos hace que el cálculo de KDE sea demasiado lento, el color se puede interpolar en np.histogram2d [Update in response to comments: If you wish to show the colorbar, use plt.scatter() instead of ax.scatter() followed by plt.colorbar()]:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.colors import Normalize 
from scipy.interpolate import interpn

def density_scatter( x , y, ax = None, sort = True, bins = 20, **kwargs )   :
    """
    Scatter plot colored by 2d histogram
    """
    if ax is None :
        fig , ax = plt.subplots()
    data , x_e, y_e = np.histogram2d( x, y, bins = bins, density = True )
    z = interpn( ( 0.5*(x_e[1:] + x_e[:-1]) , 0.5*(y_e[1:]+y_e[:-1]) ) , data , np.vstack([x,y]).T , method = "splinef2d", bounds_error = False)

    #To be sure to plot all data
    z[np.where(np.isnan(z))] = 0.0

    # Sort the points by density, so that the densest points are plotted last
    if sort :
        idx = z.argsort()
        x, y, z = x[idx], y[idx], z[idx]

    ax.scatter( x, y, c=z, **kwargs )

    norm = Normalize(vmin = np.min(z), vmax = np.max(z))
    cbar = fig.colorbar(cm.ScalarMappable(norm = norm), ax=ax)
    cbar.ax.set_ylabel('Density')

    return ax


if "__main__" == __name__ :

    x = np.random.normal(size=100000)
    y = x * 3 + np.random.normal(size=100000)
    density_scatter( x, y, bins = [30,30] )

¿Cómo puedo hacer un diagrama de dispersión coloreado por densidad?

  • Este es un gran consejo, gracias. Estaba trazando 100k puntos y gaussian_kde era prohibitivamente lento.

    – Emmanuel

    10 de marzo de 2019 a las 14:05

  • Advertencia, noté que en algunos casos esto genera NaN y porque “bounds_error = False” es silencioso. Los puntos con c establecido en NaN no se trazan. Esto no es un problema con gaussian_kde.

    – Emmanuel

    28 de junio de 2019 a las 10:45

  • Muchas gracias por esta respuesta. Por lo general, queremos un mapa de calor como este cuando tenemos una gran cantidad de puntos de datos, y KDE es muy lento en este caso. Sin embargo, todavía hay un tema abierto. ¡Quiero incluir una barra de color que indique la frecuencia! Esto arroja un error: el objeto ‘AxesSubplot’ no tiene el atributo ‘autoscale_None’. Hice “plt.colorbar(scat, ax=ax)”

    – Vinod Kumar

    19/11/2019 a las 14:00


  • @VinodKumar, ¿supiste cómo trazar la barra de colores?

    – Daniel

    23 de marzo de 2020 a las 16:14

  • @Daniel sí, esto es posible, vea la respuesta editada. Luego, debe establecer “densidad = Verdadero” al construir el histograma; de lo contrario, la barra de colores depende del tamaño del contenedor.@Emanuel, ¡Efectivamente! Reemplacé los NaN por cero para asegurarme de trazar todos los puntos (los NaN deberían ocurrir cuando no hay muchos datos, por lo que 0.0 debería ser suficiente)

    – Guillermo

    23 de marzo de 2020 a las 17:14

avatar de usuario de askewchan
asqueado

Podrías hacer un histograma:

import numpy as np
import matplotlib.pyplot as plt

# fake data:
a = np.random.normal(size=1000)
b = a*3 + np.random.normal(size=1000)

plt.hist2d(a, b, (50, 50), cmap=plt.cm.jet)
plt.colorbar()

2dhist

¿Ha sido útil esta solución?