Xác định điểm giữa của bản đồ màu trong matplotlib


87

Tôi muốn đặt điểm giữa của bản đồ màu, tức là dữ liệu của tôi đi từ -5 đến 10, tôi muốn số 0 là giữa. Tôi nghĩ rằng cách để làm điều đó là chuẩn hóa lớp con và sử dụng quy chuẩn, nhưng tôi không tìm thấy bất kỳ ví dụ nào và tôi không rõ ràng, chính xác thì tôi phải thực hiện những gì.


đây được gọi là bản đồ màu "phân kỳ" hoặc "lưỡng cực", trong đó điểm trung tâm của bản đồ là quan trọng và dữ liệu đi trên và dưới điểm này. sandia.gov/~kmorel/documents/ColorMaps
endolith

3
Tất cả các câu trả lời trong chủ đề này có vẻ khá phức tạp. Giải pháp dễ sử dụng được thể hiện trong câu trả lời tuyệt vời này , đồng thời cũng được đưa vào tài liệu matplotlib, phần Chuẩn hóa tùy chỉnh: Hai phạm vi tuyến tính .
ImportanceOfBeingErnest

Câu trả lời:


14

Lưu ý rằng trong phiên bản matplotlib 3.1, lớp DivergingNorm đã được thêm vào. Tôi nghĩ rằng nó bao gồm trường hợp sử dụng của bạn. Nó có thể được sử dụng như thế này:

from matplotlib import colors
colors.DivergingNorm(vmin=-4000., vcenter=0., vmax=10000)

Trong matplotlib 3.2, lớp đã được đổi tên thành TwoSlopesNorm


Điều này có vẻ thú vị, nhưng có vẻ như điều này phải được sử dụng để biến đổi dữ liệu trước khi vẽ biểu đồ. Chú giải của thanh màu sẽ liên quan đến dữ liệu đã biến đổi, không phải dữ liệu gốc.
Bli

3
@bli không phải vậy đâu. các normkhông bình thường cho hình ảnh của bạn. normssong hành với các bản đồ màu.
Paul H

1
Thật khó chịu vì điều này không được chấp nhận kể từ 3.2 và không có tài liệu nào về cách thay thế nó: matplotlib.org/3.2.0/api/_as_gen/…
daknowles

1
Vâng, các tài liệu không rõ ràng. Tôi nghĩ nó đã được đổi tên thành TwoSlopeNorm: matplotlib.org/3.2.0/api/_as_gen/…
macKaiver

91

Tôi biết điều này là muộn đối với trò chơi, nhưng tôi chỉ trải qua quá trình này và đưa ra một giải pháp có lẽ ít mạnh mẽ hơn so với phân lớp bình thường, nhưng đơn giản hơn nhiều. Tôi nghĩ sẽ rất tốt nếu chia sẻ nó ở đây cho hậu thế.

Chức năng

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid

def shiftedColorMap(cmap, start=0, midpoint=0.5, stop=1.0, name='shiftedcmap'):
    '''
    Function to offset the "center" of a colormap. Useful for
    data with a negative min and positive max and you want the
    middle of the colormap's dynamic range to be at zero.

    Input
    -----
      cmap : The matplotlib colormap to be altered
      start : Offset from lowest point in the colormap's range.
          Defaults to 0.0 (no lower offset). Should be between
          0.0 and `midpoint`.
      midpoint : The new center of the colormap. Defaults to 
          0.5 (no shift). Should be between 0.0 and 1.0. In
          general, this should be  1 - vmax / (vmax + abs(vmin))
          For example if your data range from -15.0 to +5.0 and
          you want the center of the colormap at 0.0, `midpoint`
          should be set to  1 - 5/(5 + 15)) or 0.75
      stop : Offset from highest point in the colormap's range.
          Defaults to 1.0 (no upper offset). Should be between
          `midpoint` and 1.0.
    '''
    cdict = {
        'red': [],
        'green': [],
        'blue': [],
        'alpha': []
    }

    # regular index to compute the colors
    reg_index = np.linspace(start, stop, 257)

    # shifted index to match the data
    shift_index = np.hstack([
        np.linspace(0.0, midpoint, 128, endpoint=False), 
        np.linspace(midpoint, 1.0, 129, endpoint=True)
    ])

    for ri, si in zip(reg_index, shift_index):
        r, g, b, a = cmap(ri)

        cdict['red'].append((si, r, r))
        cdict['green'].append((si, g, g))
        cdict['blue'].append((si, b, b))
        cdict['alpha'].append((si, a, a))

    newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
    plt.register_cmap(cmap=newcmap)

    return newcmap

