Show derivative on a plot line with matplotlib - matplotlib

I want to show a triangle on a line plot from matplotlib describing the derivative with some description text or plain value/name.
Let's say I'm plotting a very simple line:
from matplotlib import pyplot as plt
x = (1, 3, 7, 10)
y = (1, 3, 7, 10)
plt.plot(x, y, '-o', mfc='none')
# plt.show_derivative(x=x[1], y=y[1], name='$\gamma$') # <- What I want
plt.show()
And I want to explicitely show the derivative like this:
Wished result in red
another example
Is there a quick function or way to do this?

You cloud try something like this:
from matplotlib import pyplot as plt
def plotDerivative(x1, x2, y1, y2, name):
triangle = plt.Polygon([[x1, y1], [x2, y2], [x2, y1]], color='black', alpha=1, fill=False)
plt.gca().add_patch(triangle)
plt.text(x2 ,y1 , name, fontsize=20)
x = (1, 3, 7, 10)
y = (1, 3, 7, 10)
plt.plot(x, y, '-o', mfc='none')
plotDerivative(x[1] + x[1]/2, x[2]-x[1]/2, y[1]+y[1]/2, y[2]-y[1]/2, '$\gamma$')
plt.show()
There is maybe a better way, but it works
Example

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.

change color of bar for data selection in seaborn histogram (or plt)

Let's say I have a dataframe like:
X2 = np.random.normal(10, 3, 200)
X3 = np.random.normal(34, 2, 200)
a = pd.DataFrame({"X3": X3, "X2":X2})
and I am doing the following plotting routine:
f, axes = plt.subplots(2, 2, gridspec_kw={"height_ratios":(.10, .30)}, figsize = (13, 4))
for i, c in enumerate(a.columns):
sns.boxplot(a[c], ax=axes[0,i])
sns.distplot(a[c], ax = axes[1,i])
axes[1, i].set(yticklabels=[])
axes[1, i].set(xlabel='')
axes[1, i].set(ylabel='')
plt.tight_layout()
plt.show()
Which yields to:
Now I want to be able to perform a data selection on the dataframe a. Let's say something like:
b = a[(a['X2'] <4)]
and highlight the selection from b in the posted histograms.
for example if the first row of b is [32:0] for X3 and [0:5] for X2, the desired output would be:
is it possible to do this with the above for loop and with sns? Many thanks!
EDIT: I am also happy with a matplotlib solution, if easier.
EDIT2:
If it helps, it would be similar to do the following:
b = a[(a['X3'] >38)]
f, axes = plt.subplots(2, 2, gridspec_kw={"height_ratios":(.10, .30)}, figsize = (13, 4))
for i, c in enumerate(a.columns):
sns.boxplot(a[c], ax=axes[0,i])
sns.distplot(a[c], ax = axes[1,i])
sns.distplot(b[c], ax = axes[1,i])
axes[1, i].set(yticklabels=[])
axes[1, i].set(xlabel='')
axes[1, i].set(ylabel='')
plt.tight_layout()
plt.show()
which yields the following:
However, I would like to be able to just colour those bars in the first plot in a different colour!
I also thought about setting the ylim to only the size of the blue plot so that the orange won't distort the shape of the blue distribution, but it wouldn't still be feasible, as in reality I have about 10 histograms to show, and setting ylim would be pretty much the same as sharey=True, which Im trying to avoid, so that I'm able to show the true shape of the distributions.
I think I found the solution for this using the inspiration from the previous answer and this video:
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
np.random.seed(2021)
X2 = np.random.normal(10, 3, 200)
X3 = np.random.normal(34, 2, 200)
a = pd.DataFrame({"X3": X3, "X2":X2})
b = a[(a['X3'] < 30)]
hist_idx=[]
for i, c in enumerate(a.columns):
bin_ = np.histogram(a[c], bins=20)[1]
hist = np.where(np.logical_and(bin_<=max(b[c]), bin_>min(b[c])))
hist_idx.append(hist)
f, axes = plt.subplots(2, 2, gridspec_kw={"height_ratios":(.10, .30)}, figsize = (13, 4))
for i, c in enumerate(a.columns):
sns.boxplot(a[c], ax=axes[0,i])
axes[1, i].hist(a[c], bins = 20)
axes[1, i].set(yticklabels=[])
axes[1, i].set(xlabel='')
axes[1, i].set(ylabel='')
for it, index in enumerate(hist_idx):
lenght = len(index[0])
for r in range(lenght):
try:
axes[1, it].patches[index[0][r]-1].set_fc("red")
except:
pass
plt.tight_layout()
plt.show()
which yields the following for b = a[(a['X3'] < 30)] :
or for b = a[(a['X3'] > 36)]:
Thought I'd leave it here - although niche, might help someone in the future!
I created the following code with the understanding that the intent of your question is to add a different color to the histogram based on the data extracted under certain conditions.
Use np.histogram() to get an array of frequencies and an array of bins. Get the index of the value closest to the value of the first row of data extracted for a certain condition. Change the color of the histogram with that retrieved index. The same method can be used to deal with the other graph.
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
np.random.seed(2021)
X2 = np.random.normal(10, 3, 200)
X3 = np.random.normal(34, 2, 200)
a = pd.DataFrame({"X3": X3, "X2":X2})
f, axes = plt.subplots(2, 2, gridspec_kw={"height_ratios":(.10, .30)}, figsize = (13, 4))
for i, c in enumerate(a.columns):
sns.boxplot(a[c], ax=axes[0,i])
sns.distplot(a[c], ax = axes[1,i])
axes[1, i].set(yticklabels=[])
axes[1, i].set(xlabel='')
axes[1, i].set(ylabel='')
b = a[(a['X2'] <4)]
hist3, bins3 = np.histogram(X3)
idx = np.abs(np.asarray(hist3) - b['X3'].head(1).values[0]).argmin()
for k in range(idx):
axes[1,0].get_children()[k].set_color("red")
plt.tight_layout()
plt.show()

mouse-over only on actual data points

Here's a really simple line chart.
%matplotlib notebook
import matplotlib.pyplot as plt
lines = plt.plot([1, 2, 3, 4], [1, 4, 9, 16])
plt.setp(lines,marker='D')
plt.ylabel('foo')
plt.xlabel('bar')
plt.show()
If I move my mouse over the chart, I get the x and y values for wherever the pointer is. Is there any way to only get values only when I'm actually over a data point?
I understood you wanted to modify the behavior of the coordinates displayed in the status bar at the bottom right of the plot, is that right?
If so, you can "hijack" the Axes.format_coord() function to make it display whatever you want. You can see an example of this on matplotlib's example gallery.
In your case, something like this seem to do the trick?
my_x = np.array([1, 2, 3, 4])
my_y = np.array([1, 4, 9, 16])
eps = 0.1
def format_coord(x, y):
close_x = np.isclose(my_x, x, atol=eps)
close_y = np.isclose(my_y, y, atol=eps)
if np.any(close_x) and np.any(close_y):
return 'x=%s y=%s' % (ax.format_xdata(my_x[close_x]), ax.format_ydata(my_y[close_y]))
else:
return ''
fig, ax = plt.subplots()
ax.plot(my_x, my_y, 'D-')
ax.set_ylabel('foo')
ax.set_xlabel('bar')
ax.format_coord = format_coord
plt.show()

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)

Adding an arbitrary line to a matplotlib plot in ipython notebook

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.