combined colour bar that works both as colour bar and legend - python

I recently came across a powerpoint slide containing a nice plot which I suspect is made rather with Julia or Python. The person giving the talk didn't give details on how to plot this (maybe he thought I was trying to get his data rather than the actual plot style).
Does anyone know how to produce this type of side colour bar? As you can see from the image attached the colour bar works both as colour bar and legend for the different curves plotted.

Here is some code to create a similar colorbar:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.colors import LinearSegmentedColormap
def burr_pdf(x, c, k):
return c * k * (x ** (c - 1)) / (1 + x ** c) ** (k + 1)
xs = np.linspace(0.0001, 3, 300)
c_k_params = [(1, 1), (1, 2), (1, 3), (2, 1), (3, 1), (0.5, 2)]
curve_labels = [f'c={c}; k={k}' for c, k in c_k_params]
curve_colors = np.linspace(0.98, 0.02, len(c_k_params))
cmap = cm.get_cmap('jet')
special_map_list = [tuple((c+2)/3 for c in cmap(i/256)) for i in range(256)] # "whiten" the colors
for c in curve_colors:
special_map_list[int(c*256)] = cmap(c)
special_map_list[int(c*256)-1] = cmap(c)
special_map = LinearSegmentedColormap.from_list('', special_map_list)
fig, ax = plt.subplots(figsize=(7, 7))
for (c, k), col in zip(c_k_params, curve_colors):
ax.plot(xs, burr_pdf(xs, c, k), color=cmap(col))
ax.set_title('Burr – probability density function')
ax.set_ylim((0, 2))
ax.set_xlim((0, 3))
cbar = plt.colorbar(cm.ScalarMappable(norm=None, cmap=special_map), ax=ax, ticks=curve_colors)
cbar.ax.set_yticklabels(curve_labels)
plt.show()
The curves are from Wikipedia's Burr distribution.
The plot:

Related

Wrong result trying to plot a spider-web graph in matplotlib

I am practicing how to use the matplotlib and pyplot library, and for that very reason I'm trying to make a function that plots points so that any two points have a line that
connects them.
I think I'm close to solving the problem, but the result still seems a bit off.
My code is:
import numpy as np
import matplotlib.pyplot as plt
alpha = (np.sqrt(2)/2)
square_points = ((0, 0),(1, 0),(0, 1),(1, 1))
shape_points = ((1, 0),(alpha, alpha),(0, 1),(-alpha, alpha),(-1, 0),(-alpha, -alpha),(0, -1),(alpha, -alpha))
def complete_graph(points):
for i in range(len(points)):
for j in range(i):
x = (points[i])
y = (points[j])
plt.plot(x, y)
plt.show()
complete_graph(square_points) #square shape
complete_graph(shape_points) #spider web ish shape
The result is supposed to look like this:
Square shape
Spider web shape
My result however is:
For what is supposed to be a square shape:
For what is supposed to be a spiderweb-ish shape
You need to have the x and y coordinates separately. The simplest would be x=[points[i][0], points[j][0]] and y=[points[i][1], points[j][1]].
Using numpy, the code could be written creating all x. The vertices can be drawn using plt.scatter(). Setting the z-order to 3 shows them in front of the edges.
import numpy as np
import matplotlib.pyplot as plt
alpha = np.sqrt(2) / 2
square_points = ((0, 0), (1, 0), (0, 1), (1, 1))
shape_points = ((1, 0), (alpha, alpha), (0, 1), (-alpha, alpha), (-1, 0), (-alpha, -alpha), (0, -1), (alpha, -alpha))
def complete_graph(points):
# calculate and plot the edges
edges = np.array([(points[i], points[j]) for i in range(len(points)) for j in range(i)]).reshape(-1, 2)
plt.plot(edges[:, 0], edges[:, 1], color='dodgerblue')
points = np.array(points)
# plot the vertices
plt.scatter(points[:, 0], points[:, 1], color='mediumvioletred', s=100, zorder=3)
plt.axis('equal') # show squares as squares (x and y with the same distances)
plt.axis('off') # hide the surrounding rectangle and ticks
plt.show()
complete_graph(square_points) # square shape
complete_graph(shape_points) # spider web ish shape

matplotlib preserve aspect ratio in Wedge patches (pie charts)

