How to change the x-axis unit in matplotlib? [duplicate] - python

I am creating a plot in python. Is there a way to re-scale the axis by a factor? The yscale and xscale commands only allow me to turn log scale off.
Edit:
For example. If I have a plot where the x scales goes from 1 nm to 50 nm, the x scale will range from 1x10^(-9) to 50x10^(-9) and I want it to change from 1 to 50. Thus, I want the plot function to divide the x values placed on the plot by 10^(-9)

As you have noticed, xscale and yscale does not support a simple linear re-scaling (unfortunately). As an alternative to Hooked's answer, instead of messing with the data, you can trick the labels like so:
ticks = ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x*scale))
ax.xaxis.set_major_formatter(ticks)
A complete example showing both x and y scaling:
import numpy as np
import pylab as plt
import matplotlib.ticker as ticker
# Generate data
x = np.linspace(0, 1e-9)
y = 1e3*np.sin(2*np.pi*x/1e-9) # one period, 1k amplitude
# setup figures
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
# plot two identical plots
ax1.plot(x, y)
ax2.plot(x, y)
# Change only ax2
scale_x = 1e-9
scale_y = 1e3
ticks_x = ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x/scale_x))
ax2.xaxis.set_major_formatter(ticks_x)
ticks_y = ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x/scale_y))
ax2.yaxis.set_major_formatter(ticks_y)
ax1.set_xlabel("meters")
ax1.set_ylabel('volt')
ax2.set_xlabel("nanometers")
ax2.set_ylabel('kilovolt')
plt.show()
And finally I have the credits for a picture:
Note that, if you have text.usetex: true as I have, you may want to enclose the labels in $, like so: '${0:g}$'.

Instead of changing the ticks, why not change the units instead? Make a separate array X of x-values whose units are in nm. This way, when you plot the data it is already in the correct format! Just make sure you add a xlabel to indicate the units (which should always be done anyways).
from pylab import *
# Generate random test data in your range
N = 200
epsilon = 10**(-9.0)
X = epsilon*(50*random(N) + 1)
Y = random(N)
# X2 now has the "units" of nanometers by scaling X
X2 = (1/epsilon) * X
subplot(121)
scatter(X,Y)
xlim(epsilon,50*epsilon)
xlabel("meters")
subplot(122)
scatter(X2,Y)
xlim(1, 50)
xlabel("nanometers")
show()

To set the range of the x-axis, you can use set_xlim(left, right), here are the docs
Update:
It looks like you want an identical plot, but only change the 'tick values', you can do that by getting the tick values and then just changing them to whatever you want. So for your need it would be like this:
ticks = your_plot.get_xticks()*10**9
your_plot.set_xticklabels(ticks)

Related

Show custom tick value in plot

Let's say I have made a plot, and in that plot there is a specific point where I draw vertical line from to the x-axis. This point has the x-value 33.55 for example. However, my tick separation is something like 10 or 20 from 0 to 100.
So basically: Is there a way in which I can add this single custom value to the tick axis, so it shows together with all the other values that where there before ?
Use np.append to add to the array of ticks:
import numpy as np
from matplotlib import pyplot as plt
x = np.random.rand(100) * 100
y = np.random.rand(100) * 100
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(x, y)
ax.set_xticks(np.append(ax.get_xticks(), 33.55))
Note that if your plot is not big enough, the tick labels may overlap.
If you want the new tick to "clear its orbit", so to speak:
special_value = 33.55
black_hole_radius = 10
new_ticks = [value for value in ax.get_xticks() if abs(value - special_value) > black_hole_radius] + [special_value]
ax.set_xticks(new_ticks)

Can't Get Axis to Align Right on MatPlotLib 3d

