Creando mapas de colores en Matplotlib #

Matplotlib tiene una serie de mapas de colores integrados accesibles a través de matplotlib.colormaps. También hay bibliotecas externas como palettable que tienen muchos mapas de colores adicionales.

Sin embargo, a menudo queremos crear o manipular mapas de colores en Matplotlib. Esto se puede hacer usando la clase ListedColormapo LinearSegmentedColormap. Visto desde el exterior, ambas clases de mapa de colores asignan valores entre 0 y 1 a un grupo de colores. Sin embargo, existen ligeras diferencias, algunas de las cuales se muestran a continuación.

Antes de crear o manipular mapas de colores manualmente, primero veamos cómo podemos obtener mapas de colores y sus colores a partir de clases de mapas de colores existentes.

Obtener mapas de colores y acceder a sus valores #

Primero, obtener un mapa de colores con nombre, la mayoría de los cuales se enumeran en Elección de mapas de colores en Matplotlib , se puede hacer usando matplotlib.colormaps, que devuelve un objeto de mapa de colores. La longitud de la lista de colores utilizados internamente para definir el mapa de colores se puede ajustar a través de Colormap.resampled. A continuación, usamos un valor modesto de 8, por lo que no hay muchos valores para mirar.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.colors import ListedColormap, LinearSegmentedColormap

viridis = mpl.colormaps['viridis'].resampled(8)

El objeto viridises invocable, que cuando se le pasa un valor flotante entre 0 y 1 devuelve un valor RGBA del mapa de colores:

print(viridis(0.56))
(0.122312, 0.633153, 0.530398, 1.0)

ListedColormap #

ListedColormapalmacenan sus valores de color en un .colorsatributo. Se puede acceder directamente a la lista de colores que componen el mapa de colores usando la colorspropiedad, o indirectamente llamando viridiscon una matriz de valores que coincidan con la longitud del mapa de colores. Tenga en cuenta que la lista devuelta tiene la forma de una matriz RGBA Nx4, donde N es la longitud del mapa de colores.

print('viridis.colors', viridis.colors)
print('viridis(range(8))', viridis(range(8)))
print('viridis(np.linspace(0, 1, 8))', viridis(np.linspace(0, 1, 8)))
viridis.colors [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]
viridis(range(8)) [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]
viridis(np.linspace(0, 1, 8)) [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]

El mapa de colores es una tabla de búsqueda, por lo que "sobremuestrear" el mapa de colores devuelve la interpolación del vecino más cercano (tenga en cuenta los colores repetidos en la lista a continuación)

print('viridis(np.linspace(0, 1, 12))', viridis(np.linspace(0, 1, 12)))
viridis(np.linspace(0, 1, 12)) [[0.267004 0.004874 0.329415 1.      ]
 [0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]

LinearSegmentedColormap #

LinearSegmentedColormaps no tienen un .colorsatributo. Sin embargo, todavía se puede llamar al mapa de colores con una matriz de enteros o con una matriz flotante entre 0 y 1.

copper = mpl.colormaps['copper'].resampled(8)

print('copper(range(8))', copper(range(8)))
print('copper(np.linspace(0, 1, 8))', copper(np.linspace(0, 1, 8)))
copper(range(8)) [[0.         0.         0.         1.        ]
 [0.17647055 0.1116     0.07107143 1.        ]
 [0.35294109 0.2232     0.14214286 1.        ]
 [0.52941164 0.3348     0.21321429 1.        ]
 [0.70588219 0.4464     0.28428571 1.        ]
 [0.88235273 0.558      0.35535714 1.        ]
 [1.         0.6696     0.42642857 1.        ]
 [1.         0.7812     0.4975     1.        ]]
copper(np.linspace(0, 1, 8)) [[0.         0.         0.         1.        ]
 [0.17647055 0.1116     0.07107143 1.        ]
 [0.35294109 0.2232     0.14214286 1.        ]
 [0.52941164 0.3348     0.21321429 1.        ]
 [0.70588219 0.4464     0.28428571 1.        ]
 [0.88235273 0.558      0.35535714 1.        ]
 [1.         0.6696     0.42642857 1.        ]
 [1.         0.7812     0.4975     1.        ]]

Creación de mapas de colores listados #

La creación de un mapa de colores es esencialmente la operación inversa de la anterior, donde proporcionamos una lista o matriz de especificaciones de color ListedColormappara crear un nuevo mapa de colores.

Antes de continuar con el tutorial, definamos una función auxiliar que tome uno o más mapas de colores como entrada, cree algunos datos aleatorios y aplique los mapas de colores a un gráfico de imagen de ese conjunto de datos.

def plot_examples(colormaps):
    """
    Helper function to plot data with associated colormap.
    """
    np.random.seed(19680801)
    data = np.random.randn(30, 30)
    n = len(colormaps)
    fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
                            constrained_layout=True, squeeze=False)
    for [ax, cmap] in zip(axs.flat, colormaps):
        psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
        fig.colorbar(psm, ax=ax)
    plt.show()

