dash from the point to x and y axes in matplotlib - python

as you can see, I want to make the dash connect to the x and y axes.
There is always a small gap.
I use matplotlib
the vline function, and I don't know how to use the transform parameters.

Using vlines and hlines from matplotlib.pyplot, you can specify your axes and your line limits:
from matplotlib import pyplot as plt
# Drawing example diagram
plt.scatter(x=11,y=0.891)
plt.xlim(5,20)
plt.xticks([5,8,11,14,17,20])
plt.ylim(0.780,0.9)
# Specifying lines, notice how despite setting xmin and ymin lower than your axes,
# the lines stop at each boundary
plt.vlines(x=11, ymin=0.7, ymax=0.891, colors='r',linestyles='dashed')
plt.hlines(y=0.891, xmin=4, xmax=11, colors='k',linestyles='dashed')
plt.show()

The result is beautiful, but the code not so good.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.ticker as ticker
x = [i for i in range(5, 21, 3)]
# [5, 8, 11, 14, 17, 20]
y = [0.780, 0.865, 0.891, 0.875, 0.884, 0.870]
y_max_index = np.argmax(y)
# print(y_max_index)
# get the max point
x_max = x[y_max_index]
y_max = y[y_max_index]
fig, ax = plt.subplots()
ax.plot(x, y, marker='o', color='r')
# set x ticks as [5, 8, 11, 14, 17, 20]
my_x_ticks = x
plt.xticks(my_x_ticks)
# set x and y lim
axe_y_min, axe_y_max = ax.get_ylim()
axe_x_min, axe_x_max = ax.get_xlim()
ax.set_ylim(axe_y_min, axe_y_max)
ax.set_xlim(axe_x_min, axe_x_max)
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('%.3f')) # set y axe format
anno_text = "(11, 0.891)"
plt.annotate(anno_text, xy=(x_max, y_max), xytext=(x_max+0.5, y_max)) # annotate
y_scale_trans = (y_max - axe_y_min) / (axe_y_max - axe_y_min)
x_scale_trans = (x_max - axe_x_min) / (axe_x_max - axe_x_min)
ax.vlines(x_max, 0, y_scale_trans, transform=ax.get_xaxis_transform(), colors='black', linestyles="dashed")
ax.hlines(y_max, 0, x_scale_trans, transform=ax.get_yaxis_transform(), colors='black', linestyles="dashed")
plt.ylabel("准确率")
plt.xlabel("滑动窗口大小")
plt.savefig("滑动窗口.pdf", dpi=100)
plt.show()

Here is a solution using plt.plot to draw the lines.
import matplotlib.pyplot as plt
import numpy as np
y = np.random.randint(1, 10, 10)
x = np.arange(len(y))
point = [x[2], y[2]]
plt.plot(x,y)
plt.plot((point[0], point[0]), (0, point[1]), '--')
plt.plot((0, point[0]), (point[1], point[1]), '--')
plt.xlim(0,10)
plt.ylim(0,10)

Related

Show only certain tick labels in log plot