I'm trying to do a 3d matplot graph. I'm having trouble getting the full axis to show with nicely aligned labels. I've outlined the steps I've tried below.
1) I can set the y-axis labels using:
yTicks = list(range(0,90,5)
ax.set_yticks(range(len(yTicks)), True)
However, as you can see, the labels are very badly aligned. It also isn't matching what I've actually defined, which should have been ticks counting by 5, not 10.
2) If I try using set_yticklabels as well, though, the alignment fixes but it only prints part of the axis. Here is the code and image:
ax.set_yticklabels(yTicks, verticalalignment='baseline',
horizontalalignment='left')
Notice how the y-axis went from 80 to 40.
3) And if I get rid of the True in set_yticks, everything squishes together:
4) Finally, if I use both set_yticks and set_yticklabels calling get_yticks() in the labels function, it almost works but you can see the axis lines extend beyond the "surface" of the graph:
ax.set_yticks(range(len(yTicks)), True)
ax.set_yticklabels(ax.get_yticks(), verticalalignment='baseline',
horizontalalignment='left')
5) Here is a more complete version of my code for reference:
plt.clf()
ax = plt.axes(projection='3d')
ax.bar3d(x,y,z,
1,1,[val*-1 if val != 0 else 0 for val in z])
xTicks = list(range(0,25,2))
yTicks = list(range(30,90,5))
ax.set_zlim(0, 1)
ax.set_xticks(range(len(xTicks)), True)
ax.set_yticks(range(len(yTicks)), True)
ax.set_xticklabels(ax.get_xticks(),
verticalalignment='baseline',
horizontalalignment='left')
ax.set_yticklabels(ax.get_yticks(),
verticalalignment='baseline',
horizontalalignment='left')
plt.savefig(file_path)
How can I get it to show my full axis (0-90) at intervals of 5 and have it aligned well?
6) UPDATE: Per the conversation below with #ImportanceOfBeingErnest, here is the issue I'm still experiencing using the following code:
x=[15,28,20]; y=[30,50,80]; z=[1,1,1]
plt.clf()
ax = plt.axes(projection='3d')
ax.bar3d(x,y,z,
1,1,[val*-1 if val != 0 else 0 for val in z])
xTicks = list(range(0,25,2))
yTicks = list(range(30,90,5))
ax.set_xticks(xTicks)
ax.set_yticks(yTicks)
ax.set_yticklabels(ax.get_yticks(),
verticalalignment='baseline',
horizontalalignment='left')
ax.set_zlim(0, 1)
plt.savefig(getSaveGraphPath(save_name))
As commented, you can set the ticks via ax.set_yticks.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x=[15,28,20]; y=[30,50,80]; z=[1,1,1]
ax = plt.axes(projection='3d')
ax.bar3d(x,y,z,
1,1,[val*-1 if val != 0 else 0 for val in z])
yTicks = list(range(30,90,5))
ax.set_yticks(yTicks)
ax.set_yticklabels(ax.get_yticks(),
verticalalignment='baseline',
horizontalalignment='left')
ax.set_zlim(0, 1)
plt.show()
This will show the desired 5 unit steps on the y axis.
So after much trial-and-error, the only way I could get the graph to render axes appropriately in various limit cases is as follows. I'm not completely happy with it (notice how the last y-tick label doesn't appear) but it is the only version that has the numbers actually next to their tick marks). I had to let x and y limits be effective only if the data didn't exceed their values, whereas the z boundary is a hard limit. I don't claim to understand why these permutations are all necessary (this all is only an issue with 3D plotting), but this is the solution that works for me:
plt.clf()
ax = plt.axes(projection='3d')
# Need to force include fake NaN data at the axis limits to make sure labels
# render correctly
#
# xLims and yLims create boundaries only if data doesn't stretch beyond them
xstart, xend = xLims
ystart, yend = yLims
x = [xstart] + x + [xend]
y = [ystart] + y + [yend]
z = [numpy.nan] + z + [numpy.nan]
# Plot graph
ax.bar3d(x,y,z,1,1,[val*-1 if val != 0 else 0 for val in z])
# Set z boundary (after graph creation)
ax.set_zbound(zBounds)
# Need to adjust labels slightly to make sure they align properly
use_x_ticks = ax.get_xticks()
### ON SOME SYSTEMS, use_x_ticks = ax.get_xticks()[1:] is needed for correct alignment ###
ax.set_xticklabels([int(x) if x==int(x) else x for x in use_x_ticks],
horizontalalignment='right',
verticalalignment='baseline')
ax.set_yticklabels([int(y) if y==int(y) else y for y in ax.get_yticks()],
verticalalignment='top')
# Save graph
plt.savefig(file_save_path)
As you can see below, everything is nicely aligned:

Matplotlib: non-alignment of the dots on a plot

I am using matplotlib to do a Component-Component plus Residual (CCPR) Plots (= partial residual plot)
This script :
fig, ax = plt.subplots(figsize=(5, 5))
fig = sm.graphics.plot_ccpr(lm_full, 'diag[T.sz]', ax=ax)
plt.close
Gives :
How can I modify my script to get something like
I don't want my dots to be aligned. In both cases, the variables of the x axis are dummy variable (ill vs healthy controls).
This may seem stupid, but I don't even know how to express what I want : it's much more easier with the images.
It sounds like you want to add some jitter to the x values, like this:
import numpy as np
# get x and y coordinates from the axes
coords = ax.collections[0].get_offsets()
# add small random number to each x coordinate
coords[:,0] = coords[:,0] + np.random.rand(coords.shape[0]) * 0.01
# move the points to the new coordinates
ax.collections[0].set_offsets(coords)

Is it possible to test if the legend is covering any data in matplotlib/pyplot

