Pyplot move origin with axis - python

I have some code for a plot I want to create:
import numpy as np
import matplotlib.pyplot as plt
# data
X = np.linspace(-1, 3, num=50, endpoint=True)
b = 2.0
Y = X + b
# plot stuff
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1, 1, 1)
ax.set_title('linear neuron')
# move axes
ax.spines['left'].set_position(('axes', 0.30))
# ax.spines['left'].set_smart_bounds(True)
ax.yaxis.set_ticks_position('left')
ax.spines['bottom'].set_position(('axes', 0.30))
# ax.spines['bottom'].set_smart_bounds(True)
ax.xaxis.set_ticks_position('bottom')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# title
title = ax.set_title('Linear Neuron', y=1.10)
# axis ticks
# ax.set_xticklabels([0 if item == 0 else '' for item in X])
# ax.set_yticklabels([])
# for tick in ax.xaxis.get_majorticklabels():
# tick.set_horizontalalignment('left')
# ax.tick_params(axis=u'both', which=u'both',length=0)
# axis labels
ax.xaxis.set_label_coords(1.04, 0.30 - 0.025)
ax.yaxis.set_label_coords(0.30 - 0.03, 1.04)
y_label = ax.set_ylabel('output')
y_label.set_rotation(0)
ax.set_xlabel('input')
# ax.get_xaxis().set_visible(False)
# ax.get_yaxis().set_visible(False)
# grid
ax.grid(True)
ax.plot(X, Y, '-', linewidth=1.5)
fig.tight_layout()
fig.savefig('plot.pdf')
In this plot the x and y axis are moved. However, the origin is not moved with then, as one can see from the ticks and ticklabels.
How can I always move the origin with the x and y axis?
I guess it would be the same as simply looking at another area of the plot, so that the x and y axis are at the lower left but not in the corner as they usually are.
To visualize this:
What I want:
Where the arrow points to the x and y axis intersection, I want to have the origin, (0|0). Where the dashed arrow points upwards I want the line to move upwards, so that it is still mathematically at the correct position, when the origin moves.
(the final result of the efforts can be found here)

You've done a lot of manual tweaking of where each thing goes, so the solution is not very portable. But here it is: remove the ax.spines['bottom'].set_position and ax.xaxis.set_label_coords calls from your original code, and add this instead:
ax.set_ylim(-1, 6)
ax.spines['bottom'].set_position('zero')
xlabel = ax.xaxis.get_label()
lpos = xlabel.get_position()
xlabel.set_position((1.04, lpos[1]))
The "bring origin up" was really accomplished by just ax.set_ylim, the rest is to get your labels where you want them.

Related

2nd scale with ticks being a function of first ticks at same position in python/matplotlib