The code below produces plots like this one:
I need to show only the tick labels in the y axis that are over the horizontal line. In this case, the labels [2,3,4,5] would need to be hidden. I've tried using
ax.get_yticks()
ax.get_yticklabels()
to retrieve the ticks that are drawn, and from those select only the ones above the y_min value to show. Neither command returns the actual tick labels drawn in the plot.
How can I do this?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter
# Some random data
x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)
ax = plt.subplot(111)
ax.scatter(x, y)
ax.hlines(y_min, xmin=min(x), xmax=max(x))
ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(FormatStrFormatter('%.0f'))
ax.yaxis.set_major_formatter(FormatStrFormatter('%.0f'))
plt.show()
The tick labels are only available when the plot is effectively drawn. Note that the positions will change when the plot is interactively resized or zoomed in.
An idea is to add the test to the formatter function, so everything will stay OK after zooming etc.
The following example code uses the latest matplotlib, which allows to set a FuncFormatter without declaring a separate function:
import numpy as np
import matplotlib.pyplot as plt
x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)
ax = plt.subplot(111)
ax.scatter(x, y)
ax.axhline(y_min) # occupies the complete width of the plot
ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(lambda x, t: f'{x:.0f}' if x >= y_min else None)
ax.yaxis.set_major_formatter(lambda x, t: f'{x:.0f}' if x >= y_min else None)
plt.show()
PS: You might use ax.tick_params(length=4, which='both') to set the same tick length for minor and major ticks.
You have to get current y tick labels:
fig.canvas.draw()
labels = [float(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'minor')]
Then apply the filter you need:
labels_above_threshold = [label if label >= y_min else '' for label in labels]
And finally set filtered labels:
ax.yaxis.set_ticklabels(labels_above_threshold, minor = True)
Complete Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter
x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.hlines(y_min, xmin=min(x), xmax=max(x))
ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(FormatStrFormatter('%.0f'))
ax.yaxis.set_major_formatter(FormatStrFormatter('%.0f'))
fig.canvas.draw()
# MINOR AXIS
labels = [int(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'minor')]
labels_above_threshold = [label if label >= y_min else '' for label in labels]
ax.yaxis.set_ticklabels(labels_above_threshold, minor = True)
# MAJOR AXIS
labels = [int(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'major')]
labels_above_threshold = [label if label >= y_min else '' for label in labels]
ax.yaxis.set_ticklabels(labels_above_threshold, minor = False)
plt.show()

Plotting a line between 2-D point to its corresponding value in 3-D

I am trying to plot a dashed line in a 3-D Matplotlib plot. I would like to get a dashed line between each (x_pt, y_pt) to its corresponding z_pt.
from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib
matplotlib.rcParams['mathtext.fontset'] = 'cm'
matplotlib.rcParams['axes.labelsize'] = 13
def z_function(x, y):
a = 1
b = 5.1/(4*np.pi**2)
c = 5/np.pi
r = 6
s = 10
t = 1/(8*np.pi)
return a*(y - b*x**2 + c*x - r)**2 + s*(1 - t)*np.cos(x) + s
x = np.linspace(-5, 10, 100)
y = np.linspace(0, 15, 100)
indexes = np.random.randint(0, 100, 5)
x_pt = x[indexes]
y_pt = y[indexes]
z_pt = z_function(x_pt, y_pt)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(x_pt, y_pt, color='k', marker='x', depthshade=False)
ax.scatter(x_pt, y_pt, z_pt, color='k', marker='^', depthshade=False)
ax.set_xticks([-5, 0, 5, 10])
ax.set_yticks([0, 5, 10, 15])
ax.set_zticks([100, 200, 300])
ax.view_init(30, -120)
ax.set_xlabel(r'$x_1$')
ax.set_ylabel(r'$x_2$')
ax.zaxis.set_rotate_label(False)
ax.set_zlabel(r'$f(x)$', rotation=0)
ax.w_xaxis.pane.fill = False
ax.w_yaxis.pane.fill = False
ax.w_zaxis.pane.fill = False
plt.show()
Can anyone help me with this?
If I understand your problem correctly, you need to connect the point (x,y,0) to (x,y,z) like so:
for x_,y_,z_ in zip(x_pt, y_pt, z_pt):
ax.plot([x_,x_],[y_,y_],[0,z_], '--', c='grey')
It should be as simple as:
ax.plot(x_pt, y_pt, zs=z_pt, color='blue', marker='--', depthshade=False)
alternatively using:
ax.plot3D(x_pt, y_pt, z_pt, marker='--')
UPDATE:
You will need to create extra dummy coordinates for each point on the x-y axis, like so:
import numpy as np
n = 10 # number of points in the line
for i in len(x_pt):
x_range = np.linspace(0, x_pt[i], n)
y_range = np.linspace(0, y_pt[i], n)
ax.plot3D(x_range, y_range, [z_pt[i]]*n, marker='--')
NOTE: Untested

How to create a step-plot with a gradient based on y-value?

