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

82

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áfico 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?

2964502
fuente
4
¡Hola! La gente te ha estado rechazando probablemente porque no reescribiste la pregunta ni le diste ningún contexto, ni mostraste ningún intento de hacerlo tú mismo. Considere editar la pregunta para que sea autosuficiente (no solo un enlace), y para preguntas futuras, intente antes de publicar.
askewchan

Respuestas:

157

Además de hist2do hexbincomo sugirió @askewchan, puede usar el mismo método que usa la respuesta aceptada en la pregunta que 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, edgecolor='')
plt.show()

ingrese la descripción de la imagen aquí

Si desea que los puntos se tracen 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, edgecolor='')
plt.show()

ingrese la descripción de la imagen aquí

Joe Kington
fuente
4
Inteligente, especialmente para conseguir los 'más densos' en la parte superior :)
askewchan
5
@Leszek - Llamada Ether plt.colorbar(), o si prefieres ser más explícito, hazlo 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 para 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 gaussian_kdeestimados a partir de los datos).
Joe Kington
1
¡Muy agradable! Ver otros KDE en Python también puede ser útil: jakevdp.github.io/blog/2013/12/01/kernel-density-estimation y scikit-learn.org/stable/modules/density.html En mi caso, scipy.stats 'KDE estaba tardando demasiado
Rems
1
¿Por qué se llama dos veces al kernel gaussiano con (xy)?
Arjan Groen
@ArjanGroen La primera llamada crea un nuevo objeto gaussian_kde y la segunda llamada evalúa el pdf estimado en el conjunto de puntos (atajo para llamar al método de evaluación).
qRTPCR
34

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

askewchan
fuente
26

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 [Actualización en respuesta a los comentarios: si desea mostrar la barra de colores, use plt.scatter () en lugar de ax.scatter () seguido por 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] )

Guillaume
fuente
Este es un gran consejo, gracias. Estaba trazando 100k puntos y gaussian_kde era prohibitivamente lento.
Emanuel
2
Advertencia, he notado que en algunos casos esto genera NaN y porque "limits_error = False" es silencioso. Los puntos con c establecido en NaNs no se trazan. Este no es un problema con gaussian_kde.
Emanuel
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
@VinodKumar, ¿averiguaste cómo trazar la barra de colores?
Daniel
1
@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. Reemplacé los NaN por cero para asegurarme de trazar todos los puntos (los NaN deberían suceder cuando no hay muchos datos, por lo que 0.0 debería ser suficiente)
Guillaume
4

¿Trazar> 100k puntos de datos?

La respuesta aceptada , usar gaussian_kde () llevará mucho tiempo. En mi máquina, 100 mil filas tomaron aproximadamente 11 minutos . Aquí agregaré dos métodos alternativos ( mpl-scatter-density y datashader ) y compararé 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 tiempo de salida y 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 densidad de dispersión de mpl

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

2: datashader

pip install "git+https://github.com/nvictus/datashader.git@mpl"

Código (fuente de dsshow aquí ):

from functools import partial

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

dyn = partial(ds.tf.dynspread, max_px=40, threshold=0.5)

def using_datashader(ax, x, y):

    df = pd.DataFrame(dict(x=x, y=y))
    da1 = dsshow(df, ds.Point('x', 'y'), spread_fn=dyn, aspect='auto', ax=ax)
    plt.colorbar(da1)

fig, ax = plt.subplots()
using_datashader(ax, x, y)
plt.show()
  • Se necesitaron 0.83 s para dibujar esto:

ingrese la descripción de la imagen aquí

¡y la imagen ampliada se ve genial!

ingrese la descripción de la imagen 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: scatter_with_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)

  • Se necesitaron 0.021 s para dibujar estos contenedores = (50,50): using_hist2d_50
  • Se necesitaron 0.173 s para dibujar estos contenedores = (1000,1000): using_hist2d_1000
  • Desventajas: Los datos ampliados no se ven tan bien como con mpl-scatter-density o datashader. También debe determinar la cantidad de contenedores usted mismo.

ampliado en hist2d 1000bins

5: density_scatter

  • El código es como en la respuesta de Guillaume .
  • Se necesitaron 0.073 s para dibujar esto con bins = (50,50): densidad_dispersión_50bins
  • Se necesitaron 0.368 s para dibujar esto con bins = (1000,1000): densidad_dispersión_1000bins
np8
fuente