Làm cách nào để tạo biểu đồ phân tán được tô màu theo mật độ trong matplotlib?


82

Tôi muốn tạo một biểu đồ phân tán trong đó mỗi điểm được tô màu theo mật độ không gian của các điểm lân cận.

Tôi đã gặp một câu hỏi tương tự, cho thấy một ví dụ về điều này bằng cách sử dụng R:

R Scatter Plot: màu biểu tượng thể hiện số điểm chồng chéo

Cách tốt nhất để thực hiện điều gì đó tương tự trong python bằng matplotlib là gì?


4
Chào! Mọi người đã phản đối bạn có thể vì bạn đã không viết lại câu hỏi hoặc đưa ra bất kỳ bối cảnh nào, cũng như bạn không thể hiện bất kỳ nỗ lực nào để tự mình làm điều đó. Cân nhắc chỉnh sửa câu hỏi để tự cung cấp (không chỉ là một liên kết) và đối với các câu hỏi trong tương lai, vui lòng thử trước khi đăng.
askewchan

Câu trả lời:


157

Ngoài hist2dhoặc hexbinnhư @askewchan đã đề xuất, bạn có thể sử dụng cùng một phương pháp mà câu trả lời được chấp nhận trong câu hỏi mà bạn đã liên kết sử dụng.

Nếu bạn muốn làm điều đó:

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

nhập mô tả hình ảnh ở đây

Nếu bạn muốn các điểm được vẽ theo thứ tự mật độ để các điểm dày đặc nhất luôn ở trên cùng (tương tự như ví dụ được liên kết), chỉ cần sắp xếp chúng theo giá trị z. Tôi cũng sẽ sử dụng kích thước điểm đánh dấu nhỏ hơn ở đây vì nó trông đẹp hơn một chút:

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

nhập mô tả hình ảnh ở đây


4
Thông minh, đặc biệt là nhận được những cái 'dày đặc nhất' trên đỉnh :)
askewchan

5
@Leszek - Cuộc gọi Ether plt.colorbar(), hoặc nếu bạn muốn rõ ràng hơn, hãy làm cax = ax.scatter(...)và sau đó fig.colorbar(cax). Cần biết rằng các đơn vị là khác nhau. Phương pháp này ước tính hàm phân phối xác suất cho các điểm, vì vậy các giá trị sẽ nằm trong khoảng từ 0 đến 1 (và thường sẽ không gần bằng 1). Bạn có thể chuyển đổi lại thành một thứ gì đó gần với số lượng biểu đồ hơn, nhưng sẽ mất một chút công việc (bạn cần biết các tham số được gaussian_kdeước tính từ dữ liệu).
Joe Kington

1
Rất đẹp! Kiểm tra các KDE khác bằng Python cũng có thể hữu ích: jakevdp.github.io/blog/2013/12/01/kernel-density-estimationscikit-learn.org/stable/modules/density.html Trong trường hợp của tôi là scipy.stats 'KDE đã mất quá nhiều thời gian
Rems

1
Tại sao hạt nhân Gauss được gọi là hai lần với (xy)?
Arjan Groen,

@ArjanGroen Cuộc gọi đầu tiên tạo một đối tượng gaussian_kde mới và cuộc gọi thứ hai đánh giá pdf ước tính trên tập hợp điểm (phím tắt để gọi phương thức đánh giá).
qRTPCR

34

Bạn có thể tạo một biểu đồ:

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


26

Ngoài ra, nếu số điểm làm cho phép tính KDE quá chậm, màu có thể được nội suy trong np.histogram2d [Cập nhật theo nhận xét: Nếu bạn muốn hiển thị thanh màu, hãy sử dụng plt.scatter () thay vì ax.scatter () theo sau bởi 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] )


Đây là một mẹo tuyệt vời, cảm ơn bạn. Tôi đã vẽ được 100k điểm và gaussian_kde cực kỳ chậm.
Emanuel

2
Cảnh báo, tôi nhận thấy trong một số trường hợp, điều này tạo NaN và vì "bounds_error = False" nên nó không hoạt động. Các điểm có c được đặt thành NaN không được vẽ biểu đồ. Đây không phải là vấn đề với gaussian_kde.
Emanuel

Rất cám ơn vì phản hồi này. Thông thường, chúng ta muốn bản đồ nhiệt như thế này khi chúng ta có một số lượng lớn các điểm dữ liệu và KDE rất chậm trong trường hợp này. Tuy nhiên, vẫn còn một vấn đề còn bỏ ngỏ. Tôi muốn bao gồm một thanh màu cho biết tần số! Điều này gây ra lỗi: đối tượng 'AxesSubplot' không có thuộc tính 'autoscale_None'. Tôi đã làm "plt.colorbar (sc, ax = ax)"
Vinod Kumar

@VinodKumar bạn đã tìm ra cách vẽ thanh màu chưa?
Daniel

1
@Daniel vâng, điều này có thể, hãy xem câu trả lời đã chỉnh sửa. Sau đó, bạn phải đặt "mật độ = True" khi xây dựng biểu đồ, nếu không, thanh màu phụ thuộc vào kích thước thùng. @ Emanuel, Thật vậy! Tôi đã thay thế các NaN bằng 0 để đảm bảo vẽ tất cả các điểm (NaN sẽ xảy ra khi không có nhiều dữ liệu, vì vậy 0,0 phải là đủ)
Guillaume

4

Mưu đồ> 100k điểm dữ liệu?

Các câu trả lời được chấp nhận , sử dụng gaussian_kde () sẽ mất rất nhiều thời gian. Trên máy của tôi, 100k hàng mất khoảng 11 phút . Ở đây tôi sẽ thêm hai phương pháp thay thế ( mpl-scatter-mật độdatashader ) và so sánh các câu trả lời đã cho với cùng một tập dữ liệu.

Trong phần sau, tôi đã sử dụng tập dữ liệu thử nghiệm gồm 100 nghìn hàng:

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)

So sánh đầu ra và thời gian tính toán

Dưới đây là so sánh các phương pháp khác nhau.

1: mpl-scatter-density

Cài đặt

pip install mpl-scatter-density

Mã mẫu

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

Vẽ cái này mất 0,05 giây: sử dụng mật độ phân tán mpl

Và phóng to trông khá đẹp: phóng to mật độ phân tán mpl

2: datashader

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

Code (nguồn dsshow đây ):

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()
  • Phải mất 0,83 giây để vẽ cái này:

nhập mô tả hình ảnh ở đây

và hình ảnh được phóng to trông rất tuyệt!

nhập mô tả hình ảnh ở đây

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='')
  • Phải mất 11 phút để vẽ cái này: 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)

  • Mất 0,021 giây để vẽ thùng này = (50,50): using_hist2d_50
  • Mất 0,173 giây để vẽ thùng này = (1000,1000): using_hist2d_1000
  • Nhược điểm: Dữ liệu được phóng to trông không đẹp bằng với mật độ phân tán mpl hoặc datashader. Ngoài ra bạn phải tự xác định số lượng thùng.

phóng to lịch sử 1000bins

5: density_scatter

  • Mã như trong câu trả lời của Guillaume .
  • Mất 0,073 giây để vẽ cái này với thùng = (50,50): Mật độ_scatter_50bins
  • Mất 0,368 giây để vẽ cái này với thùng = (1000,1000): mật độ_scatter_1000bins
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.