Một ví dụ

biased_data = np.random.random_integers(low=-15, high=5, size=(37,37))

orig_cmap = matplotlib.cm.coolwarm
shifted_cmap = shiftedColorMap(orig_cmap, midpoint=0.75, name='shifted')
shrunk_cmap = shiftedColorMap(orig_cmap, start=0.15, midpoint=0.75, stop=0.85, name='shrunk')

fig = plt.figure(figsize=(6,6))
grid = AxesGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.5,
                label_mode="1", share_all=True,
                cbar_location="right", cbar_mode="each",
                cbar_size="7%", cbar_pad="2%")

# normal cmap
im0 = grid[0].imshow(biased_data, interpolation="none", cmap=orig_cmap)
grid.cbar_axes[0].colorbar(im0)
grid[0].set_title('Default behavior (hard to see bias)', fontsize=8)

im1 = grid[1].imshow(biased_data, interpolation="none", cmap=orig_cmap, vmax=15, vmin=-15)
grid.cbar_axes[1].colorbar(im1)
grid[1].set_title('Centered zero manually,\nbut lost upper end of dynamic range', fontsize=8)

im2 = grid[2].imshow(biased_data, interpolation="none", cmap=shifted_cmap)
grid.cbar_axes[2].colorbar(im2)
grid[2].set_title('Recentered cmap with function', fontsize=8)

im3 = grid[3].imshow(biased_data, interpolation="none", cmap=shrunk_cmap)
grid.cbar_axes[3].colorbar(im3)
grid[3].set_title('Recentered cmap with function\nand shrunk range', fontsize=8)

for ax in grid:
    ax.set_yticks([])
    ax.set_xticks([])

Kết quả của ví dụ:

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


Rất cám ơn vì sự đóng góp tuyệt vời của bạn! Tuy nhiên, mã đã không có khả năng cả hai cắt xén và thay đổi bản đồ cùng màu, và hướng dẫn của bạn là một không chính xác chút và gây hiểu lầm. Bây giờ tôi đã sửa điều đó và có quyền tự do chỉnh sửa bài đăng của bạn. Ngoài ra, tôi đã đưa nó vào một trong những thư viện cá nhân của mình và thêm bạn làm tác giả. Tôi hy vọng bạn không phiền.
TheChymera

@TheChymera bản đồ màu ở góc dưới bên phải đã được cắt và tạo mới. Hãy sử dụng cái này khi bạn thấy phù hợp.
Paul H

Vâng, nó có, thật đáng buồn là nó chỉ có vẻ đúng như một sự trùng hợp. Nếu startstopkhông tương ứng là 0 và 1, sau khi bạn làm như vậy reg_index = np.linspace(start, stop, 257), bạn không còn có thể giả định rằng giá trị 129 là điểm giữa của cmap ban đầu, do đó toàn bộ việc thay đổi tỷ lệ không có ý nghĩa gì bất cứ khi nào bạn cắt. Ngoài ra, startnên từ 0 đến 0,5 và stoptừ 0,5 đến 1, không phải cả hai từ 0 đến 1 như bạn hướng dẫn.
TheChymera

@TheChymera Tôi đã dùng thử phiên bản của bạn và có hai suy nghĩ về nó. 1) Đối với tôi, dường như các chỉ số bạn đã tạo đều có độ dài 257, và trong matplotlib, nó được mặc định là 256. 2) giả sử phạm vi dữ liệu của tôi từ -1 đến 1000, nó bị chi phối bởi các mức tích cực và do đó, nhiều cấp / lớp hơn sẽ chuyển sang nhánh tích cực. Nhưng chức năng của bạn cung cấp 128 cấp độ cho cả tiêu cực và tích cực, vì vậy tôi nghĩ sẽ "công bằng" hơn nếu chia các cấp độ không đồng đều.
Jason

