Matplotlib PathPatch 颜色和图例不匹配

Matplotlib PathPatch Colors and Legends not Matching

我有一个数据集,它是一个列表列表。

每个列表都是一个要绘制为箱线图的类别。

每个列表都有一个最多包含 9 个要绘制成子图的组件的列表。

下面我使用的函数是基于这个answer。我把它从我的工作中拉出来并添加了一些模拟数据。下面应该是一个最小的例子。

neonDict = {
    0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8
    
    }
import matplotlib as mpl
import matplotlib.pyplot as plt


def coloredBoxPlot(axis, data,edgeColor,fillColor):
        bp = axis.boxplot(data,vert=False,patch_artist=True)
        for element in ['boxes', 'whiskers', 'fliers', 'means', 'medians', 'caps']:
            plt.setp(bp[element], color=edgeColor)
            
        for patch in bp['boxes']:
            patch.set(facecolor=fillColor)
    
        return bp 

def plotCalStats(data, prefix='Channel', savedir=None,colors=['#00597c','#a8005c','#00aeea','#007d50','#400080','#e07800'] ):       
    
    csize = mpl.rcParams['figure.figsize']
    cdpi = mpl.rcParams['figure.dpi']
       
    mpl.rcParams['figure.figsize'] = (12,8)
    mpl.rcParams['figure.dpi'] = 1080
    
    pkdata  = []
    labels  = []
    lstyles = []
    
    fg, ax = plt.subplots(3,3)
    for pk in range(len(neonDict)):
        px = pk // 3
        py = pk  % 3
        ax[px,py].set_xlabel('Max Pixel')
        ax[px,py].set_ylabel('')
        ax[px,py].set_title(str(neonDict[pk]) + ' nm')    
        pkdata.append([])
    
    for cat in range(len(data)):
        bp = ''       
        
            
        for acal in data[cat]:
            for apeak in acal.peaks:
                pkdata[apeak].append(acal.peaks[apeak][0])
        
        for pk in range(9):
            px = pk // 3
            py = pk  % 3        
            bp = coloredBoxPlot(ax[px,py], pkdata[pk], colors[cat], '#ffffff')
        
        if len(data[cat]) > 0: 
            #print(colors[cat])
            #print(bp['boxes'][0].get_edgecolor())
            labels.append(prefix+' '+str(cat))
            lstyles.append(bp['boxes'][0])
    
    fg.legend(lstyles,labels) 
    fg.suptitle('Calibration Summary by '+prefix)
    fg.tight_layout()
    if savedir is not None:
        plt.savefig(savedir + 'Boxplots.png')
        
    plt.show()
    
    mpl.rcParams['figure.figsize'] = csize
    mpl.rcParams['figure.dpi']     = cdpi    
    return


class acal:
    def __init__(self):
        self.peaks = {}
        for x in range(9):
            self.peaks[x] = (np.random.randint(20*x,20*(x+1)),)

mockData = [[acal() for y in range(100)] for x in range(6)]

#Some unused channels
mockData[2] = []
mockData[3] = []
mockData[4] = []

plotCalStats(mockData)

所以问题是绘图颜色与图例不符。即使我将数据限制为仅在数据存在时添加标签(因此确保使用空数据集调用箱线图并且没有获得适当的 PathPatch 没有问题。

打印输出验证颜色是否正确存储在 PathPatch 中。 (我可以添加我的数字 -> 十六进制转换器)如果有疑问。

附件是输出。可以看到我得到了一个紫色的盒子,但传说中没有紫色。紫色是第 4 个类别,它是空的。

知道标签与实际样式不符的原因吗?非常感谢!

编辑: 解决关于 'confusing' 的问题。 我有六类数据,每一类都来自一个事件。每个事件有 9 个组成部分。我想比较所有事件,对于每个单独的组件,对于单个图上的每个类别,如下所示。

每个子图都是一个单独的组件,由每个分类(通道)的数据系列组成。

所以我提供的 link(就像我说的,改编自)展示了如何在一个轴上为 2 个数据集创建一个箱线图。我基本上对 9 轴上的 6 个数据集做了同样的事情,其中​​ 3 个数据集是空的(但不必如此,我这样做是为了说明问题。如果我有所有 6 个数据集,怎么能你说颜色乱了?????)

关于阿尔法:

仅向 matplotlib 提供 RGB 数据时,alpha 总是 'ff'。如果我调用 get_edgecolors,它将 return 一个元组 (RGBA),其中 A = 1.0。 请参阅注释掉的打印语句。

编辑 2:

如果我将其限制为单个类别,则箱形图视图不会那么混乱。
单个示例(查看箱形图颜色如何为橙色,图中显示为蓝色) 所有颜色关闭 感觉这个曾经有用....

不确定错误是如何出现的,但问题与在创建箱形图之前重新格式化数据有关。

通过在循环类别之前创建子图期间删除 pkdata.append([]) 并添加: pkdata = [[],[],[],[],[],[],[],[],[]] 在类别循环的每次迭代中修复了该问题。前者正在发送所有以前的频道数据...

输出现在更好了。附上完整的 sol。

可能,因为绘图使用来自 pkdata 的数据,空通道 (data[cat]) 绘制了以前的数据(来自 data[cat-1]),因为它仍然在 pkdata 中(实际上,所有以前的数据 [cat] ] 仍在 pkdata 中)然后被绘制。我只检查 data[cat] 以获取每个循环中的数据以添加到图例中。例如,图例是为通道 0、1、5 设置的。但是我们看到通道的数据:0 为 0,0+1 为 1,0+1 为 2,0+1 为 3,0+1 为4、0+1+5 为 5... 因此通道 4(紫色)有要绘制的数据,但未添加到图例中。给人的印象是 'misaligned' 传奇,而不是非传奇数据...

