Определение середины палитры в matplotlib

Я хочу установить среднюю точку цветовой карты, то есть мои данные идут от -5 до 10, и я хочу, чтобы ноль был средней точкой. Я думаю, что способ сделать это - создать подклассы normalize и использовать норму, но я не нашел ни одного примера, и мне непонятно, что именно я должен реализовать?


person tillsten    schedule 13.09.2011    source источник
comment
это называется расходящейся или биполярной цветовой картой, когда важна центральная точка карты, а данные идут выше и ниже этой точки. sandia.gov/~kmorel/documents/ColorMaps   -  person endolith    schedule 31.05.2012
comment
Все ответы в этой ветке кажутся довольно сложными. Простое в использовании решение показано в этом отличном ответе, который тем временем также включен в раздел документации matplotlib. Пользовательская нормализация: два линейных диапазона.   -  person ImportanceOfBeingErnest    schedule 03.02.2018


Ответы (9)


Обратите внимание, что в 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 класс назывался DivergingNorm.

person macKaiver    schedule 21.06.2019
comment
Это выглядит интересно, но кажется, что это нужно использовать для преобразования данных перед построением графика. Легенда цветовой шкалы будет относиться к преобразованным данным, а не к исходным. - person bli; 09.08.2019
comment
@bli это не так. norm выполняет нормализацию вашего изображения. norms идут рука об руку с палитрой. - person Paul H; 26.09.2019
comment
Досадно, что это устарело с версии 3.2 без документации о том, как его заменить: matplotlib.org/3.2.0/api/_as_gen/ - person daknowles; 02.06.2020
comment
Да, документы неясны. Думаю, его переименовали в TwoSlopeNorm: matplotlib.org/3.2.0/api/_as_gen/ - person macKaiver; 08.06.2020
comment
Обратите внимание на опечатку в ответе TwoSlope s Norm должно быть TwoSlopeNorm - person Chris; 07.04.2021

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

Функция

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

Результаты примера:

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

person Paul H    schedule 11.12.2013
comment
Большое спасибо за ваш потрясающий вклад! Однако код не мог одновременно обрезать и сдвигать одну и ту же цветовую карту, и ваши инструкции были немного неточными и вводящими в заблуждение. Теперь я исправил это и взял на себя смелость отредактировать ваш пост. Кроме того, я включил его в одну из моих личных библиотек , и добавил вас в качестве автора. Я надеюсь, ты не против. - person TheChymera; 25.04.2014
comment
@TheChymera палитра в правом нижнем углу была обрезана и повторно центрирована. Не стесняйтесь использовать это по своему усмотрению. - person Paul H; 25.04.2014
comment
Да, но, к сожалению, это выглядит примерно как совпадение. Если start и stop не равны 0 и 1 соответственно, после того, как вы сделаете reg_index = np.linspace(start, stop, 257), вы больше не сможете предполагать, что значение 129 является средней точкой исходного cmap, поэтому полное изменение масштаба не имеет смысла при кадрировании. Кроме того, start должно быть от 0 до 0,5 и stop от 0,5 до 1, а не от 0 до 1, как вы указываете. - person TheChymera; 26.04.2014
comment
@TheChymera Я попробовал вашу версию, и у меня возникли две мысли по этому поводу. 1) мне кажется, что все созданные вами индексы имеют длину 257, а в matplotlib по умолчанию установлено 256, я полагаю? 2) предположим, что мои данные находятся в диапазоне от -1 до 1000, в нем преобладают положительные значения, и поэтому больше уровней / слоев должно идти в положительную ветвь. Но ваша функция дает 128 уровней как для отрицательных, так и для положительных сторон, поэтому, я думаю, было бы справедливее разделить уровни неравномерно. - person Jason; 25.10.2015
comment
Это отличное решение, но оно терпит неудачу, если midpoint данных равно 0 или 1. См. Мой ответ ниже для простого решения этой проблемы. - person DaveTheScientist; 21.06.2017
comment
Я использую это для построения тепловых карт коэффициентов корреляции. Для 99,99% моих данных есть как положительные, так и отрицательные значения cc с асимметричными диапазонами. Это очень полезно! - person MyCarta; 26.09.2019