Đây là một giải pháp tuyệt vời, nhưng nó không thành công nếu midpointdữ liệu bằng 0 hoặc 1. Xem câu trả lời của tôi bên dưới để biết cách khắc phục đơn giản cho vấn đề đó.
DaveTheScientist

22

Đây là một giải pháp phân lớp Normalize. Để dùng nó

norm = MidPointNorm(midpoint=3)
imshow(X, norm=norm)

Đây là Lớp:

import numpy as np
from numpy import ma
from matplotlib import cbook
from matplotlib.colors import Normalize

class MidPointNorm(Normalize):    
    def __init__(self, midpoint=0, vmin=None, vmax=None, clip=False):
        Normalize.__init__(self,vmin, vmax, clip)
        self.midpoint = midpoint

    def __call__(self, value, clip=None):
        if clip is None:
            clip = self.clip

        result, is_scalar = self.process_value(value)

        self.autoscale_None(result)
        vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint

        if not (vmin < midpoint < vmax):
            raise ValueError("midpoint must be between maxvalue and minvalue.")       
        elif vmin == vmax:
            result.fill(0) # Or should it be all masked? Or 0.5?
        elif vmin > vmax:
            raise ValueError("maxvalue must be bigger than minvalue")
        else:
            vmin = float(vmin)
            vmax = float(vmax)
            if clip:
                mask = ma.getmask(result)
                result = ma.array(np.clip(result.filled(vmax), vmin, vmax),
                                  mask=mask)

            # ma division is very slow; we can take a shortcut
            resdat = result.data

            #First scale to -1 to 1 range, than to from 0 to 1.
            resdat -= midpoint            
            resdat[resdat>0] /= abs(vmax - midpoint)            
            resdat[resdat<0] /= abs(vmin - midpoint)

            resdat /= 2.
            resdat += 0.5
            result = ma.array(resdat, mask=result.mask, copy=False)                

        if is_scalar:
            result = result[0]            
        return result

    def inverse(self, value):
        if not self.scaled():
            raise ValueError("Not invertible until scaled")
        vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint

        if cbook.iterable(value):
            val = ma.asarray(value)
            val = 2 * (val-0.5)  
            val[val>0]  *= abs(vmax - midpoint)
            val[val<0] *= abs(vmin - midpoint)
            val += midpoint
            return val
        else:
            val = 2 * (value - 0.5)
            if val < 0: 
                return  val*abs(vmin-midpoint) + midpoint
            else:
                return  val*abs(vmax-midpoint) + midpoint

Có thể sử dụng lớp này ngoài việc mở rộng quy mô log hoặc sym-log mà không cần phải tạo thêm lớp con không? Trường hợp sử dụng hiện tại của tôi đã sử dụng "norm = SymLogNorm (linthresh = 1)"
AnnanFay

Hoàn hảo, đây chính xác là những gì tôi đang tìm kiếm. Có lẽ bạn nên thêm một hình ảnh để chứng minh sự khác biệt? Ở đây điểm giữa được căn giữa trong thanh, trái với các bộ chuẩn hóa điểm giữa khác, nơi điểm giữa có thể được kéo về phía các điểm cực hạn.
nhã vào

18

Dễ nhất là chỉ sử dụng đối số vminvà (giả sử bạn đang làm việc với dữ liệu hình ảnh) thay vì phân lớp .vmaximshowmatplotlib.colors.Normalize

Ví dụ

import numpy as np
import matplotlib.pyplot as plt

data = np.random.random((10,10))
# Make the data range from about -5 to 10
data = 10 / 0.75 * (data - 0.25)

plt.imshow(data, vmin=-10, vmax=10)
plt.colorbar()

plt.show()

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


1
Có thể cập nhật ví dụ thành đường cong gaussian để chúng ta có thể thấy rõ hơn sự chuyển màu của màu sắc không?
Dat Chu,

