Label python data points on plot - python

I searched for ages (hours which is like ages) to find the answer to a really annoying (seemingly basic) problem, and because I cant find a question that quite fits the answer I am posting a question and answering it in the hope that it will save someone else the huge amount of time I just spent on my noobie plotting skills.
If you want to label your plot points using python matplotlib
from matplotlib import pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
A = anyarray
B = anyotherarray
plt.plot(A,B)
for i,j in zip(A,B):
ax.annotate('%s)' %j, xy=(i,j), xytext=(30,0), textcoords='offset points')
ax.annotate('(%s,' %i, xy=(i,j))
plt.grid()
plt.show()
I know that xytext=(30,0) goes along with the textcoords, you use those 30,0 values to position the data label point, so its on the 0 y axis and 30 over on the x axis on its own little area.
You need both the lines plotting i and j otherwise you only plot x or y data label.
You get something like this out (note the labels only):
Its not ideal, there is still some overlap - but its better than nothing which is what I had..

How about print (x, y) at once.
from matplotlib import pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
A = -0.75, -0.25, 0, 0.25, 0.5, 0.75, 1.0
B = 0.73, 0.97, 1.0, 0.97, 0.88, 0.73, 0.54
ax.plot(A,B)
for xy in zip(A, B): # <--
ax.annotate('(%s, %s)' % xy, xy=xy, textcoords='data') # <--
ax.grid()
plt.show()

I had a similar issue and ended up with this:
For me this has the advantage that data and annotation are not overlapping.
from matplotlib import pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
A = -0.75, -0.25, 0, 0.25, 0.5, 0.75, 1.0
B = 0.73, 0.97, 1.0, 0.97, 0.88, 0.73, 0.54
plt.plot(A,B)
# annotations at the side (ordered by B values)
x0,x1=ax.get_xlim()
y0,y1=ax.get_ylim()
for ii, ind in enumerate(np.argsort(B)):
x = A[ind]
y = B[ind]
xPos = x1 + .02 * (x1 - x0)
yPos = y0 + ii * (y1 - y0)/(len(B) - 1)
ax.annotate('',#label,
xy=(x, y), xycoords='data',
xytext=(xPos, yPos), textcoords='data',
arrowprops=dict(
connectionstyle="arc3,rad=0.",
shrinkA=0, shrinkB=10,
arrowstyle= '-|>', ls= '-', linewidth=2
),
va='bottom', ha='left', zorder=19
)
ax.text(xPos + .01 * (x1 - x0), yPos,
'({:.2f}, {:.2f})'.format(x,y),
transform=ax.transData, va='center')
plt.grid()
plt.show()
Using the text argument in .annotate ended up with unfavorable text positions.
Drawing lines between a legend and the data points is a mess, as the location of the legend is hard to address.

Related

Isochrone plot in polar coordinates

So I have some data in spherical coords, but r is not important. So I really have (theta,phi,value), where theta goes 0-360 deg and phi 0-90 deg... Values go from -40 to 40 ... I can plot this data using pcolormesh on a polar diagram,
phis2 = np.linspace(0.001,63,201)
thetas2 = np.linspace(0,2*np.pi,201)
# Using same number of samples in phi and thera to simplify plotting
print(phis2.shape,thetas2.shape)
X,Y = np.meshgrid(thetas2,phis2)
doppMap2 =orbits.doppler(X*units.rad,Y*deg) # Calling function with a vector: MUCH faster than looping as above
fig, ax = plt.subplots(figsize=(8,7),subplot_kw=dict(projection='polar'))
im=ax.pcolormesh(X,Y,doppMap2,cmap=mpl.cm.jet_r, edgecolors='face')
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.set_xticks([x for x in np.linspace(0,2*np.pi,13)][:-1]) # ignore label 360
ax.grid(True)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.text(.6, 1.025, "Nadir ang", transform=ax.transAxes, fontsize=14)
## Add colorbar
cbar_ax = fig.add_axes([0.95, 0.15, 0.015, 0.7])
cbar = fig.colorbar(im, cax=cbar_ax)
cbar.ax.tick_params(labelsize=14)
#cbar.ax.set_yticklabels(['1', '2', '4', '6', '10', maxCV], size=24)
#cbar.set_label(r"log ($P(\overline{Z_{G}} /Z_{\odot})$ / $d(M_{G}/M_{\odot})$)",fontsize=36)
cbar.set_label(r"$d$f [kHz]",fontsize=24)
gc.collect()
but I'd like to generate isochrone lines instead. How would I do that?
Data for doppMap2 is here...
Matplotlib calls that a contour map:
# answering https://stackoverflow.com/questions/74073323/isochrone-plot-in-polar-coordinates
import numpy as np
import pandas
import matplotlib as mpl
import matplotlib.pyplot as plt
phis2 = np.linspace(0.001,63,201)
thetas2 = np.linspace(0,2*np.pi,201)
# Using same number of samples in phi and thera to simplify plotting
print(phis2.shape,thetas2.shape)
X,Y = np.meshgrid(thetas2,phis2)
# doppMap2 = orbits.doppler(X*units.rad,Y*deg) # Calling function with a vector: MUCH faster than looping as above
doppMap2 = pandas.read_csv('dopMap.csv', header=None)
print(doppMap2.shape)
fig, ax = plt.subplots(figsize=(8,7),subplot_kw=dict(projection='polar'))
im = ax.contour(X, Y, doppMap2, 12)
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.set_xticks([x for x in np.linspace(0,2*np.pi,13)][:-1]) # ignore label 360
ax.grid(True)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.text(.6, 1.025, "Nadir ang",
transform=ax.transAxes, fontsize=14)
## Add colorbar
cbar_ax = fig.add_axes([0.95, 0.15, 0.015, 0.7])
cbar = fig.colorbar(im, cax=cbar_ax)
cbar.ax.tick_params(labelsize=14)
cbar.set_label(r"$d$f [kHz]",fontsize=24)
plt.show()

Modifying saved plot with matplotlib

I am having a problem right now. I have run an extremely heavy simulation and, thus, generated a plot with matplotlib containing the results and saved it (as .jpg). However, there are some elemnts of the plot I would like to change, such as labels size and one vertical line. Is there a straighforward way to do this using matplotlib? I know I could have stored the data and now just replot changing the parameters (and, actually, I have done this), but I was wondering whether there is an easier way. Maybe something like:
fig, ax = plt.figure(path_to_figure)
ax.set_ylabel("Y_label")
...
You can refer to below example, which gives you more idea on how you can do this while plotting everything.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
%matplotlib inline
plt.rc('text', usetex=True)
def f(t):
return t ** 2
t1 = np.arange(0.0, 2.0, 0.1)
noise = np.random.randn(len(t1)) * 0.04
# x coordinates for the lines
xcoords = [0.1, 0.3, 0.5]
# colors for the lines
colors = ['r','k','b']
fig = plt.figure(figsize=(4, 3), dpi=200)
ax = fig.add_subplot(1, 1, 1)
plt.scatter(t1, f(t1 + noise), color = 'hotpink', label='Values obtained by experiment', edgecolors='k')
plt.plot(t1, f(t1), ls='solid', label='Theoretical expectation', color='b')
plt.title(r'This is latex title example $\mathbf{E = m \times c^2}$', fontsize='small')
for xc,c in zip(xcoords,colors):
plt.axvline(x=xc, label='line at x = {}'.format(xc), c=c)
plt.grid()
plt.legend(loc=0)
If you want to make all the fonts bold, you can also use below code to make everything bold:
font = {'weight' : 'bold',
'size' : 14 }
plt.rc('font', **font)
def f(t):
return t ** 2
t1 = np.arange(0.0, 2.0, 0.1)
noise = np.random.randn(len(t1)) * 0.04
# x coordinates for the lines
xcoords = [0.1, 0.3, 0.5]
# colors for the lines
colors = ['r','k','b']
fig = plt.figure(figsize=(4, 3), dpi=200)
ax = fig.add_subplot(1, 1, 1)
plt.scatter(t1, f(t1 + noise), color = 'hotpink', label='Values obtained by experiment', edgecolors='k')
plt.plot(t1, f(t1), ls='solid', label='Theoretical expectation', color='b')
plt.title(r'This is latex title example $\mathbf{E = m \times c^2}$', fontsize='small')
plt.xlabel("This is X-label.", fontsize=12)
plt.ylabel("This is Y-label.", fontsize=16)
for xc,c in zip(xcoords,colors):
plt.axvline(x=xc, label='line at x = {}'.format(xc), c=c)
plt.grid()
plt.legend(loc=(1.15,0.2))

How do I smooth out the edges of a closed line similar to d3's curveCardinal method implementation?

I have a few data points that I am connecting using a closed line plot, and I want the line to have smooth edges similar to how the curveCardinal methods in d3 do it. Link Here
Here's a minimal example of what I want to do:
import numpy as np
from matplotlib import pyplot as plt
x = np.array([0.5, 0.13, 0.4, 0.5, 0.6, 0.7, 0.5])
y = np.array([1.0, 0.7, 0.5, 0.2, 0.4, 0.6, 1.0])
fig, ax = plt.subplots()
ax.plot(x, y)
ax.scatter(x, y)
Now, I'd like to smooth out/interpolate the line similar to d3's curveCardinal methods. Here are a few things that I've tried.
from scipy import interpolate
tck, u = interpolate.splprep([x, y], s=0, per=True)
xi, yi = interpolate.splev(np.linspace(0, 1, 100), tck)
fig, ax = plt.subplots(1, 1)
ax.plot(xi, yi, '-b')
ax.plot(x, y, 'k')
ax.scatter(x[:2], y[:2], s=200)
ax.scatter(x, y)
The result of the above code is not bad, but I was hoping that the curve would stay closer to the line when the data points are far apart (I increased the size of two such data points above to highlight this). Essentially, have the curve stay close to the line.
Using interp1d (has the same problem as the code above):
from scipy.interpolate import interp1d
x = [0.5, 0.13, 0.4, 0.5, 0.6, 0.7, 0.5]
y = [1.0, 0.7, 0.5, 0.2, 0.4, 0.6, 1.0]
orig_len = len(x)
x = x[-3:-1] + x + x[1:3]
y = y[-3:-1] + y + y[1:3]
t = np.arange(len(x))
ti = np.linspace(2, orig_len + 1, 10 * orig_len)
kind='cubic'
xi = interp1d(t, x, kind=kind)(ti)
yi = interp1d(t, y, kind=kind)(ti)
fig, ax = plt.subplots()
ax.plot(xi, yi, 'g')
ax.plot(x, y, 'k')
ax.scatter(x, y)
I also looked at the Chaikins Corner Cutting algorithm, but I don't like the result.
def chaikins_corner_cutting(coords, refinements=5):
coords = np.array(coords)
for _ in range(refinements):
L = coords.repeat(2, axis=0)
R = np.empty_like(L)
R[0] = L[0]
R[2::2] = L[1:-1:2]
R[1:-1:2] = L[2::2]
R[-1] = L[-1]
coords = L * 0.75 + R * 0.25
return coords
fig, ax = plt.subplots()
ax.plot(x, y, 'k', linewidth=1)
ax.plot(chaikins_corner_cutting(x, 4), chaikins_corner_cutting(y, 4))
I also, superficially, looked at Bezier curves, matplotlibs PathPatch, and Fancy box implementations, but I couldn't get any satisfactory results.
Suggestions are greatly appreciated.
So, here's how I ended up doing it. I decided to introduce new points between every two existing data points. The following image shows how I am adding these new points. Red are data that I have. Using a convex hull I calculate the geometric center of the data points and draw lines to it from each point (shown with blue lines). Divide these lines twice in half and connect the resulting points (green line). The center of the green line is the new point added.
Here are the functions that accomplish this:
def midpoint(p1, p2, sf=1):
"""Calculate the midpoint, with an optional
scaling-factor (sf)"""
xm = ((p1[0]+p2[0])/2) * sf
ym = ((p1[1]+p2[1])/2) * sf
return (xm, ym)
def star_curv(old_x, old_y):
""" Interpolates every point by a star-shaped curve. It does so by adding
"fake" data points in-between every two data points, and pushes these "fake"
points towards the center of the graph (roughly 1/4 of the way).
"""
try:
points = np.array([old_x, old_y]).reshape(7, 2)
hull = ConvexHull(points)
x_mid = np.mean(hull.points[hull.vertices,0])
y_mid = np.mean(hull.points[hull.vertices,1])
except:
x_mid = 0.5
y_mid = 0.5
c=1
x, y = [], []
for i, j in zip(old_x, old_y):
x.append(i)
y.append(j)
try:
xm_i, ym_i = midpoint((i, j),
midpoint((i, j), (x_mid, y_mid)))
xm_j, ym_j = midpoint((old_x[c], old_y[c]),
midpoint((old_x[c], old_y[c]), (x_mid, y_mid)))
xm, ym = midpoint((xm_i, ym_i), (xm_j, ym_j))
x.append(xm)
y.append(ym)
c += 1
except IndexError:
break
orig_len = len(x)
x = x[-3:-1] + x + x[1:3]
y = y[-3:-1] + y + y[1:3]
t = np.arange(len(x))
ti = np.linspace(2, orig_len + 1, 10 * orig_len)
kind='quadratic'
xi = interp1d(t, x, kind=kind)(ti)
yi = interp1d(t, y, kind=kind)(ti)
return xi, yi
Here's how it looks:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.spatial import ConvexHull
x = [0.5, 0.13, 0.4, 0.5, 0.6, 0.7, 0.5]
y = [1.0, 0.7, 0.5, 0.2, 0.4, 0.6, 1.0]
xi, yi = star_curv(x, y)
fig, ax = plt.subplots()
ax.plot(xi, yi, 'g')
ax.plot(x, y, 'k', alpha=0.5)
ax.scatter(x, y, color='r')
The result is especially noticeable when the data points are more symmetric, for example the following x, y values give the results in the image below:
x = [0.5, 0.32, 0.34, 0.5, 0.66, 0.65, 0.5]
y = [0.71, 0.6, 0.41, 0.3, 0.41, 0.59, 0.71]
Comparison between the interpolation presented here, with the default interp1d interpolation.
I would create another array with the vertices extended in/out or up/down by about 5%. So if a point is lower than the average of the neighbouring points, make it a bit lower still.
Then do a linear interpolation between the new points, say 10 points/edge. Finally do a spline between the second last point per edge and the actual vertex. If you use Bezier curves, you can make the spline come in at the same angle on each side.
It's a bit of work, but of course you can use this anywhere.

Drawing heat map in python

I'm having two lists x, y representing coordinates in 2D. For example x = [1,4,0.5,2,5,10,33,0.04] and y = [2,5,44,0.33,2,14,20,0.03]. x[i] and y[i] represent one point in 2D. Now I also have a list representing "heat" values for each (x,y) point, for example z = [0.77, 0.88, 0.65, 0.55, 0.89, 0.9, 0.8,0.95]. Of course x,y and z are much higher dimensional than the example.
Now I would like to plot a heat map in 2D where x and y represents the axis coordinates and z represents the color. How can this be done in python?
This code produces a heat map. With a few more data points, the plot starts looking pretty nice and I've found it to be very quick in general even for >100k points.
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
import math
x = [1,4,0.5,2,5,10,33,0.04]
y = [2,5,44,0.33,2,14,20,0.03]
z = [0.77, 0.88, 0.65, 0.55, 0.89, 0.9, 0.8, 0.95]
levels = [0.7, 0.75, 0.8, 0.85, 0.9]
plt.figure()
ax = plt.gca()
ax.set_aspect('equal')
CS = ax.tricontourf(x, y, z, levels, cmap=plt.get_cmap('jet'))
cbar = plt.colorbar(CS, ticks=np.sort(np.array(levels)),ax=ax, orientation='horizontal', shrink=.75, pad=.09, aspect=40,fraction=0.05)
cbar.ax.set_xticklabels(list(map(str,np.sort(np.array(levels))))) # horizontal colorbar
cbar.ax.tick_params(labelsize=8)
plt.title('Heat Map')
plt.xlabel('X Label')
plt.ylabel('Y Label')
plt.show()
Produces this image:
or if you're looking for a more gradual color change, change the tricontourf line to this:
CS = ax.tricontourf(x, y, z, np.linspace(min(levels),max(levels),256), cmap=cmap)
and then the plot will change to:
Based on this answer, you might want to do something like:
import numpy as np
from matplotlib.mlab import griddata
import matplotlib.pyplot as plt
xs0 = [1,4,0.5,2,5,10,33,0.04]
ys0 = [2,5,44,0.33,2,14,20,0.03]
zs0 = [0.77, 0.88, 0.65, 0.55, 0.89, 0.9, 0.8,0.95]
N = 30j
extent = (np.min(xs0),np.max(xs0),np.min(ys0),np.max(ys0))
xs,ys = np.mgrid[extent[0]:extent[1]:N, extent[2]:extent[3]:N]
resampled = griddata(xs0, ys0, zs0, xs, ys, interp='linear')
plt.imshow(np.fliplr(resampled).T, extent=extent,interpolation='none')
plt.colorbar()
The example here might also help: http://matplotlib.org/examples/pylab_examples/griddata_demo.html

Matplotlib Colorbar Display Digtis

How do I exactly specify the colorbar labels in matplotlib? Frequently, I need to create very specific color scales, but the colorbar labels display so poorly you can't tell what the scale is. I would like to manually define the text next to the colorbar tick marks, or at least have them display in scientific notation.
Here is an example plot where you can't tell what the bottom four color bins represent:
And here is a working example of how that plot was created:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
# mock up some data
x = np.random.random(50)
y = np.random.random(50)
c = np.arange(0, 1, 1.0/50.0) # color of points
c[0] = 0.00001
c[1] = 0.0001
c[2] = 0.001
c[3] = 0.01
s = 500 * np.random.random(50) + 25 # size of points
# set up some custom color scaling
lcmap = colors.ListedColormap(['#FFFFFF', '#FF99FF', '#8000FF',
'#0000FF', '#0080FF', '#58FAF4',
'#00FF00', '#FFFF00', '#FF8000',
'#FF0000'])
bounds = [0.0, 0.000001, 0.00001, 0.0001,
0.001, 0.01, 0.1, 0.25, 0.5, 0.75, 1.0]
norm = colors.BoundaryNorm(bounds, lcmap.N)
# create some plot
fig, ax = plt.subplots()
im = ax.scatter(x, y, c=c, s=s, cmap=lcmap, norm=norm)
# add the colorbar
fig.colorbar(im, ax=ax)
fig.savefig('temp.jpg')
cbar = fig.colorbar(cax, ticks=[-1, 0, 1])
cbar.ax.set_xticklabels(['Low', 'Medium', 'High'])
and use whatever iterable you want instead of ['Low', 'Medium', 'High']
see: http://matplotlib.org/examples/pylab_examples/colorbar_tick_labelling_demo.html

Categories