I am using matplotlib
In plot() or bar(), we can easily put legend, if we add labels to them. but what if it is a contourf() or imshow()
I know there is a colorbar() which can present the color range, but it is not satisfied. I want such a legend which have names(labels).
For what I can think of is that, add labels to each element in the matrix, then ,try legend(), to see if it works, but how to add label to the element, like a value??
in my case, the raw data is like:
1,2,3,3,4
2,3,4,4,5
1,1,1,2,2
for example, 1 represents 'grass', 2 represents 'sand', 3 represents 'hill'... and so on.
imshow() works perfectly with my case, but without the legend.
my question is:
Is there a function that can automatically add legend, for example, in my case, I just have to do like this: someFunction('grass','sand',...)
If there isn't, how do I add labels to each value in the matrix. For example, label all the 1 in the matrix 'grass', labell all the 2 in the matrix 'sand'...and so on.
Thank you!
Edit:
Thanks to #dnalow, it's smart really. However, I still wonder if there is any formal solution.
I quote here a solution to a similar question, in case someone is still interested:
I suppose putting a legend for all values in a matrix only makes sense if there aren't too many of them. So let's assume you have 8 different values in your matrix. We can then create a proxy artist of the respective color for each of them and put them into a legend like this
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
# create some data
data = np.random.randint(0, 8, (5,5))
# get the unique values from data
# i.e. a sorted list of all values in data
values = np.unique(data.ravel())
plt.figure(figsize=(8,4))
im = plt.imshow(data, interpolation='none')
# get the colors of the values, according to the
# colormap used by imshow
colors = [ im.cmap(im.norm(value)) for value in values]
# create a patch (proxy artist) for every color
patches = [ mpatches.Patch(color=colors[i], label="Level {l}".format(l=values[i]) ) for i in range(len(values)) ]
# put those patched as legend-handles into the legend
plt.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0. )
plt.grid(True)
plt.show()
You could use matplotlib.pylab.text to add text to your plot and customize it to look like a legend
For example:
import numpy as np
import matplotlib.cm as cm
import matplotlib.pylab as plt
raw_data = np.random.random((100, 100))
fig, ax = plt.subplots(1)
ax.imshow(raw_data, interpolation='nearest', cmap=cm.gray)
ax.text(5, 5, 'your legend', bbox={'facecolor': 'white', 'pad': 10})
plt.show()
which gives you following
You can check out the matplotlib documentation on text for more details matplotlib text examples
I am just working on the same project to draw a land use map like your problem. Here is my solution following the answers above.
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
##arrayLucc is the array of land use types
arrayLucc = np.random.randint(1,4,(5,5))
## first you need to define your color map and value name as a dic
t = 1 ## alpha value
cmap = {1:[0.1,0.1,1.0,t],2:[1.0,0.1,0.1,t],3:[1.0,0.5,0.1,t]}
labels = {1:'agricultural land',2:'forest land',3:'grassland'}
arrayShow = np.array([[cmap[i] for i in j] for j in arrayLucc])
## create patches as legend
patches =[mpatches.Patch(color=cmap[i],label=labels[i]) for i in cmap]
plt.imshow(arrayShow)
plt.legend(handles=patches, loc=4, borderaxespad=0.)
plt.show()
This resolution doesn't seem very good but it can works. I am also looking for my other methods.
I guess you have to fake your legend, since it requires a line for creating the legend.
You can do something like this:
import pylab as pl
mycmap = pl.cm.jet # for example
for entry in pl.unique(raw_data):
mycolor = mycmap(entry*255/(max(raw_data) - min(raw_data)))
pl.plot(0, 0, "-", c=mycolor, label=mynames[entry])
pl.imshow(raw_data)
pl.legend()
Of cause this is not very satisfying yet. But maybe you can build something on that.
[edit: added missing parenthesis]
Related
I'm experimenting with seaborn and have a question about specifying axes properties. In my code below, I've taken two approaches to creating a heatmap of a matrix and placing the results on two sets of axes in a figure.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
A=np.random.randn(4,4)
labels=['a','b','c','d']
fig, ax = plt.subplots(2)
sns.heatmap(ax =ax[0], data = A)
ax[0].set_xticks(range(len(labels)))
ax[0].set_xticklabels(labels,fontsize=10,rotation=45)
ax[0].set_yticks(range(len(labels)))
ax[0].set_yticklabels(labels,fontsize=10,rotation=45)
ax[1].set_xticks(range(len(labels)))
ax[1].set_xticklabels(labels,fontsize=10,rotation=45)
ax[1].set_yticks(range(len(labels)))
ax[1].set_yticklabels(labels,fontsize=10,rotation=45)
sns.heatmap(ax =ax[1], data = A,xticklabels=labels, yticklabels=labels)
plt.show()
The resulting figure looks like this:
Normally, I would always take the first approach of creating the heatmap and then specifying axis properties. However, when creating an animation (to be embedded on a tkinter canvas), which is what I'm ultimately interested in doing, I found such an ordering in my update function leads to "flickering" of axis labels. The second approach will eliminate this effect, and it also centers the tickmarks within squares along the axes.
However, the second approach does not rotate the y-axis tickmark labels as desired. Is there a simple fix to this?
I'm not sure this is what you're looking for. It looks like you create your figure after you change the yticklabels. so the figure is overwriting your yticklabels.
Below would fix your issue.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
A=np.random.randn(4,4)
labels=['a','b','c','d']
fig, ax = plt.subplots(2)
sns.heatmap(ax =ax[0], data = A)
ax[0].set_xticks(range(len(labels)))
ax[0].set_xticklabels(labels,fontsize=10,rotation=45)
ax[0].set_yticks(range(len(labels)))
ax[0].set_yticklabels(labels,fontsize=10,rotation=45)
ax[1].set_xticks(range(len(labels)))
ax[1].set_xticklabels(labels,fontsize=10,rotation=45)
ax[1].set_yticks(range(len(labels)))
sns.heatmap(ax =ax[1], data = A,xticklabels=labels, yticklabels=labels)
ax[1].set_yticklabels(labels,fontsize=10,rotation=45)
plt.show()
I have got the following problem: I have a sequence of letters (a protein sequence) and I would like to give them a colored background based on a value (I have a matching array of numbers). The end result should look something like this:
I tried a pyplot.matshow by adding my array twice for a 2d array.
figure = plt.figure()
axes = figure.add_subplot(111)
protein_seq='KALEPLMLVMGLISPLAT'
seq_markers= [ protein_seq[i] for i in range(len(protein_seq)) ]
data=np.random.rand(len(protein_seq))
data2d=[data,data]
# randomly generated array
# using the matshow() function
caxes = axes.matshow(data2d, cmap=plt.cm.Reds, vmin=0, vmax=2)
# figure.colorbar(caxes)
axes.set_xticklabels(seq_markers)
This gives
I am not sure how I get my labels on the matrix. I attempted using markers, but they tend to be small in a figure. Many thanks in advance!
You can provide the labels to sns.heatmap, which also will take care of choosing the text color depending on the cell's darkness.
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
figure = plt.figure()
axes = figure.add_subplot(111)
protein_seq = 'KALEPLMLVMGLISPLAT'
data = np.random.rand(len(protein_seq))
sns.heatmap(data=data.reshape(1, -1), annot=np.array([*'KALEPLMLVMGLISPLAT']).reshape(1, -1), fmt='',
xticklabels=[], yticklabels=[],
cmap='Reds', vmin=0, vmax=2, square=True, ax=axes, cbar=False)
plt.show()
The color map in matplotlib allows to mark "bad" values, i.e. NaNs, with a specific color. When we plot the color bar afterwards, this color is not included. Is there a preferred approach to have both the contiuous color bar and a discrete legend for the specific color for bad values?
Edit:
Certainly, it's possible to make use of the "extend" functionality. However, this solution is not satisfactory. The function of the legend/colorbar is to clarify the meaning of colors to the user. In my opinion, this solution does not communicate that the value is a NaN.
Code example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
data = np.random.rand(10, 10)
data[0:3, 0:3] = np.nan # some bad values for set_bad
colMap = cm.RdBu
colMap.set_bad(color='black')
plt.figure(figsize=(10, 9))
confusion_matrix = plt.imshow(data, cmap=colMap, vmin=0, vmax=1)
plt.colorbar(confusion_matrix)
plt.show()
Which produces:
A legend element could be created and used as follows:
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor=colMap(np.nan), label='Bad values')]
plt.legend(handles=legend_elements)
You can do this using one of the approaches used for out-of-range plotting shown at https://matplotlib.org/3.1.1/tutorials/colors/colorbar_only.html#discrete-intervals-colorbar
Set the color of the bad value e.g. to -999 and use the keyword extend.
Another approach is to used masked plotting as shown here.
Another way could be to use cmap.set_bad(). An example can be found here.
I've spent too much time looking into this, some tabs still open in my browser:
Link1 Link2 Link3 Link4
I'm supposed to be working!
Anyway, my problem is: I use someone else's scripts to produce lots of heat maps which I then have to review and sort/assign:
Here's an example of one:
HM sample
I need to be able to easily distinguish a 0.03 from a zero but as you can see they look virtually the same. Ideal solution would be: White(just zero's)-Yellow-Orange-Red or White(just zero's)-Orange-Red
The dev used 'YlOrRd' like so:
sns.heatmap(heat_map, annot=True, fmt=".2g", cmap="YlOrRd", linewidths=0.5,
linecolor='black', xticklabels=xticks, yticklabels=yticks
)
I've tried a bunch of the standard/default colour map options provided to no avail.
I don't have any real experience building colour maps and I don't want break something that's already working. Would anyone have any ideas?
Thanks
**I'm limited in what code/samples I can post due to it being work product.
An option is to take the colors from an existing colormap, replace the first one by white and create a new colormap from those manipulated values.
import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
import matplotlib.colors
import seaborn as sns
# some data
a = np.array([0.,0.002,.005,.0099,0.01,.0101,.02,.04,.24,.42,.62,0.95,.999,1.])
data = np.random.choice(a, size=(12,12))
# create colormap. We take 101 values equally spaced between 0 and 1
# hence the first value 0, second value 0.01
c = np.linspace(0,1,101)
# For those values we store the colors from the "YlOrRd" map in an array
colors = plt.get_cmap("YlOrRd",101)(c)
# We replace the first row of that array, by white
colors[0,:] = np.array([1,1,1,1])
# We create a new colormap with the colors
cmap = matplotlib.colors.ListedColormap(colors)
# Plot the heatmap. The format is set to 4 decimal places
# to be able to disingush specifically the values ,.0099, .0100, .0101,
sns.heatmap(data, annot=True, fmt=".4f", cmap=cmap, vmin=0, vmax=1,
linewidths=0.5, linecolor='black')
plt.show()
I want to make a legend for all bars in my barplot. I have already extracted the labels for all bars, but somehow legend()z only creates a line for the first one and not the second one.
How should I proceed? I was thinking that I maybe have to extract the colors of the bars manually as well, but I don't know. I also hoped there should be an easier way.
df.Completeness.value_counts().plot(kind='bar')
_, labels = plt.xticks()
label_names = list(map(lambda p: p.get_text(), labels))
print(label_names)
plt.legend(label_names)
Set the color by hand and use mpaches
import matplotlib.patches as mpatches
df.Completeness.value_counts().plot(kind='bar')
complete = mpatches.Patch(color='red', label='Complete')
partial = mpatches.Patch(color='blue', label='Partial')
plt.legend(handles=[complete, partial])
If you run this dummy example, do you get the layout that you want?
import pandas as pd
import numpy as np
df=pd.DataFrame({'A':np.random.rand(2)-1,'B':np.random.rand(2)},index=['val1','val2'] )
ax = df.plot(kind='bar', color=['r','b'])