3
Tôi không thích giải pháp này, vì nó không sử dụng toàn bộ dải màu có sẵn. Ngoài ra, tôi muốn một ví dụ về normalize để xây dựng một kiểu chuẩn hóa của symlog.
tillsten

2
@tillsten - Tôi nhầm lẫn, sau đó ... Bạn không thể sử dụng toàn bộ dải động của thanh màu nếu bạn muốn 0 ở giữa, phải không? Sau đó, bạn muốn một tỷ lệ phi tuyến tính? Một thang điểm cho các giá trị trên 0, một cho các giá trị dưới đây? Trong trường hợp đó, bạn sẽ cần phân lớp Normalize. Tôi sẽ thêm một ví dụ sau một chút (giả sử ai đó không đánh bại tôi ...).
Joe Kington

@Joe: Bạn nói đúng, nó không phải là tuyến tính (chính xác hơn là hai phần tuyến tính). Sử dụng vmin / vmax, dải màu cho các giá trị nhỏ hơn -5 không được sử dụng (điều này có ý nghĩa trong một số ứng dụng, nhưng không phải của tôi.).
tillsten

2
cho dữ liệu chung trong Z:vmax=abs(Z).max(), vmin=-abs(Z).max()
endolith

12

Ở đây tôi tạo một lớp con Normalizetheo sau là một ví dụ tối thiểu.

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt


class MidpointNormalize(mpl.colors.Normalize):
    def __init__(self, vmin, vmax, midpoint=0, clip=False):
        self.midpoint = midpoint
        mpl.colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        normalized_min = max(0, 1 / 2 * (1 - abs((self.midpoint - self.vmin) / (self.midpoint - self.vmax))))
        normalized_max = min(1, 1 / 2 * (1 + abs((self.vmax - self.midpoint) / (self.midpoint - self.vmin))))
        normalized_mid = 0.5
        x, y = [self.vmin, self.midpoint, self.vmax], [normalized_min, normalized_mid, normalized_max]
        return np.ma.masked_array(np.interp(value, x, y))


vals = np.array([[-5., 0], [5, 10]]) 
vmin = vals.min()
vmax = vals.max()

norm = MidpointNormalize(vmin=vmin, vmax=vmax, midpoint=0)
cmap = 'RdBu_r' 

plt.imshow(vals, cmap=cmap, norm=norm)
plt.colorbar()
plt.show()

Kết quả: pic-1

Ví dụ tương tự chỉ với dữ liệu tích cực vals = np.array([[1., 3], [6, 10]])

pic-2

Tính chất:

  • Điểm giữa lấy màu ở giữa.
  • Phạm vi trên và dưới được thay đổi tỷ lệ bằng cùng một phép biến đổi tuyến tính.
  • Chỉ màu xuất hiện trên hình ảnh mới được hiển thị trên thanh màu.
  • Có vẻ hoạt động tốt ngay cả khi vminlớn hơn midpoint(không thử nghiệm tất cả các trường hợp cạnh).

Giải pháp này được lấy cảm hứng từ một lớp có cùng tên từ trang này


3
Câu trả lời tốt nhất do tính đơn giản của nó. Các câu trả lời khác chỉ tốt nhất nếu bạn đã là một chuyên gia Matplotlib đang cố gắng trở thành một siêu chuyên gia. Hầu hết những người tìm kiếm câu trả lời matplotlib chỉ đang cố gắng hoàn thành một việc gì đó để về nhà với con chó và / hoặc gia đình của họ, và đối với họ câu trả lời này là tốt nhất.
sapo_cosmico

Giải pháp này có vẻ là tốt nhất thực sự, nhưng không hoạt động! Tôi vừa chạy tập lệnh thử nghiệm và kết quả là hoàn toàn khác (chỉ bao gồm các hình vuông màu xanh và không có màu đỏ). @icemtel, bạn có thể kiểm tra không? (bên cạnh vấn đề với thụt đầu dòng trên def __call__)
Filipe

Ok, tôi đã tìm thấy (các) vấn đề: các số trong phép tính normalized_minnormalized_maxđược coi là số nguyên. Chỉ cần đặt chúng là 0,0. Ngoài ra, để có được đầu ra chính xác của figure của bạn, tôi đã phải sử dụng vals = sp.array([[-5.0, 0.0], [5.0, 10.0]]) . Cảm ơn vì câu trả lời, dù sao!
Filipe

