is there a way i can decorate my polar/radial plots with 'notches' at locations r=0 or r=outer_edge? below is an example. By 'notches' i mean the red and blue lines at the origin and at the edge of the plot.
The code below can be used to generate a polar plot.
import numpy as np
import matplotlib.pyplot as plt
data = [-121,87,118,87,109,-139,-112,115,153,-109,-106,-92,75,-98,103,-89,
152,114,77,-109,77,107,-77,106,-158,-71,-166,97,144,-166,138,39,130,
-71,-76,-82,128,74,-47,94,-119,130,76,-86,-85,108,-78,-96,-113,82,
127,-168,72,83,-61,-99,-83,-130,-69,43]
r = np.arange(0, len(data))
ax = plt.subplot(111,polar=True)
ax.scatter(data,r)
plt.show()
You could use annotate:
plt.annotate(' ',xy=(0, 0), # theta, radius
xytext = (-np.pi/10,len(data)/6.),
textcoords='data',
arrowprops=dict(facecolor='red', shrink=0.05))
plt.annotate(' ',xy=(np.pi/4.2, 1.355*max(r)), # theta, radius
xytext = (np.pi/4.2, 1.2*max(r)),
textcoords='data',
arrowprops=dict(facecolor='blue', shrink=0.05))
Or you can use plot:
rmax = ax.get_rmax()
theta_startstop = [2*[-np.pi/10],2*[np.pi/4.2]]
r_startstop = [[0,0.1*rmax],[0.9*rmax,rmax]]
notchcolor = ['red', 'blue']
for i in range(len(r_startstop)):
ax.plot(np.array(theta_startstop[i]), np.array(r_startstop[i]),
lw=3, c=notchcolor[i])
ax.set_rmax(rmax)
The result will be:
Related
So let's say I have a vector of numbers.
np.random.randn(5).round(2).tolist()
[2.05, -1.57, 1.07, 1.37, 0.32]
I want a draw a rectangle that shows this elements as numbers in a rectangle.
Something like this:
Is there an easy way to do this in matplotlib?
A bit convoluted but you could take advantage of seaborn.heatmap, creating a white colormap:
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
data = np.random.randn(5).round(2).tolist()
linewidth = 2
ax = sns.heatmap([data], annot=True, cmap=LinearSegmentedColormap.from_list('', ['w', 'w'], N=1),
linewidths=linewidth, linecolor='black', square=True,
cbar=False, xticklabels=False, yticklabels=False)
plt.tight_layout()
plt.show()
In this case, the external lines won't be as thick as the internal ones. If needed, this can be fixed with:
ax.axhline(y=0, color='black', lw=linewidth*2)
ax.axhline(y=1, color='black', lw=linewidth*2)
ax.axvline(x=0, color='black', lw=linewidth*2)
ax.axvline(x=len(data), color='black', lw=linewidth*2)
Edit: avoid these lines and add clip_on=False to sns.heatmap (thanks/credit #JohanC)
Output:
We can add rectangles , and annotate them in a for loop.
from matplotlib import pyplot as plt
import numpy as np
# Our numbers
nums = np.random.randn(5).round(2).tolist()
# rectangle_size
rectangle_size = 2
# We want rectangles look squared, you can change if you want
plt.rcParams["figure.figsize"] = [rectangle_size * len(nums), rectangle_size]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(len(nums)):
# We are adding rectangles
# You can change colors as you wish
plt.broken_barh([(rectangle_size * i, rectangle_size)], (0, rectangle_size), facecolors='white', edgecolor='black'
,linewidth = 1)
# We are calculating where to annotate numbers
cy = rectangle_size / 2.0
cx = rectangle_size * i + cy
# Annotation You can change color,font, etc ..
ax.annotate(str(nums[i]), (cx, cy), color='black', weight='bold', fontsize=20, ha='center', va='center')
# For squared look
plt.xlim([0, rectangle_size*len(nums)])
plt.ylim([0, rectangle_size])
# We dont want to show ticks
plt.axis('off')
plt.show()
One way using the Rectangle patch is:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
x = np.random.randn(5).round(2).tolist()
fig, ax = plt.subplots(figsize=(9, 2)) # make figure
dx = 0.15 # edge size of box
buf = dx / 10 # buffer around edges
# set x and y limits
ax.set_xlim([0 - buf, len(x) * dx + buf])
ax.set_ylim([0 - buf, dx + buf])
# set axes as equal and turn off axis lines
ax.set_aspect("equal")
ax.axis("off")
# draw plot
for i in range(len(x)):
# create rectangle with linewidth=4
rect = Rectangle((dx * i, 0), dx, dx, facecolor="none", edgecolor="black", lw=4)
ax.add_patch(rect)
# get text position
x0, y0 = dx * i + dx / 2, dx / 2
# add text
ax.text(
x0, y0, f"{x[i]}", color="black", ha="center", va="center", fontsize=28, fontweight="bold"
)
fig.tight_layout()
fig.show()
which gives:
I want to draw some circles using `ax3.scatter(x1, y1, s=r1 , facecolors='none', edgecolors='r'), where:
x1 and y1 are the coordinates of these circles
r1 is the radius of these circles
I thought typing s = r1 I would get the correct radius, but that's not the case.
How can I fix this?
If you change the value of 'r' (now 5) to your desired radius, it works. This is adapted from the matplotlib.org website, "Scatter Plots With a Legend". Should be scatter plots with attitude!
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(19680801)
fig, ax = plt.subplots()
for color in ['tab:blue', 'tab:orange', 'tab:green']:
r = 5 #radius
n = 750 #number of circles
x, y = np.random.rand(2, n)
#scale = 200.0 * np.random.rand(n)
scale = 3.14159 * r**2 #CHANGE r
ax.scatter(x, y, c=color, s=scale, label=color,
alpha=0.3, edgecolors='none')
ax.legend()
ax.grid(True)
plt.show()
I am trying to plot part of an matplotlib.patches.FancyArrowPatch in a dashed style. Using this post pyplot: Dotted line with FancyArrowPatch, I managed to get quite close to it :
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
plt.figure()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k', connectionstyle = "arc3, rad = -0.9" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
plt.gca().add_patch(arrow)
points = np.array([i[0] for i in arrow.get_path().iter_segments(curves = False)])
# arrow.remove()
a, = plt.plot(points[:-3,0], points[:-3,1])
plt.plot(points[-4:,0], points[-4:,1], linestyle = '--', color = a.get_color())
plt.tight_layout()
plt.show()
To my understanding, the blue line does not match the black one because iter_segments() converts curves into straight lines with a density of point too low.
How should I do to get a better result ?
You can evaluate the Bezier curve that is produced by the arrow manually.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.special import binom
fig, ax = plt.subplots()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k',
connectionstyle = "arc3, rad = -0.9" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
ax.add_patch(arrow)
bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)
def bezier(points, t=[0,1], num=200):
N = len(points)
t = np.linspace(*t, num=num)
curve = np.zeros((num, 2))
for i in range(N):
curve += np.outer(bernstein(N - 1, i, t), points[i])
return curve
verts = arrow.get_path().vertices
curve1 = bezier(verts, t=[0.0, 0.5], num=100)
curve2 = bezier(verts, t=[0.5, 1.0], num=100)
ax.plot(curve1[:,0], curve1[:,1], lw=3, color="crimson")
ax.plot(curve2[:,0], curve2[:,1], lw=3, ls="--", color="crimson")
plt.show()
As you notice the two curves, i.e. the original arrow and the manually created bezier curve, are not overlaying each other. This is because matplotlib evaluates the Bezier curve in screen space, while the manual version evaluates it in data space.
To obtain the same curve in both cases, we would need to do the evaluation in screen space, which is shown in the following (where we also plot the three bezier nodes, both in data and in pixel space).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.special import binom
fig, ax = plt.subplots()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k',
connectionstyle = "arc3, rad = -0.4" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
ax.add_patch(arrow)
ax.autoscale()
print(arrow.get_path().vertices)
bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)
def bezier(points, t=[0,1], num=200):
N = len(points)
t = np.linspace(*t, num=num)
curve = np.zeros((num, 2))
for i in range(N):
curve += np.outer(bernstein(N - 1, i, t), points[i])
return curve
trans = ax.transData
trans_inv = trans.inverted()
verts = trans.transform(arrow.get_path().vertices)
curve1 = trans_inv.transform(bezier(verts, t=[0.0, 0.5], num=100))
curve2 = trans_inv.transform(bezier(verts, t=[0.5, 1.0], num=100))
ax.plot(curve1[:,0], curve1[:,1], lw=3, color="crimson", zorder=0)
ax.plot(curve2[:,0], curve2[:,1], lw=3, ls="--", color="crimson", zorder=0)
from matplotlib.transforms import IdentityTransform
ax.plot(*trans.transform(arrow.get_path().vertices).T, ls="", marker="o",
color="C1", ms=7, transform=IdentityTransform())
ax.plot(*arrow.get_path().vertices.T, ls="", marker="o", color="C0", ms=3)
plt.show()
I have tried this and got the result as in the image:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list("", ["red","grey","green"])
df = pd.read_csv('t.csv', header=0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax = ax1.twiny()
# Scatter plot of positive points, coloured blue (C0)
ax.scatter(np.argwhere(df['real'] > 0), df.loc[df['real'] > 0, 'real'], color='C2')
# Scatter plot of negative points, coloured red (C3)
ax.scatter(np.argwhere(df['real'] < 0), df.loc[df['real'] < 0, 'real'], color='C3')
# Scatter neutral values in grey (C7)
ax.scatter(np.argwhere(df['real'] == 0), df.loc[df['real'] == 0, 'real'], color='C7')
ax.set_ylim([df['real'].min(), df['real'].max()])
index = len(df.index)
ymin = df['prediction'].min()
ymax= df['prediction'].max()
ax1.imshow([np.arange(index),df['prediction']],cmap=cmap,
extent=(0,index-1,ymin, ymax), alpha=0.8)
plt.show()
Image:
I was expecting one output where the color is placed according to the figure. I am getting green color and no reds or greys.
I want to get the image or contours spread as the values are. How I can do that? See the following image, something similar:
Please let me know how I can achieve this. The data I used is here: t.csv
For a live version, have a look at Tensorflow Playground
There are essentially 2 tasks required in a solution like this:
Plot the heatmap as the background;
Plot the scatter data;
Output:
Source code:
import numpy as np
import matplotlib.pyplot as plt
###
# Plot heatmap in the background
###
# Setting up input values
x = np.arange(-6.0, 6.0, 0.1)
y = np.arange(-6.0, 6.0, 0.1)
X, Y = np.meshgrid(x, y)
# plot heatmap colorspace in the background
fig, ax = plt.subplots(nrows=1)
im = ax.imshow(X, cmap=plt.cm.get_cmap('RdBu'), extent=(-6, 6, -6, 6), interpolation='bilinear')
cax = fig.add_axes([0.21, 0.95, 0.6, 0.03]) # [left, bottom, width, height]
fig.colorbar(im, cax=cax, orientation='horizontal') # add colorbar at the top
###
# Plot data as scatter
###
# generate the points
num_samples = 150
theta = np.linspace(0, 2 * np.pi, num_samples)
# generate inner points
circle_r = 2
r = circle_r * np.random.rand(num_samples)
inner_x, inner_y = r * np.cos(theta), r * np.sin(theta)
# generate outter points
circle_r = 4
r = circle_r + np.random.rand(num_samples)
outter_x, outter_y = r * np.cos(theta), r * np.sin(theta)
# plot data
ax.scatter(inner_x, inner_y, s=30, marker='o', color='royalblue', edgecolors='white', linewidths=0.8)
ax.scatter(outter_x, outter_y, s=30, marker='o', color='crimson', edgecolors='white', linewidths=0.8)
ax.set_ylim([-6,6])
ax.set_xlim([-6,6])
plt.show()
To keep things simple, I kept the colorbar range (-6, 6) to match the data range.
I'm sure this code can be changed to suit your specific needs. Good luck!
Here is a possible solution.
A few notes and questions:
What are the 'prediction' values in your data file? They do not seem to correlate with the values in the 'real' column.
Why do you create a second axis? What is represented on the bottom X-axis in your plot? I removed the second axis and labelled the remaining axes (index and real).
When you slice a pandas DataFrame, the index comes with it. You don't need to create a separate index (argwhere and arange(index) in your code). I simplified the first part of the code, where scatterplots are produced.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list("", ["red","grey","green"])
df = pd.read_csv('t.csv', header=0)
print(df)
fig = plt.figure()
ax = fig.add_subplot(111)
# Data limits
xmin = 0
xmax = df.shape[0]
ymin = df['real'].min()
ymax = df['real'].max()
# Scatter plots
gt0 = df.loc[df['real'] > 0, 'real']
lt0 = df.loc[df['real'] < 0, 'real']
eq0 = df.loc[df['real'] == 0, 'real']
ax.scatter(gt0.index, gt0.values, edgecolor='white', color='C2')
ax.scatter(lt0.index, lt0.values, edgecolor='white', color='C3')
ax.scatter(eq0.index, eq0.values, edgecolor='white', color='C7')
ax.set_ylim((ymin, ymax))
ax.set_xlabel('index')
ax.set_ylabel('real')
# We want 0 to be in the middle of the colourbar,
# because gray is defined as df['real'] == 0
if abs(ymax) > abs(ymin):
lim = abs(ymax)
else:
lim = abs(ymin)
# Create a gradient that runs from -lim to lim in N number of steps,
# where N is the number of colour steps in the cmap.
grad = np.arange(-lim, lim, 2*lim/cmap.N)
# Arrays plotted with imshow must be 2D arrays. In this case it will be
# 1 pixel wide and N pixels tall. Set the aspect ratio to auto so that
# each pixel is stretched out to the full width of the frame.
grad = np.expand_dims(grad, axis=1)
im = ax.imshow(grad, cmap=cmap, aspect='auto', alpha=1, origin='bottom',
extent=(xmin, xmax, -lim, lim))
fig.colorbar(im, label='real')
plt.show()
This gives the following result:
Here is my code to plot some data:
from scipy.interpolate import griddata
from numpy import linspace
import matplotlib.pyplot as plt
meanR = [9.95184937, 9.87947708, 9.87628496, 9.78414422,
9.79365258, 9.96168969, 9.87537519, 9.74536093,
10.16686878, 10.04425475, 10.10444126, 10.2917172 ,
10.16745917, 10.0235203 , 9.89914 , 10.11263505,
9.99756449, 10.17861254, 10.04704248]
koord = [[1,4],[3,4],[1,3],[3,3],[2,3],[1,2],[3,2],[2,2],[1,1],[3,1],[2,1],[1,0],[3,0],[0,3],[4,3],[0,2],[4,2],[0,1],[4,1]]
x,y=[],[]
for i in koord:
x.append(i[0])
y.append(i[1])
z = meanR
xi = linspace(-2,6,300);
yi = linspace(-2,6,300);
zi = griddata((x, y), z, (xi[None,:], yi[:,None]), method='cubic')
CS = plt.contourf(xi,yi,zi,15,cmap=plt.cm.jet)
plt.scatter(x,y,marker='o',c='b',s=15)
plt.xlim(min(x),max(x))
plt.ylim(min(y),max(y))
plt.show()
In result we have:
How can I inscribe it in a circle? something like this
Because you don't seem to need any axes you can also use a normal projection, remove the axes and draw a circle. I had some fun and added some bonus ears, a nose and a color bar. I annotated the code, I hope it is clear.
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import scipy.interpolate
import numpy
import matplotlib
import matplotlib.pyplot as plt
# close old plots
plt.close("all")
# some parameters
N = 300 # number of points for interpolation
xy_center = [2,2] # center of the plot
radius = 2 # radius
# mostly original code
meanR = [9.95184937, 9.87947708, 9.87628496, 9.78414422,
9.79365258, 9.96168969, 9.87537519, 9.74536093,
10.16686878, 10.04425475, 10.10444126, 10.2917172 ,
10.16745917, 10.0235203 , 9.89914 , 10.11263505,
9.99756449, 10.17861254, 10.04704248]
koord = [[1,4],[3,4],[1,3],[3,3],[2,3],[1,2],[3,2],[2,2],[1,1],[3,1],[2,1],[1,0],[3,0],[0,3],[4,3],[0,2],[4,2],[0,1],[4,1]]
x,y = [],[]
for i in koord:
x.append(i[0])
y.append(i[1])
z = meanR
xi = numpy.linspace(-2, 6, N)
yi = numpy.linspace(-2, 6, N)
zi = scipy.interpolate.griddata((x, y), z, (xi[None,:], yi[:,None]), method='cubic')
# set points > radius to not-a-number. They will not be plotted.
# the dr/2 makes the edges a bit smoother
dr = xi[1] - xi[0]
for i in range(N):
for j in range(N):
r = numpy.sqrt((xi[i] - xy_center[0])**2 + (yi[j] - xy_center[1])**2)
if (r - dr/2) > radius:
zi[j,i] = "nan"
# make figure
fig = plt.figure()
# set aspect = 1 to make it a circle
ax = fig.add_subplot(111, aspect = 1)
# use different number of levels for the fill and the lines
CS = ax.contourf(xi, yi, zi, 60, cmap = plt.cm.jet, zorder = 1)
ax.contour(xi, yi, zi, 15, colors = "grey", zorder = 2)
# make a color bar
cbar = fig.colorbar(CS, ax=ax)
# add the data points
# I guess there are no data points outside the head...
ax.scatter(x, y, marker = 'o', c = 'b', s = 15, zorder = 3)
# draw a circle
# change the linewidth to hide the
circle = matplotlib.patches.Circle(xy = xy_center, radius = radius, edgecolor = "k", facecolor = "none")
ax.add_patch(circle)
# make the axis invisible
for loc, spine in ax.spines.iteritems():
# use ax.spines.items() in Python 3
spine.set_linewidth(0)
# remove the ticks
ax.set_xticks([])
ax.set_yticks([])
# Add some body parts. Hide unwanted parts by setting the zorder low
# add two ears
circle = matplotlib.patches.Ellipse(xy = [0,2], width = 0.5, height = 1.0, angle = 0, edgecolor = "k", facecolor = "w", zorder = 0)
ax.add_patch(circle)
circle = matplotlib.patches.Ellipse(xy = [4,2], width = 0.5, height = 1.0, angle = 0, edgecolor = "k", facecolor = "w", zorder = 0)
ax.add_patch(circle)
# add a nose
xy = [[1.5,3], [2,4.5],[2.5,3]]
polygon = matplotlib.patches.Polygon(xy = xy, facecolor = "w", zorder = 0)
ax.add_patch(polygon)
# set axes limits
ax.set_xlim(-0.5, 4.5)
ax.set_ylim(-0.5, 4.5)
plt.show()
If you replace the part where you do the plotting with:
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
CS = ax.contourf(xi,yi,zi,15,cmap=plt.cm.jet)
ax.scatter(x,y,marker='o',c='b',s=15)
ax.set_xlim(min(x),max(x))
ax.set_ylim(min(y),max(y))
you get this
To get what you want, you have to rescale the x, y, xi, yi such that the image is centered in zero. You might also need to convert to polar coordinates. Now I don't have time to provide more info, but I hope that this helps you in getting started