问题
I am asking a question stemming from this original post Heatmap with circles indicating size of population
I am trying to replicate this using my dataframe, however, my circles are non aligning to the plot. Secondary, I want to also create a legend which indicates the value relative to the size of circle.
x= {'ID': {0: 'GO:0002474',
1: 'GO:0052548',
2: 'GO:0002483',
3: 'GO:0043062',
4: 'GO:0060333'},
'TERM': {0: 'antigen processing and presentation of peptide antigen via MHC class I',
1: 'regulation of endopeptidase activity',
2: 'antigen processing and presentation of endogenous peptide antigen',
3: 'extracellular structure organization',
4: 'interferon-gamma-mediated signaling pathway'},
'Count': {0: 11, 1: 17, 2: 5, 3: 15, 4: 6},
'Ratio': {0: 18.64, 1: 14.53, 2: 8.47, 3: 12.82, 4: 10.17},
'pvalue': {0: -15.83, 1: -11.39, 2: -9.67, 3: -9.05, 4: -7.41},
'qvalue': {0: -11.63, 1: -7.49, 2: -6.52, 3: -5.63, 4: -4.55},
'Label': {0: 'NODAL', 1: 'NODAL', 2: 'NODAL', 3: 'SHARED', 4: 'NODAL'}}
A2780_GOBP= pd.DataFrame(x)
Attempted Code:
ylabels = A2780_GOBP["TERM"]
xlabels = ["GFP","SHARED","NODAL"]
x, y = np.meshgrid(np.arange(len(xlabels)), np.arange(len(ylabels)))
s = A2780_GOBP["Count"].values
c = A2780_GOBP["pvalue"].values
fig, ax = plt.subplots()
R = s/s.max()/2
circles = [plt.Circle((j,i), radius=r) for r, j, i in zip(R.flat, x.flat, y.flat)]
col = PatchCollection(circles, array=c.flatten(), cmap=cmap)
ax.add_collection(col)
ax.set(xticks=np.arange(3), yticks=np.arange(10),
xticklabels=xlabels, yticklabels=ylabels)
ax.set_xticks(np.arange(3+1)-0.5, minor=True)
ax.set_yticks(np.arange(10+1)-0.5, minor=True)
ax.grid(which='minor')
fig.colorbar(col)
plt.show()
Any help would be greatly appreciated!
回答1:
The problem is here that the copied code fills all fields, whereas your code not necessarily has an entry in each box. We have to look up, where each circle has to be plotted:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
import pandas as pd
x= {'ID': {0: 'GO:0002474',
1: 'GO:0052548',
2: 'GO:0002483',
3: 'GO:0043062',
4: 'GO:0060333'},
'TERM': {0: 'antigen processing and presentation of peptide antigen via MHC class I',
1: 'regulation of endopeptidase activity',
2: 'antigen processing and presentation of endogenous peptide antigen',
3: 'extracellular structure organization',
4: 'interferon-gamma-mediated signaling pathway'},
'Count': {0: 11, 1: 17, 2: 5, 3: 15, 4: 6},
'Ratio': {0: 18.64, 1: 14.53, 2: 8.47, 3: 12.82, 4: 10.17},
'pvalue': {0: -15.83, 1: -11.39, 2: -9.67, 3: -9.05, 4: -7.41},
'qvalue': {0: -11.63, 1: -7.49, 2: -6.52, 3: -5.63, 4: -4.55},
'Label': {0: 'NODAL', 1: 'GFP', 2: 'NODAL', 3: 'SHARED', 4: 'NODAL'}}
A2780_GOBP= pd.DataFrame(x)
cmap = "plasma"
#retrieve unique labels
ylabels = A2780_GOBP["TERM"].unique().tolist()
xlabels = A2780_GOBP["Label"].unique().tolist()
xn = len(xlabels)
yn = len(ylabels)
#retrieve size and color information
s = A2780_GOBP["Count"].values
c = A2780_GOBP["pvalue"].values
#preparation of the figure with its grid
fig, ax = plt.subplots(figsize=(10, 5))
ax.set_xlim(-0.5, xn-0.5)
ax.set_ylim(-0.5, yn-0.5)
ax.set(xticks=np.arange(xn), yticks=np.arange(yn),
xticklabels=xlabels, yticklabels=ylabels)
ax.set_xticks(np.arange(xn)-0.5, minor=True)
ax.set_yticks(np.arange(yn)-0.5, minor=True)
ax.grid(which='minor')
#ensure circles are displayed as circles
ax.set_aspect("equal", "box")
#create circles patches and colorbar
R = s/s.max()/2
circles = [plt.Circle((xlabels.index(A2780_GOBP.loc[i, "Label"]), ylabels.index(A2780_GOBP.loc[i, "TERM"])), radius=r) for i, r in enumerate(R)]
col = PatchCollection(circles, array=c, cmap=cmap)
ax.add_collection(col)
fig.colorbar(col)
plt.show()
Sample output:
The code does not check the integrity of your original database, i.e., that each Label-Term pair indeed only occurs once.
回答2:
Adapted answer for @Mr. T to include legend generator
from matplotlib.legend_handler import HandlerPatch
import matplotlib.patches as mpatches
ylabels = A2780_GOBP["TERM"].unique().tolist()
xlabels = A2780_GOBP["Label"].unique().tolist()
xn = len(xlabels)
yn = len(ylabels)
s = A2780_GOBP["Count"].values
c = A2780_GOBP["pvalue"].values
fig, ax = plt.subplots(figsize=(20,10))
ax.set_xlim(-0.5, xn-0.5)
ax.set_ylim(-0.5, yn-0.5)
ax.set(xticks=np.arange(xn), yticks=np.arange(yn), yticklabels=ylabels)
ax.set_xticklabels(xlabels, rotation='vertical')
ax.set_xticks(np.arange(xn)-0.5, minor=True)
ax.set_yticks(np.arange(yn)-0.5, minor=True)
ax.grid(which='minor')
ax.set_aspect("equal", "box")
R = s/s.max()/2
circles = [plt.Circle((xlabels.index(A2780_GOBP.loc[i, "Label"]), ylabels.index(A2780_GOBP.loc[i, "TERM"])), radius=r) for i, r in enumerate(R)]
col = PatchCollection(circles, array=c, cmap=cmap)
sc=ax.add_collection(col)
cbar=fig.colorbar(col).set_label('$-log_{10}(p-value)$', rotation=270, size=16,labelpad=15)
smax=s.max()
smin=s.min()
smid=(smax+smin)/2
texts = ["3","10","17"]
class HandlerEllipse(HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
p = mpatches.Ellipse(xy=center, width=orig_handle.width,
height=orig_handle.height)
self.update_prop(p, orig_handle, legend)
p.set_transform(trans)
return [p]
c = [mpatches.Ellipse((), width=smin, height=smin, color="grey"),
mpatches.Ellipse((), width=smid, height=smid, color="grey"),
mpatches.Ellipse((), width=smax, height=smax, color="grey"),
]
legend = ax.legend(c,texts, handler_map={mpatches.Ellipse: HandlerEllipse()},title="Number of Proteins",bbox_to_anchor=(3.50, 0.82, 1.0, .102),fontsize="large")
plt.setp(legend.get_title(),fontsize='large')
plt.show()
Output:
来源:https://stackoverflow.com/questions/65631607/showing-different-size-circles-in-heatmap-with-legend-using-matplotlib