I am using matplotlib in Python and want to use the same plot but with several different axes that are all functions of the first one, but that do not linearly depend on the first y value.
As an example, let's assume a plot that shows a simple line y=x.
Now I have a random function like f(y)=5y^2 + 2.
My ideal output graph should now still be a line, but the equidistant ticks should not be y=1, 2, 3, 4, but f(y)=7, 22, 47, 82, so that I can overlay the two graphs with 2 different axes.
Is this even possible, as the distance between the ticks is not even nor can it be expressed in a log plot? Therefore I simply want to put a function on each tick value, without changing the graph nor the ticks' positions.
In a graphics program this would be straightforward, by simply using the same plot and manually rewriting each tick.
https://drive.google.com/file/d/1fp2vrFvlz-9xdJPmqdQjyMQK7gzPX24G/view?usp=sharing
Thank you in advance! The example code is not really helpful, as it is just the standard matplotlib code but the most important scaling part is missing.
I know that I can set the ticks manually with yticks, but this does not solve the scaling problem and all ticks would appear very close together.
plt.plot(["time_max_axis"], ["position_max_axis"])
plt.xlabel("Time (ms)")
plt.ylabel("Max position (mm)")
plt.ylim(0, z0_mm)
plt.show()
plt.plot(["time_max_axis"], ["frequency_axis"])
plt.xlabel("Oscillation frequency (kHz)")
plt.ylabel("Max position (mm)")
plt.ylim(fion_kHz, fion_kHz * (1 + (f_shift4 + f_shift6) / 100))
plt.show()
import matplotlib.pyplot as plt
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)
x = np.arange(50)
y = x/10 + np.random.rand(50)
fig, axs = plt.subplots(1,2, gridspec_kw={'width_ratios': [1, 20]})
plt.subplots_adjust(wspace=0, hspace=0)
axs[1].plot(x, y)
axs[1].plot(x, 2*y)
axs[1].plot(x, 3*y)
axs[1].grid()
axs[1].set_ylim(0)
axs[1].set_xlim(0)
axs[1].set_ylabel('max displacement $z_{max}$ (mm)')
ymin, ymax = axs[1].get_ylim()
majorlocator = ymax // 8 # 8 horizontal grid lines
ytickloc = np.arange(0, int(ymax), majorlocator)
axs[1].yaxis.set_major_locator(MultipleLocator(majorlocator))
ax1 = axs[1].twinx() # ghost axis of axs[1]
ax1.yaxis.set_ticks_position('left')
ax1.set_yticks([ymin, ymax])
ax1.set_yticklabels(['', f'$z_0$ = {round(ymax,2)}'])
axs[0].spines['top'].set_visible(False)
axs[0].spines['right'].set_visible(False)
axs[0].spines['bottom'].set_visible(False)
axs[0].spines['left'].set_visible(False)
axs[0].set_xticks([])
axs[0].set_yticks(ytickloc)
ytick2 = 5 * ytickloc**2 + 2 # f = 5y^2 + 2
ytick2 = list(ytick2)
ymin2 = ytick2[0]
ytick2[0] = ''
axs[0].set_yticklabels(ytick2)
axs[0].set_ylim(ymin, ymax)
axs[0].set_ylim(0)
axs[0].set_ylabel('Oscillation frequency $f_{osc}$ (kHz)')
ymax2 = 5 * ymax**2 + 2 # f = 5y^2 + 2
ax0 = axs[0].twinx() # ghost axis of axs[0]
ax0.yaxis.set_ticks_position('left')
ax0.spines['top'].set_visible(False)
ax0.spines['right'].set_visible(False)
ax0.spines['bottom'].set_visible(False)
ax0.spines['left'].set_visible(False)
ax0.set_yticks([ymin, ymax])
ax0.set_yticklabels([f'$\\bf{{f_{{ion}}}} = {round(ymin2, 2)}$', f'$f_{{max}}$ = {round(ymax2,2)}'])
plt.tight_layout()
Output:

Matplotlib Draw a Constant y Axis

I want to use matpoltlib to make a plot that with a constant y axis(always from 0 to 14 and the gap is 1), since I want to make labels for them and my dot values will be(x, y) where y is from 0 to 14 gap 1, and a changing x axis. I already tried to play with y ticks. And here is my code for that:
fig, ax = plt.subplots()
fig.canvas.draw()
plt.yticks(np.arange(0, 14, 1))
labels = [item.get_text() for item in ax.get_yticklabels()]
labels[1] = 'Not Detected'
labels[2] = 'A/G'
labels[3] = 'G/G'
labels[4] = 'C/T'
labels[5] = 'C/C'
labels[6] = 'A/A'
labels[7] = '-1'
labels[8] = 'ε3/ε3'
labels[9] = 'A/C'
labels[10] = 'T/T'
labels[11] = 'C/G'
labels[12] = 'ε2/ε3'
labels[13] = 'G/T'
ax.set_yticklabels(labels)
what I'm thinking about is to use some values or lines with white color so those y axis will appear. But I'm looking for a more efficient way of doing it. And here is the diagram I generated with the current code. It only shows C/C right now and I want all labels to appear in the diagram.
I tried draw white points with:
x1 = np.arange(n)
y1 = np.arange(1,15,1)
plt.scatter(x1,y1,color = 'white')
Which did give me what I want: But I was wondering whether there is a lib setting that can do this.
I would recommend just using a fixed locator and fixed formatter for your y axis. The function, ax.set_yticklabels() is simply a convenience wrapper for these tick methods.
I would also recommend having your y_labels in a list or using a loop structure as this is a more generalizable and modifiable implementation.
If I'm understanding the goals of your plot correctly, something like this may work well for you.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
#make some data
x = np.arange(25)
y = np.random.randint(1, 14, size=25)
#convert y labels to a list
y_labels = [
'Not Detected','A/G','G/G','C/T','C/C','A/A',
'-1','ε3/ε3', 'A/C','T/T','C/G','ε2/ε3','G/T'
]
#define figure/ax and set figsize
fig, ax = plt.subplots(figsize=(12,8))
#plot data, s is marker size, it's points squared
ax.scatter(x, y, marker='x', s=10**2, color='#5d2287', linewidth=2)
#set major locator and formatter to fixed, add grid, hide top/right spines
locator = ax.yaxis.set_major_locator(mpl.ticker.FixedLocator(np.arange(1, 14)))
formatter = ax.yaxis.set_major_formatter(mpl.ticker.FixedFormatter(y_labels))
grid = ax.grid(axis='y', dashes=(8,3), alpha=0.3, color='gray')
spines = [ax.spines[x].set_visible(False) for x in ['top','right']]
params = ax.tick_params(labelsize=12) #increase label font size

