Expanding axes to fill figure, same scale on x and y - python

I know 2 things but separately.
figure.tight_layout
will expand my current axes
axes.aspect('equal')
will keep same scale on x and y.
But when I use them both I get square axes view and I want it to be expanded.
By keeping same scale I mean there is same distance from 0 to 1 on x and y axis.
Is there any way to make it happen? Keep same scale and expand to full figure(not only a square)
The answer should work with autoscale

There might be less clumsy way, but at least you can do it manually. A very simple example:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([0,1],[1,0])
ax.set_aspect(1)
ax.set_xlim(0, 1.5)
creates
which honours the aspect ratio.
If you want to have the automatic scaling offered by the tight_layout, then you'll have to do some maths of your own:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([0,1],[1,0])
fig.tight_layout()
# capture the axis positioning in pixels
bb = fig.transFigure.transform(ax.get_position())
x0, y0 = bb[0]
x1, y1 = bb[1]
width = x1 - x0
height = y1 - y0
# set the aspect ratio
ax.set_aspect(1)
# calculate the aspect ratio of the plot
plot_aspect = width / height
# get the axis limits in data coordinates
ax0, ax1 = ax.get_xlim()
ay0, ay1 = ax.get_ylim()
awidth = ax1 - ax0
aheight = ay1 - ay0
# calculate the plot aspect
data_aspect = awidth / aheight
# check which one needs to be corrected
if data_aspect < plot_aspect:
ax.set_xlim(ax0, ax0 + plot_aspect * aheight)
else:
ax.set_ylim(ay0, ay0 + awidth / plot_aspect)
Of course, you may set the xlim and ylim any way you want, you might, for example, want to add an equal amount of space to either end of the scale.

The solution that worked in my case was to call
axis.aspect("equal")
axis.set_adjustable("datalim")
stolen from this example in the documentation.

Related

Python mouse click event.xdata using twinx()