I want to plot colored pie charts at specific positions without distorting their circular aspect ratio. I'm using Wedge patches because I could not find a better solution. Here is the code
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches, collections
fig, axes = plt.subplots()
for i in range(20):
x = np.random.uniform(low=0, high=1, size=10).cumsum()
axes.scatter(x=x, y=np.repeat(i, x.shape[0]), c='gray', s=1)
pies = []
N = 4
cmap = plt.cm.get_cmap("hsv", N + 1)
colors = list(map(cmap, range(N)))
print(colors)
for i in range(2, 2 + N):
thetas = np.linspace(0, 360, num=i)
assert len(thetas) - 1 <= len(colors)
for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
wedge = patches.Wedge((i, i), r=i / 10, theta1=theta1, theta2=theta2,
color=c)
pies.append(wedge)
axes.add_collection(collections.PatchCollection(pies,
match_original=True))
plt.show()
How to preserve the aspect ratio of pie charts? Setting axes.set_aspect("equal") is NOT an option because it squeezes the plot completely when I have more data points.
I've been looking at how to draw circles and preserve the aspect ratio but the solution cannot be adopted here - I'm plotting Wedges/pie charts, not Circles.
I also looked at matplotlib transforms but couldn't find the answer there either.
I tried the same thing, and matplotlib really doesn't try to make this easy for you, but I found a solution that you should be able to use.
You need to separate the centers from the wedges and add them to the PatchCollection as offsets. Then you can apply different transforms to the offsets (transOffset) and shape (transform).
Notice that I have changed the r-value (radius). This value is no longer in data coordinates, so it should always be the same size, regardless of how much you zoom, but it is too small to be visible at i/10.
from matplotlib import patches, collections, transforms
offsets = []
for i in range(2, 2 + N):
thetas = np.linspace(0, 360, num=i)
assert len(thetas) - 1 <= len(colors)
for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
wedge = patches.Wedge((0, 0), r=10, theta1=theta1, theta2=theta2,
color=c)
offsets.append((i, i))
pies.append(wedge)
coll = collections.PatchCollection(
pies, match_original=True, offsets=offsets,
transform=transforms.IdentityTransform(),
transOffset=axes.transData
)
It works fine for me when I set set_aspect('equal'):
Image is narrowed because y-range is longer than x-range I think.
If you'd set y_lim between 0 and a number lower than y_max, you'd see it better:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches, collections
fig, axes = plt.subplots()
for i in range(20):
x = np.random.uniform(low=0, high=1, size=10).cumsum()
axes.scatter(x=x, y=np.repeat(i, x.shape[0]), c='gray', s=1)
pies = []
N = 4
cmap = plt.cm.get_cmap("hsv", N + 1)
colors = list(map(cmap, range(N)))
print(colors)
for i in range(2, 2 + N):
thetas = np.linspace(0, 360, num=i)
assert len(thetas) - 1 <= len(colors)
for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
wedge = patches.Wedge((i, i), r=i / 10, theta1=theta1, theta2=theta2,
color=c)
pies.append(wedge)
axes.add_collection(collections.PatchCollection(pies,
match_original=True))
axes.set_aspect('equal')
axes.set_ylim(0,7.5)
plt.show()

How to make jitterplot on matplolib python

