I have the following plot
import numpy as np
import matplotlib.pyplot as plt
a = np.random.randn(4,4)
fig, ax = plt.subplots()
im = ax.imshow(a)
plt.axis('off')
plt.show()
output:
I am trying to add the following gridlines:
gridlines = np.array([
[0,2], [2,4],
[0,4],
[0,3], [3,4],
[0,1], [1,3],
])
such that the plot looks like this:
Finally, I want to add text in the center of each red rectangle:
Is there any documentation or tutorial for doing this with matplotlib?
Since you have define gridlines, it is easy to achieve that result by adding Rectangles and text annotations.
Note that in the following I have modified gridlines to be a dictionary, mapping each "row" to grids.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
gridlines = {
0: [[0,2], [2,4]],
1: [[0,4]],
2: [[0,3], [3,4]],
3: [[0,1], [1,3]],
}
a = np.random.randn(4,4)
fig, ax = plt.subplots()
im = ax.imshow(a)
plt.axis('off')
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
dx = (xmax - xmin) / 4
dy = (ymax - ymin) / 4
count = 1
for r, v in gridlines.items():
for (_xmin, _xmax) in v:
x = xmin + dx * _xmin
y = ymax - (r + 1) * dy
w = dx * (_xmax - _xmin)
ax.add_patch(Rectangle((x, y), w, dy, facecolor='none', edgecolor="r", linewidth=3))
ax.text(x + w / 2, ymax - (r + 1) * dy + dy / 2, "text%s" % count, ha="center", va="center", color="white")
count += 1
plt.show()
Related
The only references I can find online is placing a percentage on top of the barchart here, but none with connecting lines and placing some text between bars at the center of the connecting line.
Currently my chart looks like this:
df_revenue = compiled_data[compiled_data['Fee Name'] == "Item Price Credit"]
df_revenue = df_revenue.groupby(['Year']).sum()
df_revenue = df_revenue.reset_index()
sns.set_theme(font_scale=1.5, style="whitegrid")
g = sns.catplot(data=df_revenue, x='Year', y='Amount', kind='bar', height=6, aspect=12/6)
g.set(title="Yearly Revenues", xlabel = 'Year', ylabel='Amount')
sns.despine(left=True)
for ax in g.axes[:,0]:
ax.yaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
plt.show()
But I am hoping it to look like this and I am not sure what to work with:
You could loop through the bars, extract their dimensions and use those for the lines and the texts:
import seaborn as sns
import pandas as pd
from matplotlib import ticker
from matplotlib import pyplot as plt
df_revenue = pd.DataFrame({'Year': [2018, 2019, 2020], 'Amount': [100000, 1200000, 3200000]})
sns.set_theme(font_scale=1.5, style="whitegrid")
g = sns.catplot(data=df_revenue, x='Year', y='Amount', kind='bar', height=6, aspect=12 / 6)
g.set(title="Yearly Revenues", xlabel='Year', ylabel='Amount')
sns.despine(left=True)
for ax in g.axes[:, 0]:
ax.yaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
for ax in g.axes.ravel():
for bar_group in ax.containers:
prev = None
for bar in bar_group:
if prev != None:
x0 = prev.get_x() + prev.get_width()
x1 = bar.get_x()
y0 = prev.get_y() + prev.get_height()
y1 = bar.get_y() + bar.get_height()
ax.plot([x0, x1], [y0, y1], color='black', lw=2)
if y0 > 0 and y0 != y1:
ax.text((x0 + x1) / 2, (y0 + y1) / 2, f' {(y1 - y0) / y0 * 100:.1f} % ',
ha='right' if y0 < y1 else 'left', va='center')
prev = bar
plt.tight_layout()
plt.show()
Is there any way to create a 'wavy' arrow in matplotlib / python please?
Ideally, I'd like to recreate something like the following:
To reproduce the wavy arrow from the question, you may use a line plot and a triangle
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import matplotlib.patches as mpatches
fig, ax = plt.subplots()
x = np.linspace(0,9*np.pi,151)
y = np.sin(x)
ax.plot(x,y, color="gray", lw="3")
verts = np.array([[0,1],[0,-1],[2,0],[0,1]]).astype(float)*1.3
verts[:,0] += 9*np.pi
path = mpath.Path(verts)
patch = mpatches.PathPatch(path, fc='gray', ec="gray")
ax.add_patch(patch)
ax.axis("off")
ax.set_aspect("equal",'datalim')
ax.relim()
ax.autoscale_view()
plt.show()
I made a general version (function that returns a Patch for any given start and end coordinates) from the previous post snippet.
def curly_arrow(start, end, arr_size = 1, n = 5, col='gray', linew=1., width = 0.1):
xmin, ymin = start
xmax, ymax = end
dist = np.sqrt((xmin - xmax)**2 + (ymin - ymax)**2)
n0 = dist / (2 * np.pi)
x = np.linspace(0, dist, 151) + xmin
y = width * np.sin(n * x / n0) + ymin
line = plt.Line2D(x,y, color=col, lw=linew)
del_x = xmax - xmin
del_y = ymax - ymin
ang = np.arctan2(del_y, del_x)
line.set_transform(mpl.transforms.Affine2D().rotate_around(xmin, ymin, ang) + ax.transData)
ax.add_line(line)
verts = np.array([[0,1],[0,-1],[2,0],[0,1]]).astype(float) * arr_size
verts[:,1] += ymax
verts[:,0] += xmax
path = mpath.Path(verts)
patch = mpatches.PathPatch(path, fc=col, ec=col)
patch.set_transform(mpl.transforms.Affine2D().rotate_around(xmax, ymax, ang) + ax.transData)
return patch
arr_size - size of the arrow, linew - linewidth of the arrow, n - number of wiggles, width - "vertical" (latitudinal) size of the wiggles.
Example of usage:
fig, ax = plt.subplots()
ax.add_patch(curly_arrow((20, 20), (2, 10), n=10, arr_size=2))
ax.set_xlim(0,30)
ax.set_ylim(0,30)
PS. You'll also need to import:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib as mpl
I have data sets like (x,y,(z1,z2,z3..)). I am trying
plt.pcolor(x,y,z1)
plt.pcolor(x,y,z2)
plt.pcolor(x,y,z3)
plt.colorbar()
plt.show()
This is showing only the pcolor plot of the last data set. How can I plot all in same plot and same colorbar scale?
You could try with subplots, and make sure all the images with the same intensity scale (use the same vmin and vmax arguments of pcolor() for all your images). Below is an example:
import numpy as np
import matplotlib.pyplot as plt
dx, dy = 0.15, 0.05
y, x = np.mgrid[slice(-3, 3 + dy, dy),
slice(-3, 3 + dx, dx)]
z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
z1 = z[:-1, :-1]
z2 = z[:-1, :-1]
z3 = z[:-1, :-1]
z_min, z_max = -np.abs(z).max(), np.abs(z).max()
data = [[x,y,z1],[x,y,z2],[x,y,z3]]
# Plot each slice as an independent subplot
fig, axes = plt.subplots(nrows=1, ncols=3)
for dat, ax in zip(data, axes.flat):
# The vmin and vmax arguments specify the color limits
pc = ax.pcolor(dat[0],dat[1],dat[2], vmin=z_min, vmax=z_max)
# Make an axis for the colorbar on the right side
cax = fig.add_axes([0.9, 0.1, 0.03, 0.8])
fig.colorbar(pc, cax=cax)
plt.show()
It will show like this:
Right now there're some statistics plotted in 3d bar over (x, y). each bar height represents the density of the points in side the square grid of (x,y) plane. Right now, i can put different color on each bar. However, I want to put progressive color on the 3d bar, similar as the cmap, so the bar will be gradient filled depending on the density.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# height of the bars
z = np.ones((4, 4)) * np.arange(4)
# position of the bars
xpos, ypos = np.meshgrid(np.arange(4), np.arange(4))
xpos = xpos.flatten('F')
ypos = ypos.flatten('F')
zpos = np.zeros_like(xpos)
dx = 0.5 * np.ones_like(zpos)
dy = dx.copy()
dz = z.flatten()
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color='b', zsort='average')
plt.show()
Output the above code:
Let me first say that matplotlib may not be the tool of choice when it comes to sophisticated 3D plots.
That said, there is no built-in method to produce bar plots with differing colors over the extend of the bar.
We therefore need to mimic the bar somehow. A possible solution can be found below. Here, we use a plot_surface plot to create a bar that contains a gradient.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection= Axes3D.name)
def make_bar(ax, x0=0, y0=0, width = 0.5, height=1 , cmap="viridis",
norm=matplotlib.colors.Normalize(vmin=0, vmax=1), **kwargs ):
# Make data
u = np.linspace(0, 2*np.pi, 4+1)+np.pi/4.
v_ = np.linspace(np.pi/4., 3./4*np.pi, 100)
v = np.linspace(0, np.pi, len(v_)+2 )
v[0] = 0 ; v[-1] = np.pi; v[1:-1] = v_
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
xthr = np.sin(np.pi/4.)**2 ; zthr = np.sin(np.pi/4.)
x[x > xthr] = xthr; x[x < -xthr] = -xthr
y[y > xthr] = xthr; y[y < -xthr] = -xthr
z[z > zthr] = zthr ; z[z < -zthr] = -zthr
x *= 1./xthr*width; y *= 1./xthr*width
z += zthr
z *= height/(2.*zthr)
#translate
x += x0; y += y0
#plot
ax.plot_surface(x, y, z, cmap=cmap, norm=norm, **kwargs)
def make_bars(ax, x, y, height, width=1):
widths = np.array(width)*np.ones_like(x)
x = np.array(x).flatten()
y = np.array(y).flatten()
h = np.array(height).flatten()
w = np.array(widths).flatten()
norm = matplotlib.colors.Normalize(vmin=0, vmax=h.max())
for i in range(len(x.flatten())):
make_bar(ax, x0=x[i], y0=y[i], width = w[i] , height=h[i], norm=norm)
X, Y = np.meshgrid([1,2,3], [2,3,4])
Z = np.sin(X*Y)+1.5
make_bars(ax, X,Y,Z, width=0.2, )
plt.show()
I know the question is not very informative.. but as I do not know the name of his type of plot, I can not be more informative..
[EDIT] I changed the title, and now it is more informative...
You can do something similar with seaborn.swarmplot. I also use seaborn.boxplot (with the whiskers and caps turned off) to plot the mean and range:
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid")
tips = sns.load_dataset("tips")
ax = sns.swarmplot(x="day", y="total_bill", data=tips)
ax = sns.boxplot(x="day", y="total_bill", data=tips,
showcaps=False,boxprops={'facecolor':'None'},
showfliers=False,whiskerprops={'linewidth':0})
plt.show()
If (for whatever reason) you don't want to use seaborn, you can have a go at making them yourself (see e.g. this explanation: https://www.flerlagetwins.com/2020/11/beeswarm.html ).
A simple version is:
#!/usr/bin/env python3
import matplotlib.pyplot as plt
import numpy as np
def simple_beeswarm(y, nbins=None):
"""
Returns x coordinates for the points in ``y``, so that plotting ``x`` and
``y`` results in a bee swarm plot.
"""
y = np.asarray(y)
if nbins is None:
nbins = len(y) // 6
# Get upper bounds of bins
x = np.zeros(len(y))
ylo = np.min(y)
yhi = np.max(y)
dy = (yhi - ylo) / nbins
ybins = np.linspace(ylo + dy, yhi - dy, nbins - 1)
# Divide indices into bins
i = np.arange(len(y))
ibs = [0] * nbins
ybs = [0] * nbins
nmax = 0
for j, ybin in enumerate(ybins):
f = y <= ybin
ibs[j], ybs[j] = i[f], y[f]
nmax = max(nmax, len(ibs[j]))
f = ~f
i, y = i[f], y[f]
ibs[-1], ybs[-1] = i, y
nmax = max(nmax, len(ibs[-1]))
# Assign x indices
dx = 1 / (nmax // 2)
for i, y in zip(ibs, ybs):
if len(i) > 1:
j = len(i) % 2
i = i[np.argsort(y)]
a = i[j::2]
b = i[j+1::2]
x[a] = (0.5 + j / 3 + np.arange(len(b))) * dx
x[b] = (0.5 + j / 3 + np.arange(len(b))) * -dx
return x
fig = plt.figure(figsize=(2, 4))
fig.subplots_adjust(0.2, 0.1, 0.98, 0.99)
ax = fig.add_subplot(1, 1, 1)
y = np.random.gamma(20, 10, 100)
x = simple_beeswarm(y)
ax.plot(x, y, 'o')
fig.savefig('bee.png')