Reputation: 3852
With uneven distributed data, the color are too similar for almost all points. For example. the figure below represent too much shallow color corresponding to the value < 12000
.
So, I thought about using Pysal to break the data range into several class and allot the colormap evenly to these classes.
Here is my attempt
Lat
is the latitude of each pointLon
is the longitude of each point Value
is the attribute corresponding to each point which I want to plot it by different marker color and sizefrom pysal.esda.mapclassify import Natural_Breaks as nb
import pysal.esda.mapclassify as mpc
from matplotlib.colors import Normalize
my_bins = [ 20,200,600,1600, 4000, 36000]
breaks =mpc.User_Defined(value,my_bins)
bin_labels = ["%0.0f" % b for b in breaks.bins]
bins = pd.DataFrame({'Class': breaks.yb},)
Then, each point has a class based on its value's data range.
fig = plt.figure()
ax =plt.subplot()
cmap = plt.get_cmap('coolwarm')
norm1 = Normalize(vmin=bins['Class'].min(), vmax=bins['Class'].max())
em_plot = plt.scatter(px[:],py[:], linewidth='0',c =
np.array(bins['Class']),s =(value**0.4)*10, cmap = cmap,\
norm = norm1,alpha = 0.7)
cbaxes = fig.add_axes([0.6625, 0.175, 0.175, 0.04])
cbar = plt.colorbar(em_plot,cax=cbaxes,orientation='horizontal')
value_list = np.array([1,3,5,]) ### THE CLASS
value_label = ['200', '1600','36000'] ### THE VALUE CORRESPONDING TO CLASS
cbar.set_ticks(value_list)
cbar.set_ticklabels(value_label)
cbar.ax.tick_params(labelsize=8)
Instead of the bar style color-legend, I want to label the scatter point with discrete circles: Each circle represent the size and color for specific value on main figure.
An illustration which I found on the Internet shown here:
As the figure above shown, the percentage
legend with different color would be my ideal target!
Any advice would be appreciate!
Thanks for the answer. I have tried your method. But it seems that not fully fit my target.
Here is my figure which take the answer as reference.
From the legend and colorbar, we could found that the color are not the same due to the different mapping strategy(colorbar -> classify result; legend -> REAL VALUE)
Then, I do some adjustment like this:
cmap = plt.get_cmap('coolwarm')
colors=cmap(np.arange(6)/6.0)
ls = [Line2D(range(1), range(1), linewidth=0,
color=colors[np.where(np.array(my_bins) == v)[0]], \
marker='o', ms=(v**0.2)*2) for v in my_bins]
plt.legend(ls,my_bins)
But it went wrong:
ValueError: to_rgba: Invalid rgba arg "[[ 0.2298057 0.29871797 0.75368315 1. ]]" length of rgba sequence should be either 3 or 4
Thanks for Davis's answer, I want to generate the index of the element of my_bins
. So, I tried to get the indice by using np.where(my_bins == element).
But it works failed. Then, I tried to do it using:
..., color = colors[i], ms=(v**0.2)*2) for i,v in enumerate(my_bins)
It works!
Upvotes: 2
Views: 218
Reputation: 40747
I think the "easiest" would be to create a proxy artist for your legend.
The following works, but it needs to be adjusted to your needs, I'm not sure the normalization of the colormap and the size of the points is quite right.
from matplotlib.lines import Line2D
cmap = plt.get_cmap('coolwarm')
my_bins = [20,200,600,1600, 4000, 36000]
ls = [Line2D(range(1), range(1), linewidth=0, color=cmap(v), marker='o', ms=(v**0.2)) for v in my_bins]
plt.plot()
plt.legend(ls,my_bins)
UPDATE
regarding your updated problem: your issue is with color=colors[np.where(np.array(my_bins) == v)[0]]
. Truthfully, I have not idea what you are trying to do here. You should pass a (6, 4)
array, but instead you passed a (1, 4)
array.
If I understand your code, you should maybe try this:
cmap = plt.get_cmap('coolwarm')
colors=cmap(np.arange(6)/6.0)
ls = [Line2D(range(1), range(1), linewidth=0,
color=colors[i], \
marker='o', ms=(v**0.2)*2) for i,v in enumerate(my_bins)]
plt.legend(ls,my_bins)
Upvotes: 1