setting margins in matplotlib/seaborn with subplots

i'm plotting subplots in matplotlib/seaborn using:
plt.figure()
s1 = plt.subplot(2, 1, 1)
# plot 1
# call seaborn here
s2 = plt.subplot(2, 1, 2)
# plot 2
plt.tight_layout()
plt.show()
i am running into the common issue of marker being hidden by the axis (Add margin when plots run against the edge of the graph). when i try to adjust margins it doesn't work:
s1 = plt.subplot(2, 1, 1)
s1.margins(0.05)
it gives no error but doesn't set margins either.
here is a complete example:
gammas = sns.load_dataset("gammas")
s = plt.subplot(1, 1, 1)
# this does not change the x margins
s.get_axes().margins(x=0.05, y=0.01)
ax = sns.tsplot(time="timepoint", value="BOLD signal",
unit="subject", condition="ROI",
err_style="ci_bars",
interpolate=False,
data=gammas)
plt.show()
in the above, i am trying to make the x-margins bigger, but the x argument to margins() seems to have no effect. how can this be done?
You can define a function to add a given fraction of the x and y ranges to the margin, which makes use of get_xlim, get_ylim, set_xlim and set_ylim. Using your minimal example:
import matplotlib.pyplot as plt
import seaborn as sns
def add_margin(ax,x=0.05,y=0.05):
# This will, by default, add 5% to the x and y margins. You
# can customise this using the x and y arguments when you call it.
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmargin = (xlim[1]-xlim[0])*x
ymargin = (ylim[1]-ylim[0])*y
ax.set_xlim(xlim[0]-xmargin,xlim[1]+xmargin)
ax.set_ylim(ylim[0]-ymargin,ylim[1]+ymargin)
gammas = sns.load_dataset("gammas")
s = plt.subplot(1, 1, 1)
ax = sns.tsplot(time="timepoint", value="BOLD signal",
unit="subject", condition="ROI",
err_style="ci_bars",
interpolate=False,
data=gammas)
# Check what the original limits were
x0,y0=s.get_xlim(),s.get_ylim()
# Update the limits using set_xlim and set_ylim
add_margin(s,x=0.05,y=0.01) ### Call this after tsplot
# Check the new limits
x1,y1=s.get_xlim(),s.get_ylim()
# Print the old and new limits
print x0,y0
print x1,y1
plt.show()
Which prints:
# The original limits
(-0.10101010101010099, 10.1010101010101) (-2.0, 3.0)
# The updated limits
(-0.61111111111111105, 10.611111111111111) (-2.0499999999999998, 3.0499999999999998)
And here's the figure this produces:
Which, when compared to the original figure, clearly has added margins:

How do I offset lines in matplotlib by X points

I'm using matplotlib to plot some data that I wish to annotate with arrows (distance markers). These arrows should be offset by several points so as not to overlap with the plotted data:
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
fig, ax = plt.subplots()
x = [0, 1]
y = [0, 0]
# Plot horizontal line
ax.plot(x, y)
dy = 5/72
offset = transforms.ScaledTranslation(0, dy, ax.get_figure().dpi_scale_trans)
verttrans = ax.transData+offset
# Plot horizontal line 5 points above (works!)
ax.plot(x, y, transform = verttrans)
# Draw arrow 5 points above line (doesn't work--not vertically translated)
ax.annotate("", (0,0), (1,0),
size = 10,
transform=verttrans,
arrowprops = dict(arrowstyle = '<|-|>'))
plt.show()
Is there any way to make lines drawn by ax.annotate() be offset by X points? I wish to use absolute coordinates (e.g., points or inches) instead of data coordinates because the axis limits are prone to changing.
Thanks!
The following code does what I desired. It uses ax.transData and figure.get_dpi():
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
fig, ax = plt.subplots()
x = [0, 1]
y = [0, 0]
ax.plot(x, y)
dy = 5/72
i = 1 # 0 for dx
tmp = ax.transData.transform([(0,0), (1,1)])
tmp = tmp[1,i] - tmp[0,i] # 1 unit in display coords
tmp = 1/tmp # 1 pixel in display coords
tmp = tmp*dy*ax.get_figure().get_dpi() # shift pixels in display coords
ax.plot(x, y)
ax.annotate("", [0,tmp], [1,tmp],
size = 10,
arrowprops = dict(arrowstyle = '<|-|>'))
plt.show()
What's your expected output? If you're just looking to move the arrow you're drawing vertically, the API for annotate is
annotate(s, xy, xytext=None, ...)
so you can draw something like
ax.annotate("", (0,0.01), (1,0.01),
size = 10,
arrowprops = dict(arrowstyle = '<|-|>'))
which is moved up by 0.01 in data coordinates in the y direction. You can also specify coordinates as a fraction of the total figure size in annotate (see doc). Is that what you wanted?