Xin chào @Filipe Tôi không thể tái tạo sự cố của bạn trên máy của tôi (Python 3.7, matplotlib 2.2.3 và tôi nghĩ cũng nên như vậy trên các phiên bản mới hơn). Bạn có phiên bản nào? Dù sao, tôi đã thực hiện một chỉnh sửa nhỏ tạo mảng kiểu float và khắc phục sự cố thụt lề. Cảm ơn vì đã chỉ nó ra
icemtel

Hmm .. Tôi vừa thử với python3 và nó cũng hoạt động. Nhưng tôi đang sử dụng python2.7. Cảm ơn bạn đã sửa chữa và cho câu trả lời. Nó rất đơn giản để sử dụng! :)
Filipe

5

Không chắc nếu bạn vẫn đang tìm kiếm câu trả lời. Đối với tôi, cố gắng phân lớp Normalizeđã không thành công. Vì vậy, tôi tập trung vào việc tạo thủ công một tập dữ liệu mới, đánh dấu và nhãn đánh dấu để có được hiệu ứng mà tôi nghĩ bạn đang hướng tới.

Tôi tìm thấy scalemô-đun trong matplotlib có một lớp được sử dụng để chuyển đổi các biểu đồ đường theo quy tắc 'nhật ký hệ thống', vì vậy tôi sử dụng lớp đó để chuyển đổi dữ liệu. Sau đó, tôi chia tỷ lệ dữ liệu để nó đi từ 0 đến 1 (những gì Normalizethường làm), nhưng tôi chia tỷ lệ số dương khác với số âm. Điều này là do vmax và vmin của bạn có thể không giống nhau, vì vậy .5 -> 1 có thể bao phủ một phạm vi dương lớn hơn .5 -> 0, phạm vi âm thì có. Tôi thấy dễ dàng hơn khi tạo một quy trình để tính toán các giá trị đánh dấu và nhãn.

Dưới đây là mã và một hình ví dụ.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mpl as mpl
import matplotlib.scale as scale

NDATA = 50
VMAX=10
VMIN=-5
LINTHRESH=1e-4

def makeTickLables(vmin,vmax,linthresh):
    """
    make two lists, one for the tick positions, and one for the labels
    at those positions. The number and placement of positive labels is 
    different from the negative labels.
    """
    nvpos = int(np.log10(vmax))-int(np.log10(linthresh))
    nvneg = int(np.log10(np.abs(vmin)))-int(np.log10(linthresh))+1
    ticks = []
    labels = []
    lavmin = (np.log10(np.abs(vmin)))
    lvmax = (np.log10(np.abs(vmax)))
    llinthres = int(np.log10(linthresh))
    # f(x) = mx+b
    # f(llinthres) = .5
    # f(lavmin) = 0
    m = .5/float(llinthres-lavmin)
    b = (.5-llinthres*m-lavmin*m)/2
    for itick in range(nvneg):
        labels.append(-1*float(pow(10,itick+llinthres)))
        ticks.append((b+(itick+llinthres)*m))
    # add vmin tick
    labels.append(vmin)
    ticks.append(b+(lavmin)*m)

    # f(x) = mx+b
    # f(llinthres) = .5
    # f(lvmax) = 1
    m = .5/float(lvmax-llinthres)
    b = m*(lvmax-2*llinthres) 
    for itick in range(1,nvpos):
        labels.append(float(pow(10,itick+llinthres)))
        ticks.append((b+(itick+llinthres)*m))
    # add vmax tick
    labels.append(vmax)
    ticks.append(b+(lvmax)*m)

    return ticks,labels


data = (VMAX-VMIN)*np.random.random((NDATA,NDATA))+VMIN

# define a scaler object that can transform to 'symlog'
scaler = scale.SymmetricalLogScale.SymmetricalLogTransform(10,LINTHRESH)
datas = scaler.transform(data)