单通道数据实际上是所有6通道重叠,最后通道5颜色为橙色,重叠所有之前的数据,即数据所属的原始通道0数据并适当添加到图例中。

neonDict = {
    0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8
    
    }
import matplotlib as mpl
import matplotlib.pyplot as plt

def getHex(r,g,b,a=1.0):

    colors = [int(r * 255 ),int(g * 255 ),int(b * 255 ),int(a * 255) ]
    s = '#'
    
    for x in range(4):
        cs = hex(colors[x])
        if len(cs) == 3:
            cs = cs + '0'
            
        s += cs.replace('0x','')
          
    return s

def getRGB(colstr):   
    try:
        a = ''
        r = int(colstr[1:3],16) / 255
        g = int(colstr[3:5],16) / 255
        b = int(colstr[5:7],16) / 255
        
        if len (colstr) == 7:
            a = 1.0
        else:
            a = int(colstr[7:],16) / 255
        
        return (r,g,b,a)
    except Exception as e:
        print(e)
        raise e
               
    return

def compareHexColors(col1,col2):
    try:
        ## ASSUME #RBG or #RBGA
        ## If less than 7, append the ff for the colors
        if len(col1) < 9:
            col1 += 'ff'
        if len(col2) < 9:
            col2 += 'ff'
        
        return col1.lower() == col2.lower()
    except Exception as e:
        raise e
    return False

def coloredBoxPlot(axis, data,edgeColor,fillColor):
        bp = axis.boxplot(data,vert=False,patch_artist=True)
        for element in ['boxes', 'whiskers', 'fliers', 'means', 'medians', 'caps']:
            plt.setp(bp[element], color=edgeColor)
            
        for patch in bp['boxes']:
            patch.set(facecolor=fillColor)
    
        return bp 

def plotCalStats(data, prefix='Channel', savedir=None,colors=['#00597c','#a8005c','#00aeea','#007d50','#400080','#e07800'] ):       
    
    csize = mpl.rcParams['figure.figsize']
    cdpi = mpl.rcParams['figure.dpi']
       
    mpl.rcParams['figure.figsize'] = (12,8)
    mpl.rcParams['figure.dpi'] = 1080
    
    pkdata  = []
    labels  = []
    lstyles = []
    
    fg, ax = plt.subplots(3,3)
    for pk in range(len(neonDict)):
        px = pk // 3
        py = pk  % 3
        ax[px,py].set_xlabel('Max Pixel')
        ax[px,py].set_ylabel('')
        ax[px,py].set_title(str(neonDict[pk]) + ' nm')    
        
    
    for cat in range(len(data)):
        bp = ''       
        pkdata = [[],[],[],[],[],[],[],[],[]]
        
        for acal in data[cat]:
            for apeak in acal.peaks:
                pkdata[apeak].append(acal.peaks[apeak][0])
                
    
        for pk in range(9):
            px = pk // 3
            py = pk  % 3   
            bp = coloredBoxPlot(ax[px,py], pkdata[pk], colors[cat], '#ffffff')
        
        if len(data[cat]) > 0: 
            print(compareHexColors(colors[cat],getHex(*bp['boxes'][0].get_edgecolor())))
            labels.append(prefix+' '+str(cat))
            lstyles.append(bp['boxes'][0])
    
    fg.legend(lstyles,labels) 
    fg.suptitle('Calibration Summary by '+prefix)
    fg.tight_layout()
    if savedir is not None:
        plt.savefig(savedir + 'Boxplots.png')
        
    plt.show()
    
    mpl.rcParams['figure.figsize'] = csize
    mpl.rcParams['figure.dpi']     = cdpi    
    return


class acal:
    def __init__(self,center):
        self.peaks = {}      
        for x in range(9):
            self.peaks[x] = [10*x + (center) + (np.random.randint(10)-1)/2.0,0,0]

mockData = [[acal(x) for y in range(1000)] for x in range(6)]


#Some unused channels

mockData[2] = []
mockData[3] = []
mockData[4] = []

plotCalStats(mockData)