En el caso más simple, podríamos escribir una lista de nombres de colores para crear un mapa de colores a partir de ellos.

cmap = ListedColormap(["darkorange", "gold", "lawngreen", "lightseagreen"])
plot_examples([cmap])
manipulación del mapa de colores

De hecho, esa lista puede contener cualquier especificación de color Matplotlib válida . Particularmente útiles para crear mapas de colores personalizados son las matrices numpy Nx4. Porque con la variedad de operaciones numéricas que podemos hacer en una matriz de este tipo, la carpintería de nuevos mapas de colores a partir de mapas de colores existentes se vuelve bastante sencilla.

Por ejemplo, supongamos que queremos hacer que las primeras 25 entradas de un mapa de colores "viridis" de 256 longitudes sean rosas por alguna razón:

viridis = mpl.colormaps['viridis'].resampled(256)
newcolors = viridis(np.linspace(0, 1, 256))
pink = np.array([248/256, 24/256, 148/256, 1])
newcolors[:25, :] = pink
newcmp = ListedColormap(newcolors)

plot_examples([viridis, newcmp])
manipulación del mapa de colores

Podemos reducir el rango dinámico de un mapa de colores; aquí elegimos la mitad central del mapa de colores. Tenga en cuenta, sin embargo, que debido a que viridis es un mapa de colores listado, terminaremos con 128 valores discretos en lugar de los 256 valores que estaban en el mapa de colores original. Este método no interpola en el espacio de color para agregar nuevos colores.

viridis_big = mpl.colormaps['viridis']
newcmp = ListedColormap(viridis_big(np.linspace(0.25, 0.75, 128)))
plot_examples([viridis, newcmp])
manipulación del mapa de colores

y podemos concatenar fácilmente dos mapas de colores:

top = mpl.colormaps['Oranges_r'].resampled(128)
bottom = mpl.colormaps['Blues'].resampled(128)

newcolors = np.vstack((top(np.linspace(0, 1, 128)),
                       bottom(np.linspace(0, 1, 128))))
newcmp = ListedColormap(newcolors, name='OrangeBlue')
plot_examples([viridis, newcmp])
manipulación del mapa de colores

Por supuesto, no necesitamos comenzar desde un mapa de colores con nombre, solo necesitamos crear la matriz Nx4 para pasar a ListedColormap. Aquí creamos un mapa de colores que va del marrón (RGB: 90, 40, 40) al blanco (RGB: 255, 255, 255).

N = 256
vals = np.ones((N, 4))
vals[:, 0] = np.linspace(90/256, 1, N)
vals[:, 1] = np.linspace(40/256, 1, N)
vals[:, 2] = np.linspace(40/256, 1, N)
newcmp = ListedColormap(vals)
plot_examples([viridis, newcmp])
manipulación del mapa de colores

Creación de mapas de color segmentados lineales #