# scale datas so that 0 is at .5
# so two seperate scales, one for positive and one for negative
data2 = np.where(np.greater(data,0),
                 .75+.25*datas/np.log10(VMAX),
                 .25+.25*(datas)/np.log10(np.abs(VMIN))
                 )

ticks,labels=makeTickLables(VMIN,VMAX,LINTHRESH)

cmap = mpl.cm.jet
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(data2,cmap=cmap,vmin=0,vmax=1)
cbar = plt.colorbar(im,ticks=ticks)
cbar.ax.set_yticklabels(labels)

fig.savefig('twoscales.png')

vmax = 10, vmin = -5 và linthresh = 1e-4

Vui lòng điều chỉnh "hằng số" (ví dụ VMAX) ở đầu tập lệnh để xác nhận rằng tập lệnh hoạt động tốt.


Cảm ơn bạn đã gợi ý, như đã thấy bên dưới, tôi đã thành công trong việc phân lớp con. Nhưng mã của bạn vẫn rất hữu ích để làm cho các nhãn đánh dấu đúng.
tillsten

4

Tôi đang sử dụng câu trả lời tuyệt vời từ Paul H, nhưng gặp sự cố vì một số dữ liệu của tôi nằm trong khoảng từ âm sang dương, trong khi các tập khác dao động từ 0 đến dương hoặc từ âm đến 0; trong cả hai trường hợp, tôi muốn 0 có màu trắng (điểm giữa của bản đồ màu mà tôi đang sử dụng). Với triển khai hiện tại, nếu midpointgiá trị của bạn bằng 1 hoặc 0, thì các ánh xạ ban đầu sẽ không bị ghi đè. Bạn có thể thấy điều đó trong hình sau: đồ thị trước khi chỉnh sửa Cột thứ 3 trông đúng, nhưng vùng màu xanh đậm ở cột thứ 2 và vùng màu đỏ đậm ở các cột còn lại đều được cho là màu trắng (thực tế giá trị dữ liệu của chúng là 0). Sử dụng bản sửa lỗi của tôi mang lại cho tôi: đồ thị sau khi chỉnh sửa Chức năng của tôi về cơ bản giống với chức năng của Paul H, với các chỉnh sửa của tôi ở đầu forvòng lặp:

def shiftedColorMap(cmap, min_val, max_val, name):
    '''Function to offset the "center" of a colormap. Useful for data with a negative min and positive max and you want the middle of the colormap's dynamic range to be at zero. Adapted from /programming/7404116/defining-the-midpoint-of-a-colormap-in-matplotlib

    Input
    -----
      cmap : The matplotlib colormap to be altered.
      start : Offset from lowest point in the colormap's range.
          Defaults to 0.0 (no lower ofset). Should be between
          0.0 and `midpoint`.
      midpoint : The new center of the colormap. Defaults to
          0.5 (no shift). Should be between 0.0 and 1.0. In
          general, this should be  1 - vmax/(vmax + abs(vmin))
          For example if your data range from -15.0 to +5.0 and
          you want the center of the colormap at 0.0, `midpoint`
          should be set to  1 - 5/(5 + 15)) or 0.75
      stop : Offset from highets point in the colormap's range.
          Defaults to 1.0 (no upper ofset). Should be between
          `midpoint` and 1.0.'''
    epsilon = 0.001
    start, stop = 0.0, 1.0
    min_val, max_val = min(0.0, min_val), max(0.0, max_val) # Edit #2
    midpoint = 1.0 - max_val/(max_val + abs(min_val))
    cdict = {'red': [], 'green': [], 'blue': [], 'alpha': []}
    # regular index to compute the colors
    reg_index = np.linspace(start, stop, 257)
    # shifted index to match the data
    shift_index = np.hstack([np.linspace(0.0, midpoint, 128, endpoint=False), np.linspace(midpoint, 1.0, 129, endpoint=True)])
    for ri, si in zip(reg_index, shift_index):
        if abs(si - midpoint) < epsilon:
            r, g, b, a = cmap(0.5) # 0.5 = original midpoint.
        else:
            r, g, b, a = cmap(ri)
        cdict['red'].append((si, r, r))
        cdict['green'].append((si, g, g))
        cdict['blue'].append((si, b, b))
        cdict['alpha'].append((si, a, a))
    newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
    plt.register_cmap(cmap=newcmap)
    return newcmap

