The question is to read 10,000 coordinate points from a file and create a colored grid based on the density of each block on the grid. The range of x-axis is [-73.59, -73.55] and the y-axis is [45.49,45.530]. My code will plot a grid with many different colors, now I need a feature to only color the grid that has a specific density n, for example, The n = 100, only the grid with 100 points or higher will be colored to yellow, and other grids will be black.
I just added a link to my shapefile
https://drive.google.com/open?id=1H-8FhfonnPrYW9y7RQZDtiNLxVEiC6R8
import numpy as np
import matplotlib.pyplot as plt
import shapefile
grid_size = 0.002
x1 = np.arange(-73.59,-73.55,grid_size)
y1 = np.arange(45.49,45.530,grid_size)
shape = shapefile.Reader("Shape/crime_dt.shp",encoding='ISO-8859-1')
shapeRecords = shape.shapeRecords()
x_coordinates=[]
y_coordinates=[]
# read all points in .shp file, and store them in 2 lists.
for k in range(len(shapeRecords)):
x = float(shapeRecords[k].shape.__geo_interface__["coordinates"][0])
y = float(shapeRecords[k].shape.__geo_interface__["coordinates"][1])
x_coordinates.append(x)
y_coordinates.append(y)
plt.hist2d(x_coordinates,y_coordinates,bins=[x1,y1])
plt.show()
You can create a colormap with just two colors, and set vmin and vmax to be symmetrical around your desired pivot value.
Optionally you put the value of each bin inside the cells, while the pivot value decides the text color.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
grid_size = 0.002
x1 = np.arange(-73.59, -73.55, grid_size)
y1 = np.arange(45.49, 45.530, grid_size)
# read coordinates from file and put them into two lists, similar to this
x_coordinates = np.random.uniform(x1.min(), x1.max(), size=40000)
y_coordinates = np.random.uniform(y1.min(), y1.max(), size=40000)
pivot_value = 100
# create a colormap with two colors, vmin and vmax are chosen so that their center is the pivot value
cmap = ListedColormap(['indigo', 'gold'])
# create a 2d histogram with xs and ys as bin boundaries
binvalues, _, _, _ = plt.hist2d(x_coordinates, y_coordinates, bins=[x1, y1], cmap=cmap, vmin=0, vmax=2*pivot_value)
binvalues = binvalues.astype(np.int)
for i in range(len(x1) - 1):
for j in range(len(y1) - 1):
plt.text((x1[i] + x1[i + 1]) / 2, (y1[j] + y1[j + 1]) / 2, binvalues[i, j],
color='white' if binvalues[i, j] < pivot_value else 'black',
ha='center', va='center', size=8)
plt.show()
PS: If the bin values are very important, you can add them all as ticks. Then, their positions can also be used to draw gridlines as a division between the cells.
plt.yticks(y1)
plt.xticks(x1, rotation=90)
plt.grid(True, ls='-', lw=1, color='black')
To obtain contours based on these data, you could plt.contourf with the generated matrix. (You might want to use np.histogram2d to directly create the matrix.)
plt.contourf((x1[1:]+x1[:-1])/2, (y1[1:]+y1[:-1])/2, binvalues.T, levels=[0,100,1000], cmap=cmap)
I'd like to add a legend that is only whole numbers, i.e. 0,1,2,3...14, instead of the gradient color bar. Basically, I want the array values to have a unique color and label in the legend, so that you can clearly distinguish each value in the array.
fileloc=os.path.join(basepath, infile)
data=np.loadtxt(fileloc)
fig = plt.figure(figsize=(20,10))
plt.imshow(data)
plt.colorbar()
If I understand the question correctly, the data is given as an integer numpy array which results in an image with exactly N different colors.
To get a colormap with exactly N colors from the viridis colormap, use plt.cm.get_cmap('viridis', N). This will result in a colorbar with exactly N regions.
To get ticks nicely in the center of each region, divide the space into 2N+1 pieces, and then take all the odd positions. (So, if there are 5 colors, the colorbar will go from 0 to 4, which would get 11 marks of which mark 0 is skipped and marks 1, 3, 5, 7 and 9 are used). Next to each tick a label with the number can be placed.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 4))
# create some random test data
data = np.random.normal(0, 0.05, size=(150, 150)).cumsum(axis=0).cumsum(axis=1)
data = data.astype(np.int) # convert to integers
data -= data.min() # let the numbers start at zero
num_colors = data.max() + 1
cmap = plt.cm.get_cmap('viridis', num_colors)
plt.imshow(data, cmap=cmap)
cbar = plt.colorbar(ticks=np.linspace(0, num_colors - 1, num_colors * 2 + 1)[1::2])
cbar.ax.set_yticklabels(range(num_colors))
plt.show()
You can use matplotlib.colors.ListedColormap as follows:
custom_cmap = colors.ListedColormap(['purple','blue','green','yellow']) #... and so on until you have 15 colours specified
Then just pass that as the cmap argument to imshow and colorbar:
plt.imshow(data, cmap=custom_cmap)
plt.colorbar(cmap=custom_cmap)
I am stuck making the visualization I want. I cannot yet put images so the link is below. I almost have what I want. The issue is the labels are not correctly placed.
inverted-polar-bar-demo
I would like to have the labels be rotated like they are, but have the labels' right edges aligned to just inside the outer edge of the circle.
EDIT To clarify:
The labels I used for this example are all 'testing'. With actual data, these labels will be of different length. I want to have the end of the labels moved so that they always have their last letter next to the outside edge of the circle. So in this case, all the 'g's would be next to the outside edge.
import matplotlib.pyplot as mpl
import numpy as np
import random
bgcolor = '#222222'
barcolor = '#6699cc'
bottom = 15
N = 32
Values = np.random.random(N)*10
MetricLabels = ['testing' for _ in range(1, N+1)]
# Select the radii, thetas, and widths.
Radii = -5*np.ones(N)-Values
Theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
width = 2*np.pi/N
# Make a list of shifted thetas to place the labels at.
ThetaShifted = np.copy(Theta)
for i in range(N-1):
ThetaShifted[i] = (Theta[i] + Theta[i+1])/2.0
ThetaShifted[-1] = (Theta[-1] + 2.0*np.pi)/2.0
# Make the figure
fig = mpl.figure()
ax = fig.add_subplot(111, projection='polar')
bars = ax.bar(Theta, Radii, width=width, bottom=bottom)
# Set the outer ring to be invisible.
ax.spines["polar"].set_visible(False)
# Set the grid line locations but set the labels to be invisible.
ax.grid(False)
ax.set_thetagrids([], visible=False)
ax.set_rgrids([3], visible=False)
# Apply colors to bars based on the settings above.
for v, bar in zip(Values, bars):
bar.set_facecolor(barcolor)
bar.set_edgecolor(bar.get_facecolor())
# Show the metric and value labels
for counter in range(N):
ax.text(ThetaShifted[counter], bottom-3, MetricLabels[counter],
horizontalalignment='center', verticalalignment='baseline',
rotation=(counter+.5)*360/N, color=bgcolor)
ax.text(ThetaShifted[counter], bottom+0.75, np.round(Values[counter],2),
horizontalalignment='center', verticalalignment='center',
color=bars[counter].get_facecolor())
# Set the background color to be a dark grey,
ax.set_axis_bgcolor(bgcolor)
fig.set_facecolor(bgcolor)
# Show the figure.
mpl.show()
I actually solved my issue. See image and code below. The main thing to solve it was to use the monospace font family and to use rjust to create the label strings to be fixed length and right justified from the beginning. After that, it is just a matter of choosing the correct radial location for each label which should be much easier when they are all the same number of characters.
import matplotlib.pyplot as mpl
import numpy as np
import random
bgcolor = '#222222'
barcolor = '#6699cc'
bottom = 15
N = 32
Values = np.random.random(N)*10
MetricLabels = [('A'*(4+int(8*random.random()))).rjust(10) for _ in range(1, N+1)]
# Select the radii, thetas, and widths.
Radii = -5*np.ones(N)-Values
Theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
width = 2*np.pi/N
# Make a list of shifted thetas to place the labels at.
ThetaShifted = np.copy(Theta)
for i in range(N-1):
ThetaShifted[i] = (Theta[i] + Theta[i+1])/2.0
ThetaShifted[-1] = (Theta[-1] + 2.0*np.pi)/2.0
# Make the figure
fig = mpl.figure()
ax = fig.add_subplot(111, projection='polar')
bars = ax.bar(Theta, Radii, width=width, bottom=bottom)
# Set the outer ring to be invisible.
ax.spines["polar"].set_visible(False)
# Set the grid line locations but set the labels to be invisible.
ax.grid(False)
ax.set_thetagrids([], visible=False)
ax.set_rgrids([3], visible=False)
# Apply colors to bars based on the settings above.
for v, bar in zip(Values, bars):
bar.set_facecolor(barcolor)
bar.set_edgecolor(bar.get_facecolor())
# Show the metric and value labels
for counter in range(N):
ax.text(ThetaShifted[counter], bottom-.075*(10+len(MetricLabels[counter])), MetricLabels[counter]+' '*5,
horizontalalignment='center', verticalalignment='center',
rotation=(counter+.5)*360/N, color=bgcolor,
family='monospace')
ax.text(ThetaShifted[counter], bottom+1, np.round(Values[counter],2),
horizontalalignment='center', verticalalignment='center',
rotation=(counter+.5)*360/N, color=bars[counter].get_facecolor(),
family='monospace')
# Set the background color to be a dark grey,
ax.set_axis_bgcolor(bgcolor)
fig.set_facecolor(bgcolor)
# Show the figure.
mpl.show()
If I correct understand what you want you have to add rotation property to the second call of counter cycle and align the text like here:
...
# Show the metric and value labels
for counter in range(N):
ax.text(ThetaShifted[counter], bottom-3, MetricLabels[counter],
horizontalalignment='center', verticalalignment='baseline',
rotation=(counter+.5)*360/N, color=bgcolor)
ax.text(ThetaShifted[counter], bottom+2.5, np.round(Values[counter],2),
horizontalalignment='center', verticalalignment='center',
rotation=(counter+.5)*360/N,
color=bars[counter].get_facecolor())
...
I found a similar quesion on How to plot confusion matrix with string axis rather than integer in python. But the answer is not exact what I want. Because it doesn't contain gridding (e.g., the numbers are not in little squares) and there is background color to show the number which is not what I want.
import numpy as np
import matplotlib.pyplot as plt
conf_arr = [[33,2,0,0,0,0,0,0,0,1,3],
[3,31,0,0,0,0,0,0,0,0,0],
[0,4,41,0,0,0,0,0,0,0,1],
[0,1,0,30,0,6,0,0,0,0,1],
[0,0,0,0,38,10,0,0,0,0,0],
[0,0,0,3,1,39,0,0,0,0,4],
[0,2,2,0,4,1,31,0,0,0,2],
[0,1,0,0,0,0,0,36,0,2,0],
[0,0,0,0,0,0,1,5,37,5,1],
[3,0,0,0,0,0,0,0,0,39,0],
[0,0,0,0,0,0,0,0,0,0,38]]
norm_conf = []
for i in conf_arr:
a = 0
tmp_arr = []
a = sum(i, 0)
for j in i:
tmp_arr.append(float(j)/float(a))
norm_conf.append(tmp_arr)
fig = plt.figure()
plt.clf()
ax = fig.add_subplot(111)
ax.set_aspect(1)
res = ax.imshow(np.array(norm_conf), cmap=plt.cm.jet,
interpolation='nearest')
width, height = conf_arr.shape
for x in xrange(width):
for y in xrange(height):
ax.annotate(str(conf_arr[x][y]), xy=(y, x),
horizontalalignment='center',
verticalalignment='center')
cb = fig.colorbar(res)
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
plt.xticks(range(width), alphabet[:width])
plt.yticks(range(height), alphabet[:height])
plt.savefig('confusion_matrix.png', format='png')
By making only a few changes to that rather excellent code proposal (I upvoted it, consider doing that too), you can get the figure you're describing.
You'll get gridding by calling the hlines and vlines methods of the ax object, which will add horizontal and vertical lines respectively.
When you then also remove the call to imshow, the colors are gone. Like this:
import numpy as np
import matplotlib.pyplot as plt
conf_arr = np.array([[33,2,0,0,0,0,0,0,0,1,3],
[3,31,0,0,0,0,0,0,0,0,0],
[0,4,41,0,0,0,0,0,0,0,1],
[0,1,0,30,0,6,0,0,0,0,1],
[0,0,0,0,38,10,0,0,0,0,0],
[0,0,0,3,1,39,0,0,0,0,4],
[0,2,2,0,4,1,31,0,0,0,2],
[0,1,0,0,0,0,0,36,0,2,0],
[0,0,0,0,0,0,1,5,37,5,1],
[3,0,0,0,0,0,0,0,0,39,0],
[0,0,0,0,0,0,0,0,0,0,38]])
height, width = conf_arr.shape
fig = plt.figure('confusion matrix')
ax = fig.add_subplot(111, aspect='equal')
for x in range(width):
for y in range(height):
ax.annotate(str(conf_arr[x][y]), xy=(y, x), ha='center', va='center')
offset = .5
ax.set_xlim(-offset, width - offset)
ax.set_ylim(-offset, height - offset)
ax.hlines(y=np.arange(height+1)- offset, xmin=-offset, xmax=width-offset)
ax.vlines(x=np.arange(width+1) - offset, ymin=-offset, ymax=height-offset)
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
plt.xticks(range(width), alphabet[:width])
plt.yticks(range(height), alphabet[:height])
plt.savefig('confusion_matrix.png', format='png')
Remark that when you remove the call to imshow, you'll need to set the x- and y-limits explicitly, as shown above, otherwise you'll only see the lower left region (imshow updates the limits automatically depending on what you pass to it).