Проще всего использовать аргументы vmin и vmax для imshow (при условии, что вы работаете с данными изображения), а не создавать подклассы matplotlib.colors.Normalize.

E.g.

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

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

person Joe Kington    schedule 13.09.2011
comment
Можно ли обновить пример до гауссовой кривой, чтобы мы могли лучше видеть градацию цвета? - person Dat Chu; 13.09.2011
comment
Мне это решение не нравится, потому что оно не использует весь динамический диапазон доступных цветов. Также я хотел бы привести пример нормализации для создания символьной нормализации. - person tillsten; 13.09.2011
comment
@tillsten - Я запутался, тогда ... Вы не можете использовать полный динамический диапазон шкалы палитры, если хотите 0 посередине, верно? Значит, вам нужна нелинейная шкала? Одна шкала для значений выше 0, одна для значений ниже? В этом случае, да, вам нужно создать подкласс Normalize. Я добавлю пример чуть позже (при условии, что кто-то другой меня не опередит ...). - person Joe Kington; 13.09.2011
comment
@Joe: Вы правы, это не линейно (точнее, две линейные части). При использовании vmin / vmax цветовой диапазон для значений меньше -5 не используется (что имеет смысл в некоторых приложениях, но не в моем). - person tillsten; 13.09.2011
comment
в вашем примере должна использоваться какая-то плавно изменяющаяся функция, а не случайные данные, а высокие точки должны быть дальше от нуля, чем нижние точки, чтобы показать, как вы переместили центр, и вы не должны использовать цветовую карту струи в значительной степени что-нибудь когда-либо. jwave.vt.edu/~rkriz/Projects/create_color_table/color_07. pdf - person endolith; 31.05.2012
comment
для общих данных в Z: vmax=abs(Z).max(), vmin=-abs(Z).max() - person endolith; 04.03.2013

Вот подкласс решения Normalize. Использовать это

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
person tillsten    schedule 12.10.2011
comment
Можно ли использовать этот класс в дополнение к масштабированию журнала или символьного журнала без необходимости создания дополнительных подклассов? В моем текущем варианте использования уже используется norm = SymLogNorm (linthresh = 1) - person AnnanFay; 08.11.2016
comment
Отлично, это именно то, что я искал. Может, стоит добавить картинку, чтобы продемонстрировать разницу? Здесь средняя точка находится в центре полосы, в отличие от других нормализаторов средней точки, где среднюю точку можно перетащить к конечностям. - person gaborous; 02.02.2018

Здесь я создаю подкласс 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()

Результат:  pic-1

Тот же пример только с положительными данными vals = np.array([[1., 3], [6, 10]])

pic-2

Характеристики:

  • Средняя точка приобретает средний цвет.
  • Верхний и нижний диапазоны масштабируются с помощью одного и того же линейного преобразования.
  • На палитре цветов отображается только тот цвет, который изображен на картинке.
  • Кажется, работает нормально, даже если vmin больше midpoint (хотя не проверял все крайние случаи).

Это решение основано на классе с таким же именем с этой страницы

person icemtel    schedule 24.04.2018
comment
Лучший ответ благодаря своей простоте. Другие ответы лучше всего подходят только в том случае, если вы уже являетесь экспертом Matplotlib, пытающимся стать супер-экспертом. Большинство искателей ответов на matplotlib просто пытаются что-то сделать, чтобы вернуться домой к своей собаке и / или семье, и для них этот ответ является лучшим. - person sapo_cosmico; 07.11.2018
comment
Это решение действительно кажется лучшим, но не работает! Я только что запустил тестовый сценарий, и результат совершенно другой (включая только синие квадраты, а не красный). @icemtel, вы можете проверить? (помимо проблемы с отступом на def __call__) - person Filipe; 20.01.2019
comment
Хорошо, я обнаружил проблему (ы): числа при вычислении normalized_min и normalized_max принимаются как целые числа. Просто поставьте их как 0,0. Кроме того, чтобы получить правильный результат вашей фигуры, мне пришлось использовать vals = sp.array([[-5.0, 0.0], [5.0, 10.0]]) . В любом случае спасибо за ответ! - person Filipe; 20.01.2019
comment
Привет @Filipe, я не могу воспроизвести вашу проблему на своем компьютере (Python 3.7, matplotlib 2.2.3, и я думаю, что в более новых версиях должно быть то же самое). Какая у тебя версия? В любом случае, я сделал небольшую правку, сделав массив типа float, и исправил проблему с отступом. Спасибо, что указали на это - person icemtel; 21.01.2019
comment
Хм .. Я только что пробовал с python3, и он тоже работает. Но я использую python2.7. Спасибо за исправление и за ответ. Пользоваться очень просто! :) - person Filipe; 22.01.2019

