将字符串与随机颜色 matplotlib 匹配的优雅方式
Posted
技术标签:
【中文标题】将字符串与随机颜色 matplotlib 匹配的优雅方式【英文标题】:Elegant way to match a string to a random color matplotlib 【发布时间】:2015-10-31 05:10:03 【问题描述】:我想将一些数据的标签转换为颜色,以便用 matplotlib 绘图
我有一个名单["bob", "joe", "andrew", "pete"]
在 matplotlib 中是否有一种内置方法可以将这些字符串与颜色值进行映射?我考虑过随机创建十六进制值,但最终可能会得到相似的颜色或不可见的颜色。
我尝试了几种不同的方法来尝试从以下 cmap 答案中创建键值:
这个:
#names is a list of distinct names
cmap = plt.get_cmap('cool')
colors = cmap(np.linspace(0, 1, len(names)))
clr = names[i]: colors[i] for i in range(len(names))
ax.scatter(x, y, z, c=clr)
【问题讨论】:
另见***.com/questions/26139423/… 和github.com/matplotlib/matplotlib/issues/6214 【参考方案1】:选择一个color map,比如viridis
:
cmap = plt.get_cmap('viridis')
颜色图cmap
是一个函数,它可以获取从 0 到 1 的值数组并将它们映射到 RGBA 颜色。 np.linspace(0, 1, len(names))
生成长度为 len(names)
的从 0 到 1 的等距数字数组。因此,
colors = cmap(np.linspace(0, 1, len(names)))
从viridis
颜色映射中选择等距颜色。
注意这里不是使用字符串的值,它只是使用列表中字符串的序号位置来选择颜色。另请注意,这些不是随机颜色,这只是从任意字符串列表生成唯一颜色的简单方法。
所以:
import numpy as np
import matplotlib.pyplot as plt
cmap = plt.get_cmap('viridis')
names = ["bob", "joe", "andrew", "pete"]
colors = cmap(np.linspace(0, 1, len(names)))
print(colors)
# [[ 0.267004 0.004874 0.329415 1. ]
# [ 0.190631 0.407061 0.556089 1. ]
# [ 0.20803 0.718701 0.472873 1. ]
# [ 0.993248 0.906157 0.143936 1. ]]
x = np.linspace(0, np.pi*2, 100)
for i, (name, color) in enumerate(zip(names, colors), 1):
plt.plot(x, np.sin(x)/i, label=name, c=color)
plt.legend()
plt.show()
问题
clr = names[i]: colors[i] for i in range(len(names))
ax.scatter(x, y, z, c=clr)
是ax.scatter
的c
参数期望RGB(A) 的sequence
与x
相同长度的值或单一颜色。 clr
是一个字典,而不是一个序列。所以
如果colors
的长度与x
相同,那么您可以使用
ax.scatter(x, y, z, c=colors)
【讨论】:
我试图将这些作为 3d scatter 中的 c 参数输入我试过 zip(names, colors) #names 是名称列表,颜色是 cmap#,我在 matplotlib 中遇到错误。有没有办法解决这个问题 你能发布代码和完整的回溯错误信息吗? 我通过把它变成字典来添加我试图做的事情ax.scatter
的 c
参数需要 RGB(A) 值的序列(例如列表或数组),它不接受字典。因此,如果您想为x,y,z
的每个值使用不同的颜色,请使用c=colors
而不是c=clr
。【参考方案2】:
我使用哈希函数来获取 0 到 1 之间的数字,即使你不知道所有标签也可以使用它:
x = [1, 2, 3, 4, 5]
labels = ["a", "a", "b", "b", "a"]
y = [1, 2, 3, 4, 5]
colors = [float(hash(s) % 256) / 256 for s in labels]
plt.scatter(x, y, c=colors, cmap="jet")
plt.show()
【讨论】:
这个答案对我解决问题很有启发。但它可能有问题的一件事是,hash(s) % 256
对于labels
中的两个不同字符串可能是相同的。【参考方案3】:
这让我非常不安,我写了get_cmap_string
,它返回一个与cmap
完全相同但也作用于字符串的函数。
data = ["bob", "joe", "pete", "andrew", "pete"]
cmap = get_cmap_string(palette='viridis', domain=data)
cmap("joe")
# (0.20803, 0.718701, 0.472873, 1.0)
cmap("joe", alpha=0.5)
# (0.20803, 0.718701, 0.472873, 0.5)
1。实施
所有其他答案提到的基本思想是我们需要一个哈希表——从我们的字符串到整数的映射,它是唯一的。在 python 中,这只是一个字典。
hash(str)
不起作用的原因是,即使 matplotlib 的 cmap
接受任何整数,两个不同的字符串也有可能获得相同的颜色。例如,如果hash(str1)
是8
并且hash(str2)
是18
,我们将cmap
初始化为get_cmap(name=palette, lut=10)
那么cmap(hash(str1))
将与cmap(hash(str2))
相同
代码
import numpy as np
import matplotlib.cm
def get_cmap_string(palette, domain):
domain_unique = np.unique(domain)
hash_table = key: i_str for i_str, key in enumerate(domain_unique)
mpl_cmap = matplotlib.cm.get_cmap(palette, lut=len(domain_unique))
def cmap_out(X, **kwargs):
return mpl_cmap(hash_table[X], **kwargs)
return cmap_out
2。用法
与其他答案中的示例相同,但现在请注意名称 pete
出现了两次。
import matplotlib.pyplot as plt
# data
names = ["bob", "joe", "pete", "andrew", "pete"]
# color map for the data
cmap = get_cmap_string(palette='viridis', domain=names)
# example usage
x = np.linspace(0, np.pi*2, 100)
for i_name, name in enumerate(names):
plt.plot(x, np.sin(x)/i_name, label=name, c=cmap(name))
plt.legend()
plt.show()
您可以看到,图例中的条目是重复的。解决这个问题是另一个挑战,请参阅here。 或者按照here 的说明使用自定义图例。
3。替代品
就 matplotlib 开发人员的讨论而言,他们建议使用 Seaborn。 请参阅讨论 here 和示例用法 here。
【讨论】:
【参考方案4】:这是另一个选择:
names = ["bob", "joe", "andrew", "pete"]
colmap = name: n for n, name in enumerate(set(names)) # <-- uses 'set' to get unique names from list
ax.scatter(
x, y, z,
c = [colmap[name] for name in names],
cmap = 'tab10' # <-- not necessary but helpful if you want to make sure colors aren't similar
)
它只是将名称转换为整数,matplotlib 自动决定如何将整数转换为颜色。如果您使用qualitative colormap,则可以确保颜色不会相似,例如tab10
【讨论】:
以上是关于将字符串与随机颜色 matplotlib 匹配的优雅方式的主要内容,如果未能解决你的问题,请参考以下文章