Here is my code (adapted from here):
df_1 = pd.DataFrame({'Cells' : np.arange(0,100), 'Delta_7' : np.random.rand(100,), 'Delta_10' : np.random.rand(100,), 'Delta_14' : np.random.rand(100,)}, columns = ['Cells','Delta_7', 'Delta_10', 'Delta_14'])
#figure
fig, ax1 = plt.subplots()
fig.set_size_inches(13, 10)
#c sequence
c = df_1['Delta_7']
#plot
plt.scatter(np.full((len(df_1), 1), 1), df_1['Delta_7'] , s = 50, c=c, cmap = 'viridis')
plt.scatter(np.full((len(df_1), 1), 2), df_1['Delta_10'] , s = 50, c=c, cmap = 'viridis')
plt.scatter(np.full((len(df_1), 1), 3), df_1['Delta_14'] , s = 50, c=c, cmap = 'viridis')
cbar = plt.colorbar()
I would like to make a beautiful jitterplot (like on R or seaborn) with matplotlib. The thing is that I would like to give each cell a color based on its 'Delta_7' value. And this color would be kept when plotting 'Delta_10' and 'Delta_14', that I didn't manage to do with seaborn.
Please, could you let me know if you have any clue (python package, coding tricks …)?
Kindly,
The positions of the dots can be obtained from the list returned by scatter. These positions can be jittered, for example only in the x-direction. Possibly the range of the x-axis needs to be extended a bit to show every displaced dot.
Here is some code to start experimenting:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
def jitter_dots(dots):
offsets = dots.get_offsets()
jittered_offsets = offsets
# only jitter in the x-direction
jittered_offsets[:, 0] += np.random.uniform(-0.3, 0.3, offsets.shape[0])
dots.set_offsets(jittered_offsets)
df_1 = pd.DataFrame({'Cells': np.arange(0, 100),
'Delta_7': np.random.rand(100),
'Delta_10': np.random.rand(100),
'Delta_14': np.random.rand(100)})
fig, ax1 = plt.subplots()
columns = df_1.columns[1:]
c = df_1['Delta_7']
for i, column in enumerate(columns):
dots = plt.scatter(np.full((len(df_1), 1), i), df_1[column], s=50, c=c, cmap='plasma')
jitter_dots(dots)
plt.xticks(range(len(columns)), columns)
xmin, xmax = plt.xlim()
plt.xlim(xmin - 0.3, xmax + 0.3) # make some room to show the jittered dots
cbar = plt.colorbar()
plt.show()

Mathplotlib draw delta on curve + bars under function

I have this code:
import matplotlib.pyplot as plt
import numpy as np
gamma = 0.5
p = np.linspace(1/253, 253/253, 253)
y = np.power(p, gamma)
plt.plot(p, y)
plt.xlabel('p')
plt.ylabel('\u03C6(p)')
plt.title(' ')
plt.show()
which gives me this figure: https://ibb.co/M7B1PD1
And I have this code:
p = np.linspace(1/253, 253/253, 253)
b = 0.5
phi_p = np.power((b * p), (b-1))
plt.plot(p, phi_p)
plt.xlabel('p')
plt.ylabel('\u03A6(p)')
plt.title(' ')
plt.show()
which gives me this figure: https://ibb.co/5xRR1k6
Now I want this for the first figure:https://ibb.co/nzdpfNc
and this for the second one: https://ibb.co/yYCp7HD
So in the first picture the delta of the curve should be shown and in the second picture a bar for each i in (0,253) should be drawn under the curve.
Is there I way to do this with mathplotlib? I had the idea to draw a histogram under the curve but I don't solve this well..
To plot the delta under the curve, just pair up your data points one at a time and then plot it. For instance
x_data=[1,4,6]
y_data=[2,3,7]
delta_to_plot_x=[1,4,4,6,6]
delta_to_plot_y=[2,2,3,3,7]
and this function should work at generating the right values.
def make_delta_for_plot(x,y):
new_x=[val for val in x for _ in (0, 1)]
new_y=[val for val in y for _ in (0, 1)]
new_x.pop(0)
new_y.pop(-1)
return new_x,new_y
For the second plot, seaborn.distplot can plot distributions with histograms at the same time (and it uses matplotlib so it will work fine with the rest of your code). There are plenty of examples in the link
I solved the secound figure this way:
b = 0.5
p = np.array(np.arange(1/253, 253/253, 1/253))
phi_p = np.power((b * p), (b-1))
x = np.array(np.arange(1/253,253/253,1/253))
liste = []
for i in phi_p:
liste.append(i)
y = liste
plt.plot(p, phi_p, color='orange')
plt.bar(x0, liste, width=(1/253), edgecolor='black')
plt.xlabel('p')
plt.ylabel('\u03A6(p)')
plt.title(' ')
plt.show()

plot with polycollection disappears when polygons get too small