Pyplot, plot 2 dataset into one figure, skip part of the y-axis

I am plotting to different datasets into one graph with pylab.plot(), which works great. But one dataset has values between 0% an 25% and the other has values between 75% and 100%. I want to skip 30% to 70% on the y-axis to save some space. Do you have any suggestions how this might be work with pyplot?
EDIT:
For clearness I added the following graphic. I want to skip 30% to 60% on the y axis, so that the red line and the green line come closer together.
The solution is based on Space_C0wb0ys post.
fig = pylab.figure()
ax = fig.add_subplot(111)
ax.plot( range(1,10), camean - 25, 'ro-' )
ax.plot( range(1,10), oemean , 'go-' )
ax.plot( range(1,10), hlmean , 'bo-' )
ax.set_yticks(range(5, 60, 5))
ax.set_yticklabels(["5","10","15","20","25","30","...","65","70","75"])
ax.legend(('ClassificationAccuracy','One-Error','HammingLoss'),loc='upper right')
pylab.show()
This code creates the following graphic.
You could subtract 40 from the x-values for your second functions to make the range of x-values continuous. This would give you a range from 0% to 70%. Then you can make set the tics and labes of the x-axis as follows:
x_ticks = range(71, 0, 10)
a.set_xticks(x_ticks)
a.set_xticklabels([str(x) for x in [0, 10, 20, 30, 70, 80, 90, 100]])
Where a is the current axes. So basically, you plot your functions in the range from 0% to 70%, but label the axis with a gap.
To illustrate - the following script:
from numpy import arange
import matplotlib.pyplot as plt
x1 = arange(0, 26) # first function
y1 = x1**2
x2 = arange(75, 100) # second function
y2 = x2*4 + 10
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x1, y1)
ax.plot(x2 - 40, y2) # shift second function 40 to left
ax.set_xticks(range(0, 61, 5)) # set custom x-ticks
# set labels for x-ticks - labels have the gap we want
ax.set_xticklabels([str(x) for x in range(0, 26, 5) + range(70, 101, 5)])
plt.show()
Produces the following plot (note the x-labels):
The matplotlib documentation actually has an example of how to do this.
The basic idea is to break up the plotting into two subplots, putting the same graph on each plot, then change the axes for each one to only show the specific part, then make it look nicer.
So, let's apply this. Imagine this is your starting code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Original plot
fig, ax = plt.subplots()
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
plt.show()
And we went to make it so that x is shown split off from the rest.
First, we want to plot the same graph twice, one on top of the other. To do this, the plotting function needs to be generic. Now it should look something like this:
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
fig, (ax1, ax2) = plt.subplots(2, 1)
plot(ax1)
plot(ax2)
Now this seems worse, but we can change the range for each axis to focus on what we want. For now I'm just choosing easy ranges that I know will capture all the data, but I'll focus on making the axes equal later.
# Changes graph axes
ax1.set_ylim(65, 75) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This is getting closer to what we're looking for. Now we need to just make it look a little nicer:
# Hides the spines between the axes
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # Don't put tick labels at the top
ax2.xaxis.tick_bottom()
# Adds slanted lines to axes
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12,
linestyle='none',
color='k',
mec='k',
mew=1,
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
Finally, let's fix the axes. Here you need to do a little math and decide more on the layout. For instance, maybe we want to make the top graph smaller, since the bottom graph has two lines. To do that, we need to change the height ratios for the subplots, like so:
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
Finally, It's a good idea to make the axes match. In this case, because the bottom image is twice the size of the top one, we need to change the axes of one to reflect that. I've chosen to modify the top one in this time. The bottom graph covers a range of 25, which means the top one should cover a range of 12.5.
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This looks good enough to me. You can play around more with the axes or tick labels if you don't want the ticks to overlap with the broken lines.
Final code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
plot(ax1)
plot(ax2)
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
# Hides the spines between the axes
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # Don't put tick labels at the top
ax2.xaxis.tick_bottom()
# Adds slanted lines to axes
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12,
linestyle='none',
color='k',
mec='k',
mew=1,
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
plt.show()

Categories

Resources