I am using canvas.mpl_connect mouse click listener for my e.g. 100x100 contourf plot with xlim from 0 to 99. Doing so I get e.g [x,y]= 10,20 as desired. However I have to display a second x-axis with different coordinates (e.g. xlim from 0.01 to 1) but I dont want event.xdata to return the coordinates in the style of the second axis. Is there a possibility to do so?
You could use the transformations in matplotlib. You would want to convert from the data-coordinates in ax2 to display coordinates (which are universal between the two axes) and then into data coordinates for ax1. Helpfully, you can combine transformations.
For example:
import matplotlib.pyplot as plt
import numpy as np
fig, ax1 = plt.subplots(1)
# First axis, with x-values going from 0 to 100
x1 = np.linspace(0, 100, 101)
y1 = np.sin(2 * np.pi * x1 / max(x1))
ax1.plot(x1, y1, 'b.-')
# Second axis, x values going from 0 to 1
ax2 = ax1.twiny()
x2 = np.linspace(0, 1, 11)
y2 = np.cos(2 * np.pi * x2 / max(x2))
ax2.plot(x2, y2, 'r.-')
# Create a combined transform from ax2 data to ax1 data
combinedTransform = ax2.transData + ax1.transData.inverted()
def onclick(event):
# event has x and y in data coordinates for ax2:
pt_data2 = (event.xdata, event.ydata)
# Convert them into data coordinates for ax1:
pt_data1 = combinedTransform.transform(pt_data2)
# ...
cid = fig.canvas.mpl_connect('button_press_event', onclick)
It feels like there would be a nicer way (somehow tell the event listener which axis you want the xdata and ydata to be valid for, but I don't know it. Sorry)
thanks, I implemented something like this as well. The problem is that there is actually no direct linear transformation behind my data. I solved the issue by just calling the second axis in a function after I finished setting the marker or choosing points. It's not beautiful but should be fine for a Master's thesis!

scatterplot and combined polar histogram in matplotlib

I am attempting to produce a plot like this which combines a cartesian scatter plot and a polar histogram. (Radial lines optional)
A similar solution (by Nicolas Legrand) exists for looking at differences in x and y (code here), but we need to look at ratios (i.e. x/y).
More specifically, this is useful when we want to look at the relative risk measure which is the ratio of two probabilities.
The scatter plot on it's own is obviously not a problem, but the polar histogram is more advanced.
The most promising lead I have found is this central example from the matplotlib gallery here
I have attempted to do this, but have run up against the limits of my matplotlib skills. Any efforts moving towards this goal would be great.
I'm sure that others will have better suggestions, but one method that gets something like you want (without the need for extra axes artists) is to use a polar projection with a scatter and bar chart together. Something like
import matplotlib.pyplot as plt
import numpy as np
x = np.random.uniform(size=100)
y = np.random.uniform(size=100)
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
h, b = np.histogram(phi, bins=np.linspace(0, np.pi/2, 21), density=True)
colors = plt.cm.Spectral(h / h.max())
ax = plt.subplot(111, projection='polar')
ax.scatter(phi, r, marker='.')
ax.bar(b[:-1], h, width=b[1:] - b[:-1],
align='edge', bottom=np.max(r) + 0.2, color=colors)
# Cut off at 90 degrees
ax.set_thetamax(90)
# Set the r grid to cover the scatter plot
ax.set_rgrids([0, 0.5, 1])
# Let's put a line at 1 assuming we want a ratio of some sort
ax.set_thetagrids([45], [1])
which will give
It is missing axes labels and some beautification, but it might be a place to start. I hope it is helpful.
You can use two axes on top of each other:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.8,.8], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
ax2.set_rorigin(-1)
ax2.set_thetamax(90)
plt.show()
Ok. Thanks to the answer from Nicolas, and the answer from tomjn I have a working solution :)
import numpy as np
import matplotlib.pyplot as plt
# Scatter data
n = 50
x = 0.3 + np.random.randn(n)*0.1
y = 0.4 + np.random.randn(n)*0.02
def radial_corner_plot(x, y, n_hist_bins=51):
"""Scatter plot with radial histogram of x/y ratios"""
# Axis setup
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.6,.6], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
ax2.set_rorigin(-20)
ax2.set_thetamax(90)
# define useful constant
offset_in_radians = np.pi/4
def rotate_hist_axis(ax):
"""rotate so that 0 degrees is pointing up and right"""
ax.set_theta_offset(offset_in_radians)
ax.set_thetamin(-45)
ax.set_thetamax(45)
return ax
# Convert scatter data to histogram data
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
h, b = np.histogram(phi,
bins=np.linspace(0, np.pi/2, n_hist_bins),
density=True)
# SCATTER PLOT -------------------------------------------------------
ax1.scatter(x,y)
ax1.set(xlim=[0, 1], ylim=[0, 1], xlabel="x", ylabel="y")
ax1.spines['right'].set_visible(False)
ax1.spines['top'].set_visible(False)
# HISTOGRAM ----------------------------------------------------------
ax2 = rotate_hist_axis(ax2)
# rotation of axis requires rotation in bin positions
b = b - offset_in_radians
# plot the histogram
bars = ax2.bar(b[:-1], h, width=b[1:] - b[:-1], align='edge')
def update_hist_ticks(ax, desired_ratios):
"""Update tick positions and corresponding tick labels"""
x = np.ones(len(desired_ratios))
y = 1/desired_ratios
phi = np.arctan2(y,x) - offset_in_radians
# define ticklabels
xticklabels = [str(round(float(label), 2)) for label in desired_ratios]
# apply updates
ax2.set(xticks=phi, xticklabels=xticklabels)
return ax
ax2 = update_hist_ticks(ax2, np.array([1/8, 1/4, 1/2, 1, 2, 4, 8]))
# just have radial grid lines
ax2.grid(which="major", axis="y")
# remove bin count labels
ax2.set_yticks([])
return (fig, [ax1, ax2])
fig, ax = radial_corner_plot(x, y)
Thanks for the pointers!

matplotlib: place axes relative to other axes, with automatic updating