I'm using a PolyCollection to plot data of various sizes. Sometimes the polygons are very small. If they are too small, they don't get plotted at all. I would expect the outline at least to show up so you'd have an idea that some data is there. Is there a a setting to control this?
Here's some code to reproduce the problem, as well as the output image:
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from matplotlib import colors
fig = plt.figure()
ax = fig.add_subplot(111)
verts = []
edge_col = colors.colorConverter.to_rgb('lime')
face_col = [(2.0 + val) / 3.0 for val in edge_col] # a little lighter
for i in range(10):
w = 0.5 * 10**(-i)
xs = [i - w, i - w, i + w, i - w]
ys = [-w, w, 0, -w]
verts.append(list(zip(xs, ys)))
ax.set_xlim(-1, 11)
ax.set_ylim(-2, 2)
ax.add_collection(PolyCollection(verts, lw=3, alpha=0.5, edgecolor=edge_col, facecolor=face_col))
plt.savefig('out.png')
Notice that only six polygons are visible, whereas there should be ten.
Edit: I understand I could zoom in to see the others, but I was hoping to see a dot or the outline or something without doing this.
Edit 2: Here's a way to get the desired effect, by plotting the faces using a PolyCollection and then the edges using a series of Line2D plots with markers, based on Patol75's answer. My application is a matplotlib animation with lots of polygons, so I'd prefer to avoid Line2D for efficiency, and it would be cleaner if I didn't need to plot things twice, so I'm still hoping for a better answer.
ax.add_collection(PolyCollection(verts, lw=3, alpha=0.5, edgecolor=None, facecolor=face_col, zorder=1))
for pts in verts:
ax.add_line(Line2D([pt[0] for pt in pts], [pt[1] for pt in pts], lw=3, alpha=0.5, color=edge_col,
marker='.', ms=1, mec=edge_col, solid_capstyle='projecting', zorder=2))
Zooming in your plotting window, you would notice that your two remaining polygons are being plotted. They are just too small for you to see them. One way to be convinced of this is to replace
ax.set_xlim(-1, 6)
ax.set_ylim(-2, 2)
by
ax.set_xlim(1e-1, 1e1)
ax.set_ylim(1e-5, 1e0)
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_aspect('equal')
Your five polygons are now visible, but on the downside the log scale restrains you to the positive side of the axes.
Now to propose an answer to your problem. If you keep a linear axis, as your polygons sizes span multiple orders of magnitude, you will not be able to see them all. What you can do is add an artist on your plot which specifies their location. This can be done with a marker, an arrow, etc... If we take the example of a marker, as you said, we only want to see this marker if we cannot see the polygon. The keyword zorder in the call to plot() allows to specify which artist should have the display priority on the figure. Please consider the example below.
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
fig = plt.figure()
ax = fig.add_subplot(111)
verts = []
for i in range(5):
w = 0.5 * 10**(-i)
xs = [i - w, i - w, i + w, i + w, i - w]
ys = [-w, w, w, -w, -w]
ax.plot((xs[2] + xs[1]) / 2, (ys[1] + ys[0]) / 2, linestyle='none',
marker='o', color='xkcd:crimson', markersize=1, zorder=-1)
verts.append(list(zip(xs, ys)))
ax.set_xlim(-1, 6)
ax.set_ylim(-2, 2)
poly = PolyCollection(verts, lw=5, edgecolor='black', facecolor='gray')
ax.add_collection(poly)
plt.show()
which produces
You would notice that if you zoom on the last two dots in the matplotlib figure, you actually do not see the markers, but rather the polygons.
You may introduce some minimal unit minw, which is the smallest size a shape can have.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from matplotlib import colors
fig = plt.figure()
ax = fig.add_subplot(111)
verts = []
edge_col = colors.colorConverter.to_rgb('lime')
face_col = [(2.0 + val) / 3.0 for val in edge_col] # a little lighter
ax.set_xlim(-1, 11)
ax.set_ylim(-2, 2)
u = np.diff(np.array([ax.get_xlim(), ax.get_ylim()]), axis=1).min()
px = np.max(fig.get_size_inches())*fig.dpi
minw = u/px/2
for i in range(10):
w = 0.5 * 10**(-i)
if w < minw:
w = minw
xs = [i - w, i - w, i + w, i - w]
ys = [-w, w, 0, -w]
verts.append(list(zip(xs, ys)))
ax.add_collection(PolyCollection(verts, lw=3, alpha=0.5, edgecolor=edge_col, facecolor=face_col))
plt.savefig('out.png')
plt.show()

Categories

Resources