Python beginner so apologies if incorrect terminology at any point.
I am using the legend(loc='best', ...) method and it works 99% of the time. However, when stacking more than 9 plots (i.e. i>9 in example below) on a single figure, with individual labels, it defaults to center and covers the data.
Is there a way to run a test in the script that will give a true/false value if the legend is covering any data points?
Very simplified code:
fig = plt.figure()
for i in data:
plt.plot(i[x, y], label=LABEL)
fig.legend(loc='best')
fig.savefig()
Example of legend covering data
One way is to add some extra space at the bottom/top/left or right side of the axis (in your case I would prefer top or bottom), by changing the limits slightly. Doing so makes the legend fit below the data. Add extra space by setting a different y-limit with ax.set_ylim(-3e-4, 1.5e-4) (the upper limit is approximately what it is in your figure and -3 is a estimate of what you need).
What you also need to do is to add split the legend into more columns, with the keyword ncol=N when creating the legend.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(0, 1, 100)
y = 3.5 * x - 2
for i in range(9):
ax.plot(x, y + i / 10., label='iiiiiiiiiiii={}'.format(i))
ax.set_ylim(-3, 1.5)
ax.legend(loc='lower center', ncol=3) # ncol=3 looked nice for me, maybe you need to change this
plt.show()
EDIT
Another solution is to put the legend in a separate axis like I do in the code below. The data-plot does not need to care about making space for the legend or anything and you should have enough space in the axis below to put all your line-labels. If you need more space, you can easily change the ratio of the upper axis to the lower axis.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(211)
ax_leg = fig.add_subplot(212)
x = np.linspace(0, 1, 100)
y = 3.5 * x - 2
lines = []
for i in range(9): #for plotting the actual data
li, = ax.plot(x, y + i / 10., label='iiiiiiiiiiii={}'.format(i))
lines.append(li)
for line in lines: # just to make the legend plot
ax_leg.plot([], [], line.get_color(), label=line.get_label())
ax_leg.legend(loc='center', ncol=3, ) # ncol=3 looked nice for me, maybe you need to change this
ax_leg.axis('off')
fig.show()

same scale of Y axis on differents figures

I try to plot different data with similar representations but slight different behaviours and different origins on several figures. So the min & max of the Y axis is different between each figure, but the scale too.
e.g. here are some extracts of my batch plotting :
Does it exists a simple way with matplotlib to constraint the same Y step on those different figures, in order to have an easy visual interpretation, while keeping an automatically determined Y min and Y max ?
In others words, I'd like to have the same metric spacing between each Y-tick
you could use a MultipleLocator from the ticker module on both axes to define the tick spacings:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig=plt.figure()
ax1=fig.add_subplot(211)
ax2=fig.add_subplot(212)
ax1.set_ylim(0,100)
ax2.set_ylim(40,70)
# set ticks every 10
tickspacing = 10
ax1.yaxis.set_major_locator(ticker.MultipleLocator(base=tickspacing))
ax2.yaxis.set_major_locator(ticker.MultipleLocator(base=tickspacing))
plt.show()
EDIT:
It seems like your desired behaviour was different to how I interpreted your question. Here is a function that will change the limits of the y axes to make sure ymax-ymin is the same for both subplots, using the larger of the two ylim ranges to change the smaller one.
import matplotlib.pyplot as plt
import numpy as np
fig=plt.figure()
ax1=fig.add_subplot(211)
ax2=fig.add_subplot(212)
ax1.set_ylim(40,50)
ax2.set_ylim(40,70)
def adjust_axes_limits(ax1,ax2):
yrange1 = np.ptp(ax1.get_ylim())
yrange2 = np.ptp(ax2.get_ylim())
def change_limits(ax,yr):
new_ymin = ax.get_ylim()[0] - yr/2.
new_ymax = ax.get_ylim()[1] + yr/2.
ax.set_ylim(new_ymin,new_ymax)
if yrange1 > yrange2:
change_limits(ax2,yrange1-yrange2)
elif yrange2 > yrange1:
change_limits(ax1,yrange2-yrange1)
else:
pass
adjust_axes_limits(ax1,ax2)
plt.show()
Note that the first subplot here has expanded from (40, 50) to (30, 60), to match the y range of the second subplot
The answer of Tom is pretty fine !
But I decided to use a simpler solution
I define an arbitrary yrange for all my plots e.g.
yrang = 0.003
and for each plot, I do :
ymin, ymax = ax.get_ylim()
ymid = np.mean([ymin,ymax])
ax.set_ylim([ymid - yrang/2 , ymid + yrang/2])
and possibly:
ax.yaxis.set_major_locator(ticker.MultipleLocator(base=0.005))

Categories