Python: 如何通过忽略零来绘制二维矩阵的热图?

Python: How to plot heat map of 2D matrix by ignoring zeros?

我有一个大小为 500 X 28000 的矩阵,其间包含很多零。但是让我们考虑一个矩阵 A 的工作示例:

A = [[0, 0, 0, 1, 0],
    [1, 0, 0, 2, 3],
    [5, 3, 0, 0, 0],
    [5, 0, 1, 0, 3],
    [6, 0, 0, 9, 0]]

我想绘制上面矩阵的热图,但由于它包含很多零,热图几乎包含白色 space 如下图所示。

如何忽略矩阵中的零点并绘制热图?

这是我尝试过的最小工作示例:

im = plt.matshow(A, cmap=pl.cm.hot, norm=LogNorm(vmin=0.01, vmax=64), aspect='auto') # pl is pylab imported a pl
plt.colorbar(im)
plt.show()

产生:

如您所见,是因为出现了白色 space 的零。

但是我原来的大小为 500X280000 的矩阵包含很多零,这使得我的颜色图几乎是白色的!!

如果删除 LogNorm,则会得到黑色方块而不是白色方块:

im = plt.matshow(A, cmap=plt.cm.hot, aspect='auto') # pl is pylab imported a pl


编辑

在颜色图中,您总是用值填充完整的网格。这就是您实际创建网格的原因:您考虑(例如:插值)所有不完全在网格中的点。这意味着您的数据 许多零,并且图形通过看起来是白色(或黑色)正确地反映了这一点。通过忽略这些值,如果您没有明确的理由这样做,就会创建一个误导性的图表。

如果您对非零值感兴趣,那么您需要另一种类型的图表,如 . For that, you may want to have a look at this answer 所指出的那样。


编辑 2

Adapted from this answer

您可以将值视为一维数组并独立绘制点,而不是用不需要的值填充网格。

A = [[0, 0, 0, 1, 0],
    [1, 0, 0, 2, 3],
    [5, 3, 0, 0, 0],
    [5, 0, 1, 0, 3],
    [6, 0, 0, 9, 0]]
A = np.array(A)
lenx, leny = A.shape

xx = np.array( [ a for a in range(lenx) for a in range(leny) ] )   # Convert 3D to 3*1D
yy = np.array( [ a for a in range(lenx) for b in range(leny) ] )
zz = np.array( [ A[x][y] for x,y in zip(xx,yy) ] )
#---
xx = xx[zz!=0]    # Drop zeroes
yy = yy[zz!=0]
zz = zz[zz!=0]
#---
zi, yi, xi = np.histogram2d(yy, xx, bins=(10,10), weights=zz, normed=False)
zi = np.ma.masked_equal(zi, 0)

fig, ax = plt.subplots()
ax.pcolormesh(xi, yi, zi, edgecolors='black')
scat = ax.scatter(xx, yy, c=zz, s=200)
fig.colorbar(scat)
ax.margins(0.05)

plt.show()

此答案与 Luis 答案的 'Edit 2' 部分方向相同。事实上,这是它的一个简化版本。我 post 这样做只是为了纠正我在评论中的误导性陈述。我在评论区看到警告说我们不应该讨论,所以我用这个回答区。

无论如何,先让我 post 我的代码。请注意,我使用了脚本中随机生成的更大矩阵,而不是您的示例矩阵 A.

#!/usr/bin/python
#
# This script was written by norio 2016-8-5.

import os, re, sys, random
import numpy as np

#from matplotlib.patches import Ellipse
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.image as img

mpl.rcParams['lines.linewidth'] = 2
mpl.rcParams['lines.markeredgewidth'] = 1.0
mpl.rcParams['axes.formatter.limits'] = (-4,4)
#mpl.rcParams['axes.formatter.limits'] = (-2,2)
mpl.rcParams['axes.labelsize'] = 'large'
mpl.rcParams['xtick.labelsize'] = 'large'
mpl.rcParams['ytick.labelsize'] = 'large'
mpl.rcParams['xtick.direction'] = 'out'
mpl.rcParams['ytick.direction'] = 'out'


