Как я могу сделать диаграмму рассеяния, окрашенную по плотности в matplotlib?

83

Я хотел бы сделать диаграмму рассеяния, где каждая точка окрашена в соответствии с пространственной плотностью соседних точек.

Я столкнулся с очень похожим вопросом, в котором показан пример этого с использованием R:

R Точечная диаграмма: цвет символа представляет количество перекрывающихся точек

Какой лучший способ сделать что-то подобное в Python с помощью matplotlib?

2964502
источник
4
Привет! Люди опускали вас, вероятно, потому, что вы не переписывали вопрос, не давали никакого контекста, а также не проявляли никаких попыток сделать это самостоятельно. Рассмотрите возможность редактирования вопроса, чтобы он был самодостаточным (а не просто ссылкой), и в случае возникновения вопросов в будущем, пожалуйста, попробуйте перед публикацией.
askewchan

Ответы:

157

В дополнение к предложению @askewchan hist2dили hexbinв соответствии с ним вы можете использовать тот же метод, который используется в принятом ответе на вопрос, на который вы ссылаетесь.

Если вы хотите это сделать:

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()

введите описание изображения здесь

Если вы хотите, чтобы точки были нанесены на график в порядке плотности, чтобы самые плотные точки всегда находились сверху (как в связанном примере), просто отсортируйте их по z-значениям. Я также собираюсь использовать здесь маркер меньшего размера, так как он выглядит немного лучше:

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()

введите описание изображения здесь

Джо Кингтон
источник
4
Умно, особенно когда наверху самые «плотные» :)
askewchan
5
@Leszek - вызов эфира plt.colorbar(), или, если вы предпочитаете быть более точным, сделайте cax = ax.scatter(...)и затем fig.colorbar(cax). Имейте в виду, что единицы измерения разные. Этот метод оценивает функцию распределения вероятностей для точек, поэтому значения будут между 0 и 1 (и обычно не очень близки к 1). Вы можете преобразовать обратно в что-то более близкое к гистограммам, но это потребует некоторой работы (вам нужно знать параметры, gaussian_kdeрассчитанные на основе данных).
Джо Кингтон
1
Очень хорошо! Также может быть полезно проверить другие KDE в Python: jakevdp.github.io/blog/2013/12/01/kernel-de density- estimation и scikit-learn.org/stable/modules/de density.html В моем случае scipy.stats 'KDE длился слишком долго
Ремс
1
Почему гауссово ядро ​​дважды вызывается с помощью (xy)?
Арьян Гроен
@ArjanGroen Первый вызов создает новый объект gaussian_kde, а второй вызов оценивает предполагаемый PDF-файл по набору точек (ярлык для вызова метода оценки).
qRTPCR
35

Вы можете составить гистограмму:

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

косо
источник
Чтобы лучше соответствовать масштабу решения Джо Кингтона, вы можете построить график в логарифмической шкале  : plt.hist2d(…, norm = LogNorm())from matplotlib.colors import LogNorm).
Скиппи ле Гран Гуру,
29

Кроме того, если количество точек делает расчет KDE слишком медленным, цвет можно интерполировать в np.histogram2d [Обновить в ответ на комментарии: если вы хотите показать шкалу цвета, используйте plt.scatter () вместо ax.scatter () с последующим автор 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] )

Гийом
источник
1
Это отличный совет, спасибо. Я рисовал 100 тыс. Точек, а gaussian_kde был слишком медленным.
Эмануэль
2
Предупреждение, я заметил, что в некоторых случаях это генерирует NaN, и потому что "bounds_error = False" молчит. Точки, для которых c установлено значение NaN, не отображаются. Это не проблема gaussian_kde.
Emanuel
Большое спасибо за этот ответ. Обычно такая тепловая карта нужна, когда у нас много точек данных, а KDE в этом случае работает очень медленно. Однако остается нерешенным вопрос. Я хочу включить цветную полосу, указывающую частоту! Это вызывает ошибку: объект «AxesSubplot» не имеет атрибута «autoscale_None». Я сделал "plt.colorbar (scat, ax = ax)"
Винод Кумар
@VinodKumar, вы узнали, как построить цветовую панель?
Даниэль
1
@Daniel да, это возможно, см. Отредактированный ответ. Затем вы должны установить «density = True» при построении гистограммы, в противном случае цветовая шкала зависит от размера ячейки. @ Emanuel, Действительно! Я заменил NaN на ноль, чтобы быть уверенным, что на графике все точки (NaN должны возникать, когда данных не так много, поэтому 0.0 должно быть достаточно)
Гийом
6

Построение> 100 тыс. Точек данных?

Общепринятый ответ , используя gaussian_kde () займет много времени. На моей машине 100 тысяч строк заняли около 11 минут . Здесь я добавлю два альтернативных метода ( mpl-scatter-density и datashader ) и полученные ответы с тем же набором данных.

Далее я использовал набор тестовых данных из 100 тыс. Строк:

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)

Сравнение времени вывода и вычислений

Ниже приводится сравнение различных методов.

1: mpl-scatter-density

Установка

pip install mpl-scatter-density

Пример кода

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()

Рисование заняло 0,05 секунды: используя mpl-scatter-density

И масштабирование выглядит неплохо: увеличить mpl-scatter-density

2: datashader

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

Код (источник dsshow здесь ):

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()
  • На это ушло 0,83 с:

введите описание изображения здесь

и увеличенное изображение выглядит великолепно!

введите описание изображения здесь

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='')
  • На это ушло 11 минут: 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)

  • Чтобы нарисовать эту ячейку, потребовалось 0,021 с = (50,50): using_hist2d_50
  • На отрисовку этого бина ушло 0,173 с = (1000,1000): using_hist2d_1000
  • Минусы: увеличенные данные выглядят не так хорошо, как с mpl-scatter-density или datashader. Также вы должны сами определить количество ящиков.

увеличено в hist2d 1000bins

5: density_scatter

  • Код , как и в ответе по Гийому .
  • Чтобы нарисовать это с помощью bins = (50,50), потребовалось 0,073 с: density_scatter_50bins
  • Это заняло 0,368 с с bins = (1000,1000): density_scatter_1000bins
np8
источник