In Python matplotlib, how can you get the line in a line or step plot to display a gradient based on the y-value?
Example plot (made in Tableau):
Code for step plot with a line that changes gradient according to x-value, adapted from this answer:
fig, ax = plt.subplots(figsize=(10, 4))
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
y = [2, 3, 9, 10, 2, 9, 0, 1, 9, 1, -8]
T = np.linspace(0,1,np.size(x))**2
s = 1
for i in range(0, len(x)-s, s):
ax.step(x[i:i+s+1], y[i:i+s+1], marker='.', color=(0.0,0.5,T[i]))
ax.tick_params(axis='both', colors='lightgray', labelsize=8)
The following code is inspired by the multicolored-line example from the matplotlib docs. First the horizontal line segments are drawn and colored using their y-value. The vertical segments are subdivided in small chunks to colored individually.
vmin of the norm is set a bit lower to avoid the too-light range of the colormap.
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import numpy as np
x = np.arange(50)
y = np.random.randint(-3, 4, x.size).cumsum()
fig, ax = plt.subplots()
norm = plt.Normalize(y.min() - y.ptp() * .2, y.max())
cmap = 'inferno_r' # 'Reds'
horizontal_lines = np.array([x[:-1], y[:-1], x[1:], y[:-1]]).T.reshape(-1, 2, 2)
hor_lc = LineCollection(horizontal_lines, cmap=cmap, norm=norm)
hor_lc.set_array(y[:-1])
ax.add_collection(hor_lc)
factor = 10
long_y0 = np.linspace(y[:-1], y[1:], factor)[:-1, :].T.ravel()
long_y1 = np.linspace(y[:-1], y[1:], factor)[1:, :].T.ravel()
long_x = np.repeat(x[1:], factor - 1)
vertical_lines = np.array([long_x, long_y0, long_x, long_y1]).T.reshape(-1, 2, 2)
ver_lc = LineCollection(vertical_lines, cmap=cmap, norm=norm)
ver_lc.set_array((long_y0 + long_y1) / 2)
ax.add_collection(ver_lc)
ax.scatter(x, y, c=y, cmap=cmap, norm=norm)
plt.autoscale() # needed in case the scatter plot would be omited
plt.show()
Here is another example, with a black background. In this case the darkest part of the colormap is avoided. The changed code parts are:
y = np.random.randint(-9, 10, x.size)
ax.patch.set_color('black')
norm = plt.Normalize(y.min(), y.max() + y.ptp() * .2)
cmap = 'plasma_r'
Here is an example with a TwoSlopeNorm and the blue-white-red colormap:
from matplotlib.colors import TwoSlopeNorm
y = np.random.uniform(-1, 1, x.size * 10).cumsum()[::10]
y = (y - y.min()) / y.ptp() * 15 - 5
norm = TwoSlopeNorm(vmin=-5, vcenter=0, vmax=10)
cmap = 'bwr'

Is it possible to change the frequency of ticks on a pyplot INDEPENDENT of length of data set and zoom?