Не уверен, что вы все еще ищете ответ. Для меня попытка создать подкласс Normalize не увенчалась успехом. Поэтому я сосредоточился на ручном создании нового набора данных, меток и меток, чтобы получить эффект, к которому, как мне кажется, вы стремитесь.

Я нашел модуль scale в 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 и linthresh = 1e-4

Не стесняйтесь настраивать «константы» (например, VMAX) в верхней части скрипта, чтобы убедиться, что он ведет себя хорошо.

person Yann    schedule 12.10.2011
comment
Спасибо за ваше предложение, как показано ниже, я успешно создал подклассы. Но ваш код по-прежнему очень полезен для создания правильных меток. - person tillsten; 13.10.2011

Я использовал отличный ответ Пола Х, но столкнулся с проблемой, потому что некоторые из моих данных варьировались от отрицательных до положительных, в то время как другие наборы варьировались от 0 до положительных или от отрицательных до 0; в любом случае я хотел, чтобы 0 был окрашен в белый цвет (средняя точка цветовой карты, которую я использую). В существующей реализации, если ваше значение midpoint равно 1 или 0, исходные сопоставления не перезаписывались. Вы можете увидеть это на следующем рисунке:  графики перед редактированием Третий столбец выглядит правильно, но темно-синяя область во втором столбце и темно-красная область в остальных столбцах должны быть белыми. (их значения данных фактически равны 0). Использование моего исправления дает мне:  графики после редактирования Моя функция практически такая же, как и у Пола Х, с моими изменениями в начале цикла 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 в код выше.

person DaveTheScientist    schedule 21.06.2017
comment
Это выглядит красиво, но кажется, что аргументы изменились по сравнению с ответом Пола Х (и комментариями) ... Можете ли вы добавить пример вызова к своему ответу? - person Filipe; 20.01.2019
comment
W содержит мои данные. Мне удалось запустить это с помощью этих двух строк: cm = shiftedColorMap(cmap=plt.get_cmap("coolwarm"), min_val=W.min(), max_val=W.max(), name="coolwarm"), затем pcolormesh(W, cmap=cm) Тем не менее, я не рекомендую использовать эту функцию. Он дополнительно смещает центр палитры каждый раз при вызове. Я рекомендую использовать TwoSlopeNorm, как описано в stackoverflow.com/a/56699813/1273751 - person Homero Esmeraldo; 08.07.2021

Если вы не возражаете против определения соотношения между vmin, vmax и нулем, это довольно простая линейная карта от синего к белому и к красному, которая устанавливает белый цвет в соответствии с соотношением 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), вторая - конечное значение для предыдущего сегмента и третий - начальное значение для следующего сегмента - если вы хотите плавные градиенты, последние два всегда одинаковы. Дополнительные сведения см. в документации.

person naught101    schedule 21.06.2016
comment
Также есть возможность указать в кортежах LinearSegmentedColormap.from_list() (val,color) и передать их как список в color аргумент этого метода, где val0=0<val1<...<valN==1. - person maurizio; 07.09.2016

У меня была аналогичная проблема, но я хотел, чтобы максимальное значение было полностью красным, а низкие значения синего отсекали, чтобы это выглядело по существу так, как будто нижняя часть шкалы цветов была обрезана. Это сработало для меня (включая дополнительную прозрачность):

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()
person ben.dichter    schedule 09.03.2017