programing

matplotlib에서 색상표의 중간점 정의

procenter 2023. 7. 29. 13:18
반응형

matplotlib에서 색상표의 중간점 정의

색상표의 중간점을 설정하려고 합니다. 즉, 데이터가 -5에서 10으로 이동하고 0을 중간점으로 설정합니다.제 생각에 그것을 하는 방법은 정규화를 하위 분류하고 표준을 사용하는 것이라고 생각합니다만, 저는 어떤 예도 찾지 못했고 제가 정확히 무엇을 구현해야 하는지 모르겠습니다.

게임이 늦었다는 것은 알지만, 저는 이 과정을 거치고 아마 하위 분류가 정상화되는 것보다 덜 강력하지만 훨씬 더 간단한 해결책을 생각해냈습니다.여기서 후손들에게 나눠주는 게 좋을 것 같아서요.

함수

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

예문

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

예제 결과:

enter image description here

matplotlib 버전 3.2+에서 TwoSlopeNorm 클래스가 추가되었습니다.당신의 사용 사례를 커버하는 것 같습니다.다음과 같이 사용할 수 있습니다.

from matplotlib import colors
divnorm=colors.TwoSlopeNorm(vmin=-5., vcenter=0., vmax=10)
pcolormesh(your_data, cmap="coolwarm", norm=divnorm)

matplotlib 3.1에서 클래스는 Diverging Norm이라고 불립니다.

그냥 사용하는 것이 가장 쉽습니다.vmin그리고.vmax 입니다.imshow으로 간주) )가 아닌matplotlib.colors.Normalize.

예.

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

enter image description here

다음은 정규화 하위 분류 솔루션입니다.사용하기

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

클래스는 다음과 같습니다.

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

여기서 다음의 하위 클래스를 만듭니다.Normalize그 뒤에 최소한의 예가 있습니다.

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

결과:

의 데이터만 한 예 양 데 터 있 동 한 예 제vals = np.array([[1., 3], [6, 10]])

pic-2

속성:

  • 중간점이 중간색이 됩니다.
  • 상한 및 하한 범위는 동일한 선형 변환에 의해 조정됩니다.
  • 사진에 표시되는 색상만 색상 표시줄에 표시됩니다.
  • 그래도 잘 작동하는 것 같습니다.vmin보다 큽니다.midpoint(그러나 모든 에지 사례를 테스트하지는 않았습니다.)

이 솔루션은 이 페이지의 동일한 이름을 가진 클래스에서 영감을 받았습니다.

당신이 여전히 답을 찾고 있는지 확실하지 않습니다.저는 서브클래스를 하려고 노력하고 있습니다.Normalize실패했습니다.그래서 저는 여러분이 목표로 하는 효과를 얻기 위해 수동으로 새로운 데이터 세트, 눈금 및 눈금 레이블을 만드는 데 중점을 두었습니다.

나는 찾았습니다.scale in 에는 '선하는 데 이 클래스를 합니다. matplotlib의 'syslog'와 같은 클래스가 .그런 다음 데이터의 크기를 0에서 1로 조정합니다.Normalize보통 그래요), 하지만 저는 양수와 음수의 배율을 다르게 조정합니다.이는 vmax와 vmin이 같지 않을 수 있으므로 .5 -> 1이 .5 -> 0보다 큰 양의 범위를 커버할 수 있고 음의 범위가 적용될 수 있기 때문입니다.눈금과 레이블 값을 계산하는 루틴을 만드는 것이 더 쉬웠습니다.

아래는 코드와 예제 그림입니다.

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 and linthresh=1e-4

자유롭게 "정수"를 조정할 수 있습니다(예:VMAX을합니다.을 클릭하여 정상적으로 동작하는지 확인합니다.

저는 Paul H의 훌륭한 답변을 사용하고 있었지만, 제 데이터 중 일부는 음에서 양으로, 다른 세트는 0에서 양으로 또는 음에서 0으로 범위가 넓기 때문에 문제가 발생했습니다. 두 경우 모두 0을 흰색(제가 사용하는 컬러 맵의 중간 지점)으로 색칠하기를 원했습니다.기존 구현의 경우,midpoint값이 1 또는 0과 같습니다. 원래 매핑을 덮어쓰지 않았습니다.다음 그림에서 확인할 수 있습니다.graphs before edit 세 번째 열은 올바른 것처럼 보이지만 두 번째 열의 진한 파란색 영역과 나머지 열의 진한 빨간색 영역은 모두 흰색이어야 합니다(데이터 값은 사실 0).내 수정을 사용하는 것은 나에게 다음과 같습니다: 내 기능은 본질적으로 폴 H의 그것과 같고, 내 편집은 처음에 있습니다.for슬롯:슬라이드:

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 https://stackoverflow.com/questions/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

편집: 제 데이터 중 일부가 작은 양의 값에서 더 큰 양의 값까지 다양할 때, 저는 비슷한 문제에 다시 부딪혔습니다. 매우 낮은 값은 흰색이 아닌 빨간색으로 칠해져 있었습니다.라인을 추가해서 고쳤습니다.Edit #2위의 암호로

matplotlib 버전 3.4 이상에서는 새로운 중심 노름을 사용하는 것이 가장 간단한 솔루션입니다.

중심 노름과 분기 색상표 중 하나를 사용한 예제:

import matplotlib.pyplot as plt
import matplotlib as mpl

plt.pcolormesh(data_to_plot, norm=mpl.colors.CenteredNorm(), cmap='coolwarm')

하게말면하순단,면▁being하말,CenteredNorm대칭이므로 데이터가 -5에서 10으로 넘어가면 색상표가 -10에서 10으로 늘어납니다.중앙의 양쪽에 다른 매핑을 적용하여 색상 맵이 -5에서 10 사이가 되도록 하려면 @macKaver의 답변에 설명된 TwoSlopeNorm을 사용합니다.

vmax의 비율을에서 흰색에서꽤입니다. , vmax 및 0 사 을 율 계 해 산 도 에 빨 간 색 서 으 시 로 맵 니 입 이 흰 다 선 기 는 형 지 어 본 색 다 면 비 으 찮 색 괜 란 파 의 이 ▁if ▁map ▁from , 간 ▁zero 니 ▁between ▁white ▁v ▁according 다 맵 ▁this 비율에 따라 흰색이 설정됩니다.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)

cdict 형식은 매우 간단합니다. 행은 생성되는 그라데이션의 점입니다. 첫 번째 항목은 x 값(0부터 1까지의 그라데이션을 따르는 비율)이고, 두 번째 항목은 이전 세그먼트의 끝 값이고, 세 번째 항목은 다음 세그먼트의 시작 값입니다. 매끄러운 그라데이션을 원하는 경우 후자 두 개는 항상 동일합니다.자세한 내용은 문서를 참조하십시오.

저도 비슷한 문제가 있었지만, 저는 가장 높은 값이 완전한 빨간색이고, 낮은 값의 파란색을 잘라내서, 본질적으로 컬러바의 바닥이 잘린 것처럼 보이게 하고 싶었습니다.이것은 저에게 효과가 있었습니다(선택 사항인 투명성 포함).

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

언급URL : https://stackoverflow.com/questions/7404116/defining-the-midpoint-of-a-colormap-in-matplotlib

반응형