Matplotlib has well documented methods of how to place multiple sets of axes in a figure window, but I cannot figure out how to define the position of one set of axes relative to the position of another set of axes. For example,
import matplotlib.pyplot as plt
import numpy as np
#Define data
x1 = np.arange(0,10,0.01)
y1 = np.sqrt(x1)
x2 = x1
y2 = 1.0/2.0 * x2**2.0
#Generate vertically stacked plots
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax1.plot(x1,y1)
ax2 = fig.add_subplot(212)
ax2.plot(x2,y2)
fig.savefig('nice_stacked_plots.png')
gives the following plot:
This is all well and good, but when I change the size of the bottom axes
#Change the size of the bottom plot
bbox2 = ax2.get_position()
ax2.set_position([bbox2.x0, bbox2.y0, bbox2.width, bbox2.height * 1.25])
ax2.set_ylim(0,60)
fig.savefig('overlapping_stacked_plots.png')
the bottom axes overlap with the top axes
I realize I could subsequently update the position of the top axes to remove the overlap, but I would like to just specify the top axes position relative to the bottom axes at the outset, and have things automatically update.
For example, in the annotate tutorial it is possible to place an annotation and then place a 2nd annotation at a specified offset from the 1st annotation using the OffsetFrom class. If the 1st annotation moves, then the 2nd annotation moves with it. I would like to do something similar with axes.
I am afraid I have no general answer to offer, but do you know about add_axes?
It lets you define the location of your subplots precisely - it is then easy to make one dependent of the other.
Here is an example - as I said, pretty specific for your task, but perhaps it may inspire you?
# General aspect of the Fig (margins)
left = 0.1
right = 0.05
width= 1.-left-right
bottom = 0.1
top = 0.05
hspace = 0.10 #space between the subplots
def placeSubplots(fig, ax2height = (1.-top-bottom-hspace)/2.):
ax1height = 1-top-bottom-hspace-ax2height
ax1 = fig.add_axes([left, bottom+ax2height+hspace, width, ax1height])
ax1.plot(x1, y1)
ax2 = fig.add_axes([left, bottom, width, ax2height])
ax2.plot(x2, y2)
return fig
fig1 = placeSubplots(plt.figure())
fig2 = placeSubplots(plt.figure(), ax2height=0.6)
fig1.savefig('fig1_equal_heigth.png')
fig2.savefig('fig2_ax2_taller.png')
fig1:
fig2:
Above, the second axis height is specified in an absolute way, but you could define the height of your subplots as a ratio between them too:
def placeSubplotsRatio(fig, ax1ax2ratio = 1.):
subplotSpace = 1.-top-bottom-hspace
ax1height = subplotSpace/(1.+1./ax1ax2ratio)
ax2height = subplotSpace/(1.+ax1ax2ratio)
ax1 = fig.add_axes([left, bottom+ax2height+hspace, width, ax1height])
ax1.plot(x1, y1)
ax2 = fig.add_axes([left, bottom, width, ax2height])
ax2.plot(x2, y2)
return fig
fig3 = placeSubplotsRatio(plt.figure()) # idem as fig1
fig4 = placeSubplotsRatio(plt.figure(), ax1ax2ratio=3.) #ax1 is 3 times taller
fig5 = placeSubplotsRatio(plt.figure(), ax1ax2ratio=0.25) #ax2 is 4 times taller
fig4.savefig('fig4_ax1ax2ratio3.png')
fig5.savefig('fig5_ax1ax2ratio025.png')
fig4:
fig5:

Scaling the second axe on a histogram with Matplotlib

I want to have a second axe on my histogram, with the pourcentage corresponding to each bin, like if I used normed=True. I tried to use twins, but the scale is not correct.
x = np.random.randn(10000)
plt.hist(x)
ax2 = plt.twinx()
plt.show()
Bonus point if you can make it work with log scaled x :)
plt.hist returns the bins and the number of data in each bucket. You may use these to compute the area under the histogram, and using that you may find the normalized height of each bar. twinx axis can be aligned accordingly:
xs = np.random.randn(10000)
ax1 = plt.subplot(111)
cnt, bins, patches = ax1.hist(xs)
# area under the istogram
area = np.dot(cnt, np.diff(bins))
ax2 = ax1.twinx()
ax2.grid('off')
# align the twinx axis
ax2.set_yticks(ax1.get_yticks() / area)
lb, ub = ax1.get_ylim()
ax2.set_ylim(lb / area, ub / area)
# display the y-axis in percentage
from matplotlib.ticker import FuncFormatter
frmt = FuncFormatter(lambda x, pos: '{:>4.1f}%'.format(x*100))
ax2.yaxis.set_major_formatter(frmt)

making hexbin in matplotlib python fill in empty space on a square axis?

I'm trying to use hexbin to plot some data on a square axis. I use the following:
import matplotlib.cm as cm
plt.figure()
num_pts = 1000
x = rand(num_pts) * 100
y = rand(num_pts) * 250
x_min = 0
x_max = 150
x_step = 25
y_min = 50
y_max = 300
y_step = 50
s = plt.subplot(1,1,1)
plt.hexbin(x,y,cmap=cm.jet,gridsize=20)
plt.xticks(range(x_min,x_max+x_step,x_step))
plt.yticks(range(y_min,y_max+y_step,y_step))
# square axes
s.axes.set_aspect(1/s.axes.get_data_ratio())
I'd like the axes to be square and I want to set my own xticks/yticks and x-y limits. for some of the axes values, there won't be data and so the counts computed by hexbin should be zero for those -- I would like hexbin to plot that as empty space, rather than leave it "white" / blank if you use the cm.jet colormap.
What I get now is this.
How can I get it to fill the empty space using its colormap? thanks.
I believe the answer is to use the extent= keyword argument, as in:
plt.hexbin(x, y, cmap=cm.jet, gridsize = 20, extent=[x_min, x_max, y_min, y_max])
You can also just create an axis and set its background color to the minimum colormap value (or any color you desire):
fig = plt.figure()
ax = fig.add_subplot(111,axisbg=cm.jet(0))

Categories