When I plot data using matplotlib I always have 5-9 ticks on my x-axis independent of the range I plot, and if I zoom on the x-axis the tick spacing decreases, so I still see 5-9 ticks.
however, I would like 20-30 ticks on my x-axis!
I can achieve this with the following:
from matplotlib import pyplot as plt
import numpy as np
x = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
y = [1, 4, 3, 2, 7, 6, 9, 8, 10, 5]
number_of_ticks_on_x_axis = 20
plt.plot(x, y)
plt.xticks(np.arange(min(x), max(x)+1, (max(x) - min(x))/number_of_ticks_on_x_axis))
plt.show()
If I now zoom on the x-axis, no new ticks appear between the existing ones. I would like to still have ~20 ticks however much I zoom.
Assuming that you want to fix the no. of ticks on the X axis
...
from matplotlib.ticker import MaxNLocator
...
fig, ax = plt.subplots()
ax.xaxis.set_major_locator(MaxNLocator(15, min_n_ticks=15))
...
Please look at the docs for MaxNLocator
Example
In [36]: import numpy as np
...: import matplotlib.pyplot as plt
In [37]: from matplotlib.ticker import MaxNLocator
In [38]: fig, ax = plt.subplots(figsize=(10,4))
In [39]: ax.grid()
In [40]: ax.xaxis.set_major_locator(MaxNLocator(min_n_ticks=15))
In [41]: x = np.linspace(0, 1, 51)
In [42]: y = x*(1-x)
In [43]: plt.plot(x, y)
Out[43]: [<matplotlib.lines.Line2D at 0x7f9eab409e10>]
gives
and when I zoom into the maximum of the curve I get
You can link a callback function to an event in the canvas. In you case you can trigger a function that updates the axis when a redraw occurs.
from matplotlib import pyplot as plt
import numpy as np
x = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
y = [1, 4, 3, 2, 7, 6, 9, 8, 10, 5]
n = 20
plt.plot(x, y)
plt.xticks(np.arange(min(x), max(x)+1, (max(x) - min(x))/n), rotation=90)
def on_zoom(event):
ax = plt.gca()
fig = plt.gcf()
x_min, x_max = ax.get_xlim()
ax.set_xticks(np.linspace(x_min, x_max, n))
# had to add flush_events to get the ticks to redraw on the last update.
fig.canvas.flush_events()
fig = plt.gcf()
fig.canvas.mpl_disconnect(cid)
cid = fig.canvas.mpl_connect('draw_event', on_zoom)

How to disable the minor ticks of log-plot in Matplotlib?

Here is a simple plot:
1) How to disable the ticks?
2) How to reduce their number?
Here is a sample code:
from pylab import *
import numpy as np
x = [5e-05, 5e-06, 5e-07, 5e-08, 5e-09, 5e-10]
y = [-13, 14, 100, 120, 105, 93]
def myfunc(x,p):
sl,yt,yb,ec=p
y = yb + (yt-yb)/(1+np.power(10, sl*(np.log10(x)-np.log10(ec))))
return y
xp = np.power(10, np.linspace(np.log10(min(x)/10), np.log10(max(x)*10), 100))
pxp=myfunc(xp, [1,100,0,1e-6])
subplot(111,axisbg="#dfdfdf")
plt.plot(x, y, '.', xp, pxp, 'g-', linewidth=1)
plt.xscale('log')
plt.grid(True,ls="-", linewidth=0.4, color="#ffffff", alpha=0.5)
plt.draw()
plt.show()
Which produces:
plt.minorticks_off()
Turns em off!
To change the number of them/position them, you can use the subsx parameter. like this:
plt.xscale('log', subsx=[2, 3, 4, 5, 6, 7, 8, 9])
From the docs:
subsx/subsy: Where to place the subticks between each major tick.
Should be a sequence of integers. For example, in a log10 scale: [2,
3, 4, 5, 6, 7, 8, 9]
will place 8 logarithmically spaced minor ticks between each major
tick.
Calling plt.minorticks_off() will apply this to the current axis. (The function is actually a wrapper to gca().minorticks_off().)
You can also apply this to an individual axis in the same way:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.minorticks_off()
from pylab import *
import numpy as np
x = [5e-05, 5e-06, 5e-07, 5e-08, 5e-09, 5e-10]
y = [-13, 14, 100, 120, 105, 93]
def myfunc(x,p):
sl,yt,yb,ec=p
y = yb + (yt-yb)/(1+np.power(10, sl*(np.log10(x)-np.log10(ec))))
return y
xp = np.power(10, np.linspace(np.log10(min(x)/10), np.log10(max(x)*10), 100))
pxp=myfunc(xp, [1,100,0,1e-6])
ax=subplot(111,axisbg="#dfdfdf")
plt.plot(x, y, '.', xp, pxp, 'g-', linewidth=1)
plt.xscale('log')
plt.grid(True,ls="-", linewidth=0.4, color="#ffffff", alpha=0.5)
plt.minorticks_off() # turns off minor ticks
plt.draw()
plt.show()

Categories

Resources