非线性颜色映射,matplotlib
有没有什么颜色映射或者简单的方法,可以把matplotlib的颜色映射调整一下,让0.5附近的颜色范围更大,而极端值的颜色范围更小呢?我正在创建一些子图,其中有一个图的颜色值大约是其他图的10倍,所以这个图的值占据了主导,导致其他图看起来都差不多。举个简单的例子,比如我们有:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1,10,10)
y = np.linspace(1,10,10)
t1 = np.random.normal(2,0.3,10)
t2 = np.random.normal(9,0.01,10)
t2_max = max(t2)
plt.figure(figsize=(22.0, 15.50))
p = plt.subplot(1,2,1)
colors = plt.cm.Accent(t1/t2_max)
p.scatter(x, y, edgecolors=colors, s=15, linewidths=4)
p = plt.subplot(1,2,2)
colors = plt.cm.Accent(t2/t2_max)
p.scatter(x, y, edgecolors=colors, s=15, linewidths=4)
plt.subplots_adjust(left=0.2)
cbar_ax = plt.axes([0.10, 0.15, 0.05, 0.7])
sm = plt.cm.ScalarMappable(cmap=plt.cm.Accent, norm=plt.Normalize(vmin=0, vmax=t2_max))
sm._A = []
cbar = plt.colorbar(sm,cax=cbar_ax)
plt.show()
在t1中变化很大,但在t2中变化却不明显,因为t2的值太高了。我想要的是一个颜色映射,它能在t1的平均值附近提供更大的颜色渐变,而不改变数据本身。我在这里找到一个解决方案 http://protracted-matter.blogspot.co.nz/2012/08/nonlinear-colormap-in-matplotlib.html,但我无法让它在我的散点图中工作。
编辑: 根据下面的回答,这个类可以修改以接受负数和固定边界。
import numpy as np
import matplotlib.pyplot as plt
x = y = np.linspace(1, 10, 10)
t1mean, t2mean = -6, 9
sigma1, sigma2 = .3, .01
t1 = np.random.normal(t1mean, sigma1, 10)
t2 = np.random.normal(t2mean, sigma2, 10)
class nlcmap(object):
def __init__(self, cmap, levels):
self.cmap = cmap
self.N = cmap.N
self.monochrome = self.cmap.monochrome
self.levels = np.asarray(levels, dtype='float64')
self._x = self.levels
self.levmax = self.levels.max()
self.levmin = self.levels.min()
self.transformed_levels = np.linspace(self.levmin, self.levmax,
len(self.levels))
def __call__(self, xi, alpha=1.0, **kw):
yi = np.interp(xi, self._x, self.transformed_levels)
return self.cmap(yi / (self.levmax-self.levmin)+0.5, alpha)
tmax = 10
tmin = -10
#the choice of the levels depends on the data:
levels = np.concatenate((
[tmin, tmax],
np.linspace(t1mean - 2 * sigma1, t1mean + 2 * sigma1, 5),
np.linspace(t2mean - 2 * sigma2, t2mean + 2 * sigma2, 5),
))
levels = levels[levels <= tmax]
levels.sort()
print levels
cmap_nonlin = nlcmap(plt.cm.jet, levels)
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.scatter(x, y, edgecolors=cmap_nonlin(t1), s=15, linewidths=4)
ax2.scatter(x, y, edgecolors=cmap_nonlin(t2), s=15, linewidths=4)
fig.subplots_adjust(left=.25)
cbar_ax = fig.add_axes([0.10, 0.15, 0.05, 0.7])
#for the colorbar we map the original colormap, not the nonlinear one:
sm = plt.cm.ScalarMappable(cmap=plt.cm.jet,
norm=plt.Normalize(vmin=tmin, vmax=tmax))
sm._A = []
cbar = fig.colorbar(sm, cax=cbar_ax)
#here we are relabel the linear colorbar ticks to match the nonlinear ticks
cbar.set_ticks(cmap_nonlin.transformed_levels)
cbar.set_ticklabels(["%.2f" % lev for lev in levels])
plt.show()
2 个回答
你可以使用 LinearSegmentedColormap:
使用这个工具时,你需要在一个字典里设置一个颜色查找表,比如下面的'cdict'。
cdict = {'red': [(0.0, 0.0, 0.0),
(0.15, 0.01, 0.01),
(0.35, 1.0, 1.0),
(1.0, 1.0, 1.0)],
'green': [(0.0, 0.0, 0.0),
(1.0, 0.0, 1.0)],
'blue': [(0.0, 0.0, 1.0),
(0.9, 0.01, 0.01),
(1.0, 0.0, 1.0)]}
这个查找表展示了数值之间的过渡。我把红色的变化范围设置得比较大,主要集中在 t1/t2_max
的值(0.15到0.35)附近,而蓝色的变化范围也设置得很大,主要集中在 t2/t2_max
的值(0.9到1.0)附近。绿色则没有变化。我建议你去看看 文档,了解这个是怎么工作的。(注意,这个过程可以自动化,让它根据你的值自动变化)。然后我对你的代码做了一些调整,以显示图表:
import matplotlib.colors as col
my_cmap = col.LinearSegmentedColormap('my_colormap', cdict)
plt.figure(figsize=(22.0, 15.50))
p = plt.subplot(1,2,1)
colors = my_cmap(t1/t2_max)
p.scatter(x, y, edgecolors=colors, s=15, linewidths=4)
p = plt.subplot(1,2,2)
colors = my_cmap(t2/t2_max)
p.scatter(x, y, edgecolors=colors, s=15, linewidths=4)
plt.subplots_adjust(left=0.2)
cbar_ax = plt.axes([0.10, 0.15, 0.05, 0.7])
sm = plt.cm.ScalarMappable(cmap=my_cmap, norm=plt.Normalize(vmin=0, vmax=t2_max))
sm._A = []
cbar = plt.colorbar(sm,cax=cbar_ax)
plt.show()
你提供的链接对颜色映射的解决方案相当不错。我稍微做了一些修改,但它包含了所有必要的内容。你需要为你的非线性颜色映射选择一些合理的级别。我使用了两个范围,这两个范围是围绕平均值的,范围在样本标准差的+- 4
之间。通过把这个数字换成其他值,你可以在两个平均值周围得到不同的颜色渐变。
关于颜色条,你可以选择:
- 要么让颜色非线性分布,而标签线性分布
- 要么让颜色线性分布,而标签非线性分布。
第二种方式在查看数据时能提供更高的分辨率,看起来也更好,下面是实现的代码:
import numpy as np
import matplotlib.pyplot as plt
x = y = np.linspace(1, 10, 10)
t1mean, t2mean = 2, 9
sigma1, sigma2 = .3, .01
t1 = np.random.normal(t1mean, sigma1, 10)
t2 = np.random.normal(t2mean, sigma2, 10)
class nlcmap(object):
def __init__(self, cmap, levels):
self.cmap = cmap
self.N = cmap.N
self.monochrome = self.cmap.monochrome
self.levels = np.asarray(levels, dtype='float64')
self._x = self.levels
self.levmax = self.levels.max()
self.transformed_levels = np.linspace(0.0, self.levmax,
len(self.levels))
def __call__(self, xi, alpha=1.0, **kw):
yi = np.interp(xi, self._x, self.transformed_levels)
return self.cmap(yi / self.levmax, alpha)
tmax = max(t1.max(), t2.max())
#the choice of the levels depends on the data:
levels = np.concatenate((
[0, tmax],
np.linspace(t1mean - 4 * sigma1, t1mean + 4 * sigma1, 5),
np.linspace(t2mean - 4 * sigma2, t2mean + 4 * sigma2, 5),
))
levels = levels[levels <= tmax]
levels.sort()
cmap_nonlin = nlcmap(plt.cm.jet, levels)
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.scatter(x, y, edgecolors=cmap_nonlin(t1), s=15, linewidths=4)
ax2.scatter(x, y, edgecolors=cmap_nonlin(t2), s=15, linewidths=4)
fig.subplots_adjust(left=.25)
cbar_ax = fig.add_axes([0.10, 0.15, 0.05, 0.7])
#for the colorbar we map the original colormap, not the nonlinear one:
sm = plt.cm.ScalarMappable(cmap=plt.cm.jet,
norm=plt.Normalize(vmin=0, vmax=tmax))
sm._A = []
cbar = fig.colorbar(sm, cax=cbar_ax)
#here we are relabel the linear colorbar ticks to match the nonlinear ticks
cbar.set_ticks(cmap_nonlin.transformed_levels)
cbar.set_ticklabels(["%.2f" % lev for lev in levels])
plt.show()
在结果中,注意颜色条的刻度并不是均匀分布的: