Adding an arbitrary line to a matplotlib plot in ipython notebook - matplotlib

I'm rather new to both python/matplotlib and using it through the ipython notebook. I'm trying to add some annotation lines to an existing graph and I can't figure out how to render the lines on a graph. So, for example, if I plot the following:
import numpy as np
np.random.seed(5)
x = arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
p = plot(x, y, "o")
I get the following graph:
So how would I add a vertical line from (70,100) up to (70,250)? What about a diagonal line from (70,100) to (90,200)?
I've tried a few things with Line2D() resulting in nothing but confusion on my part. In R I would simply use the segments() function which would add line segments. Is there an equivalent in matplotlib?

You can directly plot the lines you want by feeding the plot command with the corresponding data (boundaries of the segments):
plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)
(of course you can choose the color, line width, line style, etc.)
From your example:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# draw vertical line from (70,100) to (70, 250)
plt.plot([70, 70], [100, 250], 'k-', lw=2)
# draw diagonal line from (70, 90) to (90, 200)
plt.plot([70, 90], [90, 200], 'k-')
plt.show()

It's not too late for the newcomers.
plt.axvline(x, color='r') # vertical
plt.axhline(x, color='r') # horizontal
It takes the range of y as well, using ymin and ymax.

Using vlines:
import numpy as np
np.random.seed(5)
x = arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
p = plot(x, y, "o")
vlines(70,100,250)
The basic call signatures are:
vlines(x, ymin, ymax)
hlines(y, xmin, xmax)

Rather than abusing plot or annotate, which will be inefficient for many lines, you can use matplotlib.collections.LineCollection:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# Takes list of lines, where each line is a sequence of coordinates
l1 = [(70, 100), (70, 250)]
l2 = [(70, 90), (90, 200)]
lc = LineCollection([l1, l2], color=["k","blue"], lw=2)
plt.gca().add_collection(lc)
plt.show()
It takes a list of lines [l1, l2, ...], where each line is a sequence of N coordinates (N can be more than two).
The standard formatting keywords are available, accepting either a single value, in which case the value applies to every line, or a sequence of M values, in which case the value for the ith line is values[i % M].

Matplolib now allows for 'annotation lines' as the OP was seeking. The annotate() function allows several forms of connecting paths and a headless and tailess arrow, i.e., a simple line, is one of them.
ax.annotate("",
xy=(0.2, 0.2), xycoords='data',
xytext=(0.8, 0.8), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3, rad=0"),
)
In the documentation it says you can draw only an arrow with an empty string as the first argument.
From the OP's example:
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")
# draw vertical line from (70,100) to (70, 250)
plt.annotate("",
xy=(70, 100), xycoords='data',
xytext=(70, 250), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3,rad=0."),
)
# draw diagonal line from (70, 90) to (90, 200)
plt.annotate("",
xy=(70, 90), xycoords='data',
xytext=(90, 200), textcoords='data',
arrowprops=dict(arrowstyle="-",
connectionstyle="arc3,rad=0."),
)
plt.show()
Just as in the approach in gcalmettes's answer, you can choose the color, line width, line style, etc..
Here is an alteration to a portion of the code that would make one of the two example lines red, wider, and not 100% opaque.
# draw vertical line from (70,100) to (70, 250)
plt.annotate("",
xy=(70, 100), xycoords='data',
xytext=(70, 250), textcoords='data',
arrowprops=dict(arrowstyle="-",
edgecolor = "red",
linewidth=5,
alpha=0.65,
connectionstyle="arc3,rad=0."),
)
You can also add curve to the connecting line by adjusting the connectionstyle.

Related

Line with dashes of multiple colours [duplicate]

Using dashed lines is nice because it gives a way to distinguish lines that doesn't rely on the reader being able to perceive differences in color. The trouble is that they only work if the details of the line are all larger than the dash pattern. The documentation for Matplotlib's Axes.plot function describes how to customize a line's color (with the color keyword) and how to customize the pattern of dashes (dash keyword). Is there a way to make the plot alternate between two different selectable colors instead of "there" and "not" with a single call to Axes.plot?
I can achieve this effect by plotting the same line twice, once with a solid line and then overplotting the same data with the dashed line, but that makes managing the alpha transparency complicated (translucent lines are desirable when there are several intersecting lines on one plot). The black and grey lines in the plot below were generated with these lines of code:
ax.plot(xv1, yv1, marker="None", linestyle="-", color=(0.8, 0.8, 0.8, 1.0))
ax.plot(xv1, yv1, marker="None", linestyle="--", color=(0.0, 0.0, 0.0, 1.0))
Edit: Another reason to desire that this be doable with a single call to Axis.plot is that it would display an example line correctly when creating a legend (the only remaining drawback I've found of the methods given in the answers).
Experimenting a bit with #WilliamMiller's beautiful answer, this can be extended to more than two colors:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, np.pi*4, 100)
y = np.sin(x+np.pi/2)
z = np.sin(x+np.pi/4)
w = np.sin(x)
plt.plot(x, y, linestyle=(0, (5, 5)), color='gold')
plt.plot(x, y, linestyle=(5, (5, 5)), color='crimson')
plt.plot(x, z, linestyle=(0, (10, 30)), color='blueviolet')
plt.plot(x, z, linestyle=(10, (10, 30)), color='lime')
plt.plot(x, z, linestyle=(20, (10, 30)), color='fuchsia')
plt.plot(x, z, linestyle=(30, (10, 30)), color='coral')
plt.plot(x, w, linestyle=(0, (10, 20)), color='crimson', lw=3)
plt.plot(x, w, linestyle=(10, (10, 20)), color='lime', lw=3)
plt.plot(x, w, linestyle=(20, (10, 20)), color='deepskyblue', lw=3)
plt.show()
This doesn't accomplish what you are asking "with a single call to Axes.plot", but the desired behavior can be created by using two custom linestyles - one of which is offset by the width of the other's dash.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, np.pi*4, 100)
y = np.sin(x)
plt.plot(x, y, linestyle=(0, (2, 2)))
plt.plot(x, y, linestyle=(2, (2, 2)))
plt.show()
The linestyle specification is (offset, (on_off_seq)) where on_off_seq is a tuple of one or more values, in pts.
In case you want legends on your plot you can do the following:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 2*np.pi, 0.2)
y = np.cos(x)
blue_line, = plt.plot(x, y, linestyle='-', lw=3)
dashed_line, = plt.plot(x, y, linestyle=(2, (2, 2)), lw=3)
plt.legend([(blue_line, dashed_line)], ['data'])
Legends in matplotlib are very customizable, more interesting examples and information can be seen here.

How to plot a heatmap of coordinates on a mollweide projection

I have a set of lattitude and longitude coordinates (i.e. a list of lists: [[20,24],[100,-3],...]) that I would like to plot has a heatmap (not just a scatter) on a mollweide projection. Essentially, what I want is a seaborn hist2d plot but as a mollweide. For a reference of what I mean, please see the uploaded picture. Does anyone know how to do this?
I created some random data and showed the way to generate the histogram plot. I hope this is something you are looking for.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
# create some random data for histogram
base = [[-20, 30], [100, -20]]
data = []
for _ in range(10000):
data.append((
base[0][0] + np.random.normal(0, 20),
base[0][1] + np.random.normal(0, 10)
))
data.append((
base[1][0] + np.random.normal(0, 20),
base[1][1] + np.random.normal(0, 10)
))
data = np.array(data) / 180 * np.pi # shape (n, 2)
# create bin edges
bin_number = 40
lon_edges = np.linspace(-np.pi, np.pi, bin_number + 1)
lat_edges = np.linspace(-np.pi/2., np.pi/2., bin_number + 1)
# calculate 2D histogram, the shape of hist is (bin_number, bin_number)
hist, lon_edges, lat_edges = np.histogram2d(
*data.T, bins=[lon_edges, lat_edges], density=True
)
# generate the plot
cmap = plt.cm.Greens
fig = plt.figure()
ax = fig.add_subplot(111, projection='mollweide')
ax.pcolor(
lon_edges[:-1], lat_edges[:-1],
hist.T, # transpose from (row, column) to (x, y)
cmap=cmap, shading='auto',
vmin=0, vmax=1
)
# hide the tick labels
ax.set_xticks([])
ax.set_yticks([])
# add the colorbar
cbar = plt.colorbar(
plt.cm.ScalarMappable(
norm=mpl.colors.Normalize(0, 1), cmap=cmap
)
)
cbar.set_label("Density Distribution")
plt.show()
I get the following figure.

pyplot: loglog contour with labels fix angle

This is an extension of a related question.
I intend to make a contour plot, with labeled contours, then change the axes scales to 'log'.
This works fine except that the rotation of the contour labels is not adjusted. Can this be fixed?
loglog = False
import matplotlib.pyplot as plt
import numpy as np
x = (np.linspace(0, 10))
y = (np.linspace(0, 10))
X, Y = np.meshgrid(x, y)
C = plt.contour(X, Y, np.sqrt(X) * Y)
plt.clabel(C, inline=1, fontsize=10)
plt.xlim(1, 10)
plt.ylim(1, 10)
if loglog: plt.xscale('log')
if loglog: plt.yscale('log')
plt.show()
The fist plot is obtained with loglog=False in the second loglog=True:
So the answer is actually obvious. Changing the the axes scale types in advance helps, of course.
Edit:
I think it makes sense to use logspace instead of linspace here.
import matplotlib.pyplot as plt
import numpy as np
x = np.logspace(0, 1, 100, base=10)
y = np.logspace(0, 1, 100, base=10)
X, Y = np.meshgrid(x, y)
plt.xlim(1, 10)
plt.ylim(1, 10)
plt.xscale('log')
plt.yscale('log')
C = plt.contour(X, Y, np.sqrt(X) * Y)
plt.clabel(C, inline=1, fontsize=10)

3d tick labels do not display correctly

I am plotting 3d bar plots using mplot3d:
import numpy as np
import matplotlib
matplotlib.use("Qt4Agg")
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
result=[[0, 0, 5, 5, 14,40,50],
[0, 1, 8, 9, 20,50,70],
[0, 2, 8, 10, 25,60,80],
[0, 5, 10, 20, 40,75,100]]
result = np.array(result, dtype=np.int)
fig=plt.figure()
fig.set_size_inches(6, 4)
ax1=fig.add_subplot(111, projection='3d')
ax1.view_init(25, 280)
matplotlib.rcParams.update({'font.size': 12})
matplotlib.rcParams['font.weight']='normal'
xlabels = np.array(["Count1", "Count3","Count5", "Count6","Count7","Count8","Count9"])
xpos = np.arange(xlabels.shape[0])
ylabels = np.array(["5%","10%","20%","100%"])
ypos = np.arange(ylabels.shape[0])
xposM, yposM = np.meshgrid(xpos, ypos, copy=False)
zpos=result
zpos = zpos.ravel()
dx=0.75
dy=0.5
dz=zpos
ax1.w_xaxis.set_ticks(xpos + dx/2.)
ax1.w_xaxis.set_ticklabels(xlabels)
ax1.w_yaxis.set_ticks(ypos + dy/2)
ax1.set_yticklabels(ylabels)
ax1.w_zaxis.set_ticklabels(["","20%","40%","60%","80%","100%"])
colors = ['b','b','b','b','b','b','b','r','r','r','r','r','r','r','y','y','y','y','y','y','y','g','g','g','g','g','g','g']
ax1.bar3d(xposM.ravel(), yposM.ravel(), dz*0, dx, dy, dz, color=colors)
fig.savefig('tmp.tiff', dpi=300)
plt.close()
and here is what i got:
There are two problems here actually:
1) the y tick labels do not display correctly, they are supposed to be in the middle of the ticks but instead below the ticks. z tick labels are too close to the z ticks.
2) I suppose to use the font size 12 and the dpi should be higher than 300. I could not scale x axis such that the x tick labels fit nicely and do not overlap. I have tried multiply the xpos by 2. However the tick labels still overlap.

Matplotlib adding letter ticks

Is there a way to add two ticks (ex. two letters) along with existing ticks (numbers)?
I have:
but want to add two ticks (letters "a" and "b"). Running the following code deletes the numbers and leaves only letters, however I want to have both.
ax.set_xticks((a, b))
ax.set_xticklabels(('$a$', '$b$'), size='xx-large')
It's easiest to use annotate instead of placing ticks.
For example, let's start by approximately reproducing your example plot:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(2.8, 11, 100)
y = 0.2 * np.exp(-0.05 * x**2) + 0.01
fig, ax = plt.subplots()
ax.fill_between(x, y, alpha=0.3, label='surface', lw=2,
edgecolor='orange', facecolor='goldenrod')
ax.plot(x, y, color='darkred', lw=2, label='interpolated polinom')
ax.legend(fancybox=True, shadow=True)
ax.grid(ls=':')
ax.set_ylabel(r'$F[N]$', size=18)
ax.set_xlabel(r'$h[cm]$', size=18)
ax.spines['bottom'].set(linewidth=2)
plt.show()
We could add the "sub ticks" you want with:
labelpad = ax.xaxis.labelpad + 15
for val, letter in [(2.8, 'a'), (11, 'b')]:
ax.annotate('${}$'.format(letter), xy=(val, 0), xytext=(0, -labelpad),
xycoords=('data', 'axes fraction'), textcoords='offset points',
ha='center', va='top', size=18)