I would like to draw a sketch like the one below, using python with matplotlib. I guess making a grid is not so hard, but what about coloring certain squares in a specific way?
N = 15
# make an empty data set
data = np.ones((N, N)) * np.nan
# fill in some fake data
for j in range(3)[::-1]:
data[N//2 - j : N//2 + j +1, N//2 - j : N//2 + j +1] = j
# make a figure + axes
fig, ax = plt.subplots(1, 1, tight_layout=True)
# make color map
my_cmap = matplotlib.colors.ListedColormap(['r', 'g', 'b'])
# set the 'bad' values (nan) to be white and transparent
my_cmap.set_bad(color='w', alpha=0)
# draw the grid
for x in range(N + 1):
ax.axhline(x, lw=2, color='k', zorder=5)
ax.axvline(x, lw=2, color='k', zorder=5)
# draw the boxes
ax.imshow(data, interpolation='none', cmap=my_cmap, extent=[0, N, 0, N], zorder=0)
# turn off the axis labels
ax.axis('off')
Related
I'm trying to construct a grid of black squares, and everytime you click on one it turns white. Now for some reason my code does very weird things:
The coordinates I input doesn't correspond to the array coordinates. I tried to change that by letting i = y - (N-1) and j = x with (x,y) the mouse coordinates. But only the first line will be converted properly (top row of the plot). The rest will be inverted vertically.
When all squares are white the plot automatically reset to black squares.
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import math
N = 3
# Make an empty data set
data = np.zeros((N, N))
# Make a figure + axes
fig, ax = plt.subplots(1, 1, tight_layout=True)
# Draw the boxes
box = ax.imshow(data, cmap='gray', extent=[0, N, 0, N])
# Draw the grid
for x in range(N + 1):
ax.axhline(x, lw=2, color='w', zorder=5)
ax.axvline(x, lw=2, color='w', zorder=5)
# Create interactivity
def on_click(event):
gx = event.xdata
gy = event.ydata
print('x=',gx)
print('y=',gy)
i = int(gy) - N + 1
j = int(gx)
data[i,j] = 1
ax.imshow(data, cmap='gray', extent=[0, N, 0, N])
fig.canvas.draw_idle()
fig = plt.gcf()
fig.canvas.mpl_connect('button_press_event', on_click)
# Turn off the axis labels
ax.axis('off')
plt.show()
Thanks for your help
Well I found the issues:
It was N - 1 - int(gy)
Matplotlib normalizes the values for me when they aren't specified. So a grid of all zeros is the same as a grid of all ones to it (each cell is equal to the mean, in both cases).
i try to do a spyder graph for my streamlit app. However, it functions on my notebook but not in my streamlit. Is anyone has an idea of the problem it does not display anything ?
Spider_kmeans = df[['Cluster_kmeans_Label','Age','Annual Income (k$)','Spending Score (1-100)','Sex (100=Male)']]
Spider_kmeans = Spider_kmeans.groupby('Cluster_kmeans_Label')['Cluster_kmeans_Label','Age','Annual Income (k$)','Spending Score (1-100)','Sex (100=Male)'].mean().reset_index()
def make_spider_kmeans( row, title, color):
# number of variable
categories=list(Spider_kmeans)[1:]
N = len(categories)
# What will be the angle of each axis in the plot? (we divide the plot / number of variable)
angles = [n / float(N) * 2 * pi for n in range(N)]
angles += angles[:1]
# Initialise the spider plot
ax = plt.subplot(3,3,row+1, polar=True)
# If you want the first axis to be on top:
ax.set_theta_offset(pi / 2)
ax.set_theta_direction(-1)
# Draw one axe per variable + add labels labels yet
plt.xticks(angles[:-1], categories, color='black',fontfamily='serif',fontweight='light', size=8)
#ax.set_xticks([]) # turn labels off if you want - can look quite nice
# Draw ylabels
ax.set_rlabel_position(0)
plt.yticks([10,20,30,40,50,75,100], ["10","20","30","40","50","75","100"], color="grey", size=4)
plt.ylim(0,100)
# Ind1
values= Spider_kmeans.loc[row].drop('Cluster_kmeans_Label').values.flatten().tolist()
values += values[:1]
ax.plot(angles, values, color=color, linewidth=2, linestyle='solid')
ax.fill(angles, values, color=color, alpha=0.4)
# Add a title
plt.title(title, size=10, fontfamily='serif',fontweight='bold', y=1.2)
plt.tight_layout()
#############################################################################################
my_dpi=96
plt.figure(figsize=(1000/my_dpi, 1000/my_dpi), dpi=my_dpi)
# Create a color palette:
my_palette = plt.cm.get_cmap("crest", len(df.index))
then i put this code on my streamlit app
for row in range(0, len(Spider_kmeans.index)):
make_spider_kmeans( row=row, title='Cluster: '+ Spider_kmeans['Cluster_kmeans_Label'][row], color='#244747') #change this to my_palette if you want colour to vary by cluster
I
I've seen numerous examples of 3D plots using matplotlib/seaborn in Python but can't seem to get what I'm looking for; I have 50 or so timeseries that I would like to plot cleanly as in the following example below but with the name of the series on the axis; as an example I've marked in Goog, IBM, GE, Pepsi etc. Appreciate any pointers or examples. Thank you,
Example PLOT Click Here Please
Matplotlib has very rich gallery. I found this, you can only plot it once instead of animation. And manually put y-axis legend wherever you want.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Fixing random state for reproducibility
np.random.seed(19680801)
# Create new Figure with black background
fig = plt.figure(figsize=(12, 8))
# Add a subplot with no frame
ax = plt.subplot(111, frameon=False)
# Generate random data
data = np.random.uniform(0, 1, (64, 75))
X = np.linspace(-1, 1, data.shape[-1])
G = 1.5 * np.exp(-4 * X ** 2)
# Generate line plots
lines = []
for i in range(len(data)):
# Small reduction of the X extents to get a cheap perspective effect
xscale = 1 - i / 200.
# Same for linewidth (thicker strokes on bottom)
lw = 1.5 - i / 100.0
line, = ax.plot(xscale * X, i + G * data[i], color="b", lw=lw)
lines.append(line)
# Set y limit (or first line is cropped because of thickness)
ax.set_ylim(-1, 70)
# No ticks
ax.set_xticks([])
ax.set_yticks([])
# 2 part titles to get different font weights
ax.text(0.5, 1.0, "MATPLOTLIB ", transform=ax.transAxes,
ha="right", va="bottom", color="k",
family="sans-serif", fontweight="light", fontsize=16)
ax.text(0.5, 1.0, "UNCHAINED", transform=ax.transAxes,
ha="left", va="bottom", color="k",
family="sans-serif", fontweight="bold", fontsize=16)
def update(*args):
# Shift all data to the right
data[:, 1:] = data[:, :-1]
# Fill-in new values
data[:, 0] = np.random.uniform(0, 1, len(data))
# Update data
for i in range(len(data)):
lines[i].set_ydata(i + G * data[i])
# Return modified artists
return lines
# Construct the animation, using the update function as the animation director.
anim = animation.FuncAnimation(fig, update, interval=10)
plt.show()
I'm borrowing an example from the matplotlib custom cmap examples page:
https://matplotlib.org/examples/pylab_examples/custom_cmap.html
This produces the same image with different numbers of shading contours, as specified in the number of bins: n_bins:
https://matplotlib.org/_images/custom_cmap_00.png
However, I'm interested not only in the number of bins, but the specific break points between the color values. For example, when nbins=6 in the top right subplot, how can I specify the ranges of the bins to such that the shading is filled in these custom areas:
n_bins_ranges = ([-10,-5],[-5,-2],[-2,-0.5],[-0.5,2.5],[2.5,7.5],[7.5,10])
Is it also possible to specify the inclusivity of the break points? For example, I'd like to specify in the range between -2 and 0.5 whether it's -2 < x <= -0.5 or -2 <= x < -0.5.
EDIT WITH ANSWER BELOW:
Using the accepted answer below, here is code that plots each step including finally adding custom colorbar ticks at the midpoint. Note I can't post an image since I'm a new user.
Set up data and 6 color bins:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# Make some illustrative fake data:
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
# Create colormap with 6 discrete bins
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6
cmap_name = 'my_list'
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
Plot different options:
# Set up 4 subplots
fig, axs = plt.subplots(2, 2, figsize=(6, 9))
fig.subplots_adjust(left=0.02, bottom=0.06, right=0.95, top=0.94, wspace=0.05)
# Plot 6 bin figure
im = axs[0,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm)
axs[0,0].set_title("Original 6 Bin")
fig.colorbar(im, ax=axs[0,0])
# Change the break points
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
im = axs[0,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[0,1].set_title("Custom Break Points")
fig.colorbar(im, ax=axs[0,1])
# Arrange color labels by data interval (not colors)
im = axs[1,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[1,0].set_title("Linear Color Distribution")
fig.colorbar(im, ax=axs[1,0], spacing="proportional")
# Provide custom labels at color midpoints
# And change inclusive equality by adding arbitrary small value
n_bins_ranges_arr = np.asarray(n_bins_ranges)+1e-9
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
n_bins_ranges_midpoints = (n_bins_ranges_arr[1:] + n_bins_ranges_arr[:-1])/2.0
im = axs[1,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm ,norm=norm)
axs[1,1].set_title("Midpoint Labels\n Switched Equal Sign")
cbar=fig.colorbar(im, ax=axs[1,1], spacing="proportional",
ticks=n_bins_ranges_midpoints.tolist())
cbar.ax.set_yticklabels(['Red', 'Brown', 'Green 1','Green 2','Gray Blue','Blue'])
plt.show()
You can use a BoundaryNorm as follows:
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6 # Discretizes the interpolation into bins
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
cmap_name = 'my_list'
fig, ax = plt.subplots()
# Create the colormap
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
# Fewer bins will result in "coarser" colomap interpolation
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
ax.set_title("N bins: %s" % n_bin)
fig.colorbar(im, ax=ax)
plt.show()
Or, if you want proportional spacing, i.e. the distance between colors according to their values,
fig.colorbar(im, ax=ax, spacing="proportional")
As the boundary norm documentation states
If b[i] <= v < b[i+1]
then v is mapped to color j; as i varies from 0 to len(boundaries)-2, j goes from 0 to ncolors-1.
So the colors are always chosen as -2 <= x < -0.5, in order to obtain the equal sign on the other side you would need to supply
something like n_bins_ranges = np.array([-10,-5,-2,-0.5,2.5,7.5,10])-1e-9
I created this randomly-valued/coloured 'chessboard':
with the code:
rndm = np.random.rand(8,8)
my_cmap = plt.get_cmap('gray')
plt.figure(figsize=(5,4))
plt.imshow(rndm, cmap=my_cmap, interpolation = 'none')
plt.axis('off')
plt.show()
I wanted to apply a function to the values in a loop, for example raising all values in the array to an increasing power, and then show all in different subplots. Here's the code:
fig, axs = plt.subplots(2,2, figsize=(10, 10))
axs = axs.ravel()
for i, n in zip(np.arange(4), np.arange(2,6)):
axs[i].imshow(np.power(rndm, n), cmap=my_cmap, interpolation = 'none')
axs[i].axis('off')
axs[i].text(1, 1, str(n), fontsize=14, color = 'y')
and the result:
But what I would really like is to cycle through 4 colormaps, for example:
cmaps = ['viridis', 'inferno', 'plasma', 'magma']
fig, axs = plt.subplots(2,2, figsize=(10, 10))
axs = axs.ravel()
for i, n in zip(np.arange(4), np.arange(2,6)):
axs[i].imshow(np.power(rndm, n), cmap=cmaps[i], interpolation = 'none')
axs[i].axis('off')
axs[i].text(1, 1, str(n), fontsize=14, color = 'y')
Good. But here's where my code breaks. I want to save these as individual images.
I wrote this, which is fine except for the fact that the text numbers end up superimposed:
for i, n in zip(np.arange(4), np.arange(2,6)):
plt.imshow(np.power(rndm, n), cmap=cmaps[i], interpolation = 'none')
plt.axis('off')
plt.text(1, 1, str(n), fontsize=14, color = 'y')
plt.savefig("test_n = " + str(n) +".png", dpi=300, bbox_inches='tight', pad_inches=0)
How do I clear them each time?
Shouldn't you use cmaps[i] instead of cmap=my_cmap?
Create a figure in each loop using plt.figure() and it'll work correctly (tested).
for i, n in zip(np.arange(4), np.arange(2,6)):
plt.figure()
plt.imshow(np.power(rndm, n), cmap=cmaps[i], interpolation = 'none')
plt.axis('off')
plt.text(1, 1, str(n), fontsize=14, color = 'y')
plt.savefig("test_n = " + str(n) +".png", dpi=300, bbox_inches='tight', pad_inches=0)
result:
The main image (not saved) :
-Saved images:
Note that the labels starts from 2 because of this line :
plt.text(1, 1, str(n), fontsize=14, color = 'y')
If you want it start from 1 , change str(n) to str(i).