############################################
#numrow=500
#numcol=280000
numrow=50
numcol=28000
# .. for testing
numelm=numrow*numcol
eps=1.0e-9
#
#numnz=int(1.0e-7*numelm)
numnz=int(1.0e-5*numelm)
# .. for testing
vmin=1.0e-6
vmax=1.0
outfigname='Whosebug38790536.png'
############################################

### data matrix
# I am generating a data matrix here artificially.
print 'generating pseudo-data..'
random.seed('20160805')
matA=np.zeros((numrow, numcol))
for je in range(numnz):
    jr = random.uniform(0,numrow)
    jc = random.uniform(0,numcol)
    matA[jr,jc] = random.uniform(vmin,vmax)


### Actual processing for a given data will start from here
print 'processing..'

idxrow=[]
idxcol=[]
val=[]
for ii in range(numrow):
    for jj in range(numcol):
        if np.abs(matA[ii,jj])>eps:
            idxrow.append(ii)
            idxcol.append(jj)
            val.append( np.abs(matA[ii,jj]) )

print 'len(idxrow)=', len(idxrow)    
print 'len(idxcol)=', len(idxcol)    
print 'len(val)=',    len(val)    


############################################
# canvas setting for line plots 
############################################

f_size   = (8,5)

a1_left   = 0.15
a1_bottom  = 0.15
a1_width  = 0.65
a1_height = 0.80
#
hspace=0.02
#
ac_left   = a1_left+a1_width+hspace
ac_bottom = a1_bottom
ac_width  = 0.03
ac_height = a1_height

############################################
# plot 
############################################
print 'plotting..'

fig1=plt.figure(figsize=f_size)
ax1 =plt.axes([a1_left, a1_bottom, a1_width, a1_height], axisbg='w')

pc1=plt.scatter(idxcol, idxrow, s=20, c=val, cmap=mpl.cm.gist_heat_r)
# cf.
# http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.scatter
plt.xlabel('Column Index', fontsize=18)
plt.ylabel('Row Index', fontsize=18)
ax1.set_xlim([0, numcol-1])
ax1.set_ylim([0, numrow-1])

axc =plt.axes([ac_left, ac_bottom, ac_width, ac_height], axisbg='w')
mpl.colorbar.Colorbar(axc,pc1, ticks=np.arange(0.0, 1.5, 0.1) )

plt.savefig(outfigname)
plt.close()

此脚本输出一个图形,'Whosebug38790536.png',如下所示。

如您在我的代码中所见,我使用 scatter 而不是 plot。我意识到 plot 命令不是最适合这里的任务。

我需要更正的另一句话是 row_index 不需要有多达 140,000,000(=500*280000) 个元素。它只需要有非零元素的行索引。更准确地说,列表, idxrowidxcolval,在上面的代码中进入scatter命令,其长度等于非零元素的个数。

请注意,Luis 的回答中已正确处理了这两点。

虽然norio的回答是正确的。我认为只需几行代码就可以给出更切题的快速答案:

import numpy as np
import matplotlib.pyplot as plt
A = np.asarray(A)
x,y = A.nonzero() #get the notzero indices
plt.scatter(x,y,c=A[x,y],s=100,cmap='hot',marker='s') #adjust the size to your needs
plt.colorbar()
plt.show()

请注意轴是倒转的。你可以通过以下方式反转它们:

ax=plt.gca()
ax.invert_xaxis()
ax.invert_yaxis()

另请注意,您现在拥有更大的灵活性:

  • 您可以随意设置标记大小、标记类型和透明度
  • 此过程更快,因为零不会被解析为 matplotlib。

您可以将零设置为 float("nan") 然后绘制,对我有用。