La LinearSegmentedColormapclase especifica mapas de colores utilizando puntos de anclaje entre los que se interpolan los valores RGB(A).

El formato para especificar estos mapas de colores permite discontinuidades en los puntos de anclaje. Cada punto de ancla se especifica como una fila en una matriz de la forma , donde es el ancla y y son los valores del color a ambos lados del punto de ancla.[x[i] yleft[i] yright[i]]x[i]yleft[i]yright[i]

Si no hay discontinuidades, entonces :yleft[i] == yright[i]

cdict = {'red':   [[0.0,  0.0, 0.0],
                   [0.5,  1.0, 1.0],
                   [1.0,  1.0, 1.0]],
         'green': [[0.0,  0.0, 0.0],
                   [0.25, 0.0, 0.0],
                   [0.75, 1.0, 1.0],
                   [1.0,  1.0, 1.0]],
         'blue':  [[0.0,  0.0, 0.0],
                   [0.5,  0.0, 0.0],
                   [1.0,  1.0, 1.0]]}


def plot_linearmap(cdict):
    newcmp = LinearSegmentedColormap('testCmap', segmentdata=cdict, N=256)
    rgba = newcmp(np.linspace(0, 1, 256))
    fig, ax = plt.subplots(figsize=(4, 3), constrained_layout=True)
    col = ['r', 'g', 'b']
    for xx in [0.25, 0.5, 0.75]:
        ax.axvline(xx, color='0.7', linestyle='--')
    for i in range(3):
        ax.plot(np.arange(256)/256, rgba[:, i], color=col[i])
    ax.set_xlabel('index')
    ax.set_ylabel('RGB')
    plt.show()

plot_linearmap(cdict)
manipulación del mapa de colores

Para hacer una discontinuidad en un punto de anclaje, la tercera columna es diferente a la segunda. La matriz para cada uno de "rojo", "verde", "azul" y, opcionalmente, "alfa" se configura como:

cdict['red'] = [...
                [x[i]      yleft[i]     yright[i]],
                [x[i+1]    yleft[i+1]   yright[i+1]],
               ...]

y para los valores pasados ​​al mapa de colores entre x[i]y x[i+1], la interpolación es entre yright[i]y yleft[i+1].

En el siguiente ejemplo, hay una discontinuidad en rojo en 0,5. La interpolación entre 0 y 0,5 va de 0,3 a 1, y entre 0,5 y 1 va de 0,9 a 1. Tenga en cuenta que , y son superfluos para la interpolación porque (es decir, ) es el valor a la izquierda de 0, y ( es decir, ) es el valor a la derecha de 1, que están fuera del dominio de asignación de colores.red[0, 1]red[2, 2]red[0, 1]yleft[0]red[2, 2]yright[2]

cdict['red'] = [[0.0,  0.0, 0.3],
                [0.5,  1.0, 0.9],
                [1.0,  1.0, 1.0]]
plot_linearmap(cdict)
manipulación del mapa de colores

Crear directamente un mapa de colores segmentado a partir de una lista #

El enfoque descrito anteriormente es muy versátil, pero ciertamente un poco engorroso de implementar. Para algunos casos básicos, el uso de LinearSegmentedColormap.from_listpuede ser más fácil. Esto crea un mapa de colores segmentado con espacios iguales a partir de una lista de colores suministrada.

colors = ["darkorange", "gold", "lawngreen", "lightseagreen"]
cmap1 = LinearSegmentedColormap.from_list("mycmap", colors)

Si lo desea, los nodos del mapa de colores se pueden dar como números entre 0 y 1. Por ejemplo, se podría hacer que la parte rojiza ocupe más espacio en el mapa de colores.

nodes = [0.0, 0.4, 0.8, 1.0]
cmap2 = LinearSegmentedColormap.from_list("mycmap", list(zip(nodes, colors)))

plot_examples([cmap1, cmap2])
manipulación del mapa de colores

Tiempo total de ejecución del script: (0 minutos 4.802 segundos)

Galería generada por Sphinx-Gallery