CHỈNH SỬA: Tôi lại gặp phải vấn đề tương tự khi một số dữ liệu của tôi dao động từ giá trị dương nhỏ đến giá trị dương lớn hơn, trong đó các giá trị rất thấp được tô màu đỏ thay vì màu trắng. Tôi đã sửa nó bằng cách thêm dòng Edit #2trong đoạn mã trên.


Điều này trông đẹp, nhưng có vẻ như các lập luận đã thay đổi từ câu trả lời của Paul H (và các bình luận) ... Bạn có thể thêm một cuộc gọi ví dụ vào câu trả lời của bạn không?
Filipe

1

Nếu bạn không phiền khi tính ra tỷ lệ giữa vmin, vmax và 0, đây là một bản đồ tuyến tính khá cơ bản từ xanh lam sang trắng đến đỏ, đặt màu trắng theo tỷ lệ z:

def colormap(z):
    """custom colourmap for map plots"""

    cdict1 = {'red': ((0.0, 0.0, 0.0),
                      (z,   1.0, 1.0),
                      (1.0, 1.0, 1.0)),
              'green': ((0.0, 0.0, 0.0),
                        (z,   1.0, 1.0),
                        (1.0, 0.0, 0.0)),
              'blue': ((0.0, 1.0, 1.0),
                       (z,   1.0, 1.0),
                       (1.0, 0.0, 0.0))
              }

    return LinearSegmentedColormap('BlueRed1', cdict1)

Định dạng cdict khá đơn giản: các hàng là các điểm trong gradient được tạo: mục nhập đầu tiên là giá trị x (tỷ lệ dọc theo gradient từ 0 đến 1), mục thứ hai là giá trị kết thúc cho phân đoạn trước đó và thứ ba là giá trị bắt đầu cho phân đoạn tiếp theo - nếu bạn muốn chuyển màu mịn, hai giá trị sau luôn giống nhau. Xem tài liệu để biết thêm chi tiết.


1
Ngoài ra còn có tùy chọn để chỉ định trong các LinearSegmentedColormap.from_list()bộ giá trị (val,color)và chuyển chúng dưới dạng danh sách cho colorđối số của phương thức này ở đâu val0=0<val1<...<valN==1.
maurizio

0

Tôi đã gặp sự cố tương tự, nhưng tôi muốn giá trị cao nhất có màu đỏ hoàn toàn và cắt các giá trị thấp của màu xanh lam, làm cho nó về cơ bản giống như phần dưới cùng của thanh màu đã bị cắt. Điều này đã làm việc cho tôi (bao gồm tính minh bạch tùy chọn):

def shift_zero_bwr_colormap(z: float, transparent: bool = True):
    """shifted bwr colormap"""
    if (z < 0) or (z > 1):
        raise ValueError('z must be between 0 and 1')

    cdict1 = {'red': ((0.0, max(-2*z+1, 0), max(-2*z+1, 0)),
                      (z,   1.0, 1.0),
                      (1.0, 1.0, 1.0)),

              'green': ((0.0, max(-2*z+1, 0), max(-2*z+1, 0)),
                        (z,   1.0, 1.0),
                        (1.0, max(2*z-1,0),  max(2*z-1,0))),

              'blue': ((0.0, 1.0, 1.0),
                       (z,   1.0, 1.0),
                       (1.0, max(2*z-1,0), max(2*z-1,0))),
              }
    if transparent:
        cdict1['alpha'] = ((0.0, 1-max(-2*z+1, 0), 1-max(-2*z+1, 0)),
                           (z,   0.0, 0.0),
                           (1.0, 1-max(2*z-1,0),  1-max(2*z-1,0)))

    return LinearSegmentedColormap('shifted_rwb', cdict1)

cmap =  shift_zero_bwr_colormap(.3)

x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 5 + 5
plt.plot([0, 10*np.pi], [0, 20*np.pi], color='c', lw=20, zorder=-3)
plt.imshow(Z, interpolation='nearest', origin='lower', cmap=cmap)
plt.colorbar()
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.