matplotlib 3: 3D scatter plots with tight_layout - python

I have some code which produces a 3D scatter plot using matplotlib's scatter in conjunction with tight_layout, see the simplified code below:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import proj3d
fig = plt.figure()
ax = fig.gca(projection='3d')
N = 100
x = np.random.random(N)
y = np.random.random(N)
z = np.random.random(N)
ax.scatter(x, y, z)
plt.tight_layout() # <-- Without this, everything is fine
plt.savefig('scatter.png')
In matplotlib 2.2.3 this makes a figure like so:
Similar output is generated by older versions, at least back to 1.5.1. When using the new version 3.0.0, something goes wrong at plt.tight_layout() and I get the following output:
Accompanying this is the warning
.../matplotlib/tight_layout.py:177: UserWarning: The left and right margins cannot be made large enough to accommodate all axes decorations
One may argue that using tight_layout with no arguments as here does not (on older matplotlibs) consistently lead to the expected tightened margins anyway, and so one should refrain from using tight_layout with 3D plots in the first place. However, by manually tweaking the arguments to tight_layout it is (used to be) a decent way to trim the margins even on 3D plots.
My guess is that this is a bug in matplotlib, but maybe they've made some deliberate change I havn't picked up on. Any pointers about a fix is appreciated.

Thanks to the comment by ImportanceOfBeingErnest, it now works:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import proj3d
fig = plt.figure()
ax = fig.gca(projection='3d')
N = 100
x = np.random.random(N)
y = np.random.random(N)
z = np.random.random(N)
ax.scatter(x, y, z)
# The fix
for spine in ax.spines.values():
spine.set_visible(False)
plt.tight_layout()
plt.savefig('scatter.png')
From the links in the comment, it seems that this will be fixed in matplotlib 3.0.x. For now, the above may be used.

plt.tight_layout()
plt.show()
Right below your main body code of plotting.

Related

I have problems defining logarithmic axis values [duplicate]

I want to plot a graph with one logarithmic axis using matplotlib.
I've been reading the docs, but can't figure out the syntax. I know that it's probably something simple like 'scale=linear' in the plot arguments, but I can't seem to get it right
Sample program:
import pylab
import matplotlib.pyplot as plt
a = [pow(10, i) for i in range(10)]
fig = plt.figure()
ax = fig.add_subplot(2, 1, 1)
line, = ax.plot(a, color='blue', lw=2)
pylab.show()
You can use the Axes.set_yscale method. That allows you to change the scale after the Axes object is created. That would also allow you to build a control to let the user pick the scale if you needed to.
The relevant line to add is:
ax.set_yscale('log')
You can use 'linear' to switch back to a linear scale. Here's what your code would look like:
import pylab
import matplotlib.pyplot as plt
a = [pow(10, i) for i in range(10)]
fig = plt.figure()
ax = fig.add_subplot(2, 1, 1)
line, = ax.plot(a, color='blue', lw=2)
ax.set_yscale('log')
pylab.show()
First of all, it's not very tidy to mix pylab and pyplot code. What's more, pyplot style is preferred over using pylab.
Here is a slightly cleaned up code, using only pyplot functions:
from matplotlib import pyplot
a = [ pow(10,i) for i in range(10) ]
pyplot.subplot(2,1,1)
pyplot.plot(a, color='blue', lw=2)
pyplot.yscale('log')
pyplot.show()
The relevant function is pyplot.yscale(). If you use the object-oriented version, replace it by the method Axes.set_yscale(). Remember that you can also change the scale of X axis, using pyplot.xscale() (or Axes.set_xscale()).
Check my question What is the difference between ‘log’ and ‘symlog’? to see a few examples of the graph scales that matplotlib offers.
if you want to change the base of logarithm, just add:
plt.yscale('log',base=2)
Before Matplotlib 3.3, you would have to use basex/basey as the bases of log
You simply need to use semilogy instead of plot:
from pylab import *
import matplotlib.pyplot as pyplot
a = [ pow(10,i) for i in range(10) ]
fig = pyplot.figure()
ax = fig.add_subplot(2,1,1)
line, = ax.semilogy(a, color='blue', lw=2)
show()
I know this is slightly off-topic, since some comments mentioned the ax.set_yscale('log') to be "nicest" solution I thought a rebuttal could be due. I would not recommend using ax.set_yscale('log') for histograms and bar plots. In my version (0.99.1.1) i run into some rendering problems - not sure how general this issue is. However both bar and hist has optional arguments to set the y-scale to log, which work fine.
references:
http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.bar
http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.hist
So if you are simply using the unsophisticated API, like I often am (I use it in ipython a lot), then this is simply
yscale('log')
plot(...)
Hope this helps someone looking for a simple answer! :).

matplotlib: plotting histogram plot just above scatter plot

I would like to make beautiful scatter plots with histograms above and right of the scatter plot, as it is possible in seaborn with jointplot:
I am looking for suggestions on how to achieve this. In fact I am having some troubles in installing pandas, and also I do not need the entire seaborn module
I encountered the same problem today. Additionally I wanted a CDF for the marginals.
Code:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
x = np.random.beta(2,5,size=int(1e4))
y = np.random.randn(int(1e4))
fig = plt.figure(figsize=(8,8))
gs = gridspec.GridSpec(3, 3)
ax_main = plt.subplot(gs[1:3, :2])
ax_xDist = plt.subplot(gs[0, :2],sharex=ax_main)
ax_yDist = plt.subplot(gs[1:3, 2],sharey=ax_main)
ax_main.scatter(x,y,marker='.')
ax_main.set(xlabel="x data", ylabel="y data")
ax_xDist.hist(x,bins=100,align='mid')
ax_xDist.set(ylabel='count')
ax_xCumDist = ax_xDist.twinx()
ax_xCumDist.hist(x,bins=100,cumulative=True,histtype='step',density=True,color='r',align='mid')
ax_xCumDist.tick_params('y', colors='r')
ax_xCumDist.set_ylabel('cumulative',color='r')
ax_yDist.hist(y,bins=100,orientation='horizontal',align='mid')
ax_yDist.set(xlabel='count')
ax_yCumDist = ax_yDist.twiny()
ax_yCumDist.hist(y,bins=100,cumulative=True,histtype='step',density=True,color='r',align='mid',orientation='horizontal')
ax_yCumDist.tick_params('x', colors='r')
ax_yCumDist.set_xlabel('cumulative',color='r')
plt.show()
Hope it helps the next person searching for scatter-plot with marginal distribution.
Here's an example of how to do it, using gridspec.GridSpec:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import numpy as np
x = np.random.rand(50)
y = np.random.rand(50)
fig = plt.figure()
gs = GridSpec(4,4)
ax_joint = fig.add_subplot(gs[1:4,0:3])
ax_marg_x = fig.add_subplot(gs[0,0:3])
ax_marg_y = fig.add_subplot(gs[1:4,3])
ax_joint.scatter(x,y)
ax_marg_x.hist(x)
ax_marg_y.hist(y,orientation="horizontal")
# Turn off tick labels on marginals
plt.setp(ax_marg_x.get_xticklabels(), visible=False)
plt.setp(ax_marg_y.get_yticklabels(), visible=False)
# Set labels on joint
ax_joint.set_xlabel('Joint x label')
ax_joint.set_ylabel('Joint y label')
# Set labels on marginals
ax_marg_y.set_xlabel('Marginal x label')
ax_marg_x.set_ylabel('Marginal y label')
plt.show()
I strongly recommend to flip the right histogram by adding these 3 lines of code to the current best answer before plt.show() :
ax_yDist.invert_xaxis()
ax_yDist.yaxis.tick_right()
ax_yCumDist.invert_xaxis()
The advantage is that any person who is visualizing it can compare easily the two histograms just by moving and rotating clockwise the right histogram on their mind.
On contrast, in the plot of the question and in all other answers, if you want to compare the two histograms, your first reaction is to rotate the right histogram counterclockwise, which leads to wrong conclusions because the y axis gets inverted. Indeed, the right CDF of the current best answer looks decreasing at first sight:

How do to tighten the bounds of my 'matplotlib' figures to be closer to my axes?

When matplotlib makes figures, I find that it "pads" the space around axes too much for my taste (and in an asymmetrical way). For example with
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
x, y = 12*np.random.rand(2, 1000)
ax.set(xlim=[2,10])
ax.plot(x, y, 'go')
I get something that looks like
(here for example in Adobe Illustrator).
I'd like the bounds of the figure to be closer to the axes on all sides, especially on the left and right.
How can I adjust these bounds programmatically in matplotlib, relative to each axis?
try:
plt.tight_layout()
the default parameter set is:
plt.tight_layout(pad=1.08, h_pad=None, w_pad=None, rect=None)

How to scale axes in mplot3d

I can't seem to find documentation regarding the ability to scale axes in a 3d image using matplotlib.
For example, I have the image:
And the axes have different scales. I would like them to be uniform.
Usually it is easiest for you to include some of the code that generated the image, so we can see what you've tried and also the general setup of your code. That being said, the inclusion of the following should work:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import pyplot as plt
fig = plt.figure()
ax = Axes3D(fig)
ax.set_xlim3d(0, 1000)
ax.set_ylim3d(0, 1000)
ax.set_zlim3d(0, 1000)
The following sets the box aspect ratio from the current axes limits to achieve the "equal" behavior [source]:
ax.set_box_aspect([ub - lb for lb, ub in (getattr(ax, f'get_{a}lim')() for a in 'xyz')])

Matplotlib 3D scatter color lost after redraw

Related to this question, I want a 3D scatter plot with prescribed colors for each point. The example posted in the question works on my system, but after the first redraw (for instance after saving or if I rotate the image) the color seems to be lost, i.e. all the points are drawn in blue color with the usual depth information. Please see the modified example below.
My system is Python 2.6.7 with matplotlib 1.1.0 installed from macports on a mac 10.8.0. I use the MacOSX backend.
Does anyone know how to circumvent this problem?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Create Map
cm = plt.get_cmap("RdYlGn")
x = np.random.rand(30)
y = np.random.rand(30)
z = np.random.rand(30)
col = np.arange(30)
fig = plt.figure()
ax3D = fig.add_subplot(111, projection='3d')
ax3D.scatter(x, y, z, s=30, c=col, marker='o', cmap=cm)
plt.savefig('image1.png')
plt.savefig('image2.png')
Here are the two images, I get:
It's not clear why this is happening, and it certainly is a bug. Here I provide a hack to get the result you want, though it is not as automatic as one would want.
For some reason, the Patch3DCollection representing the scatter points is not updated after the first rendering. This update is essential, because it is where unique colors are set for each collection patch. To force it to reinitialize, you can use the changed method on the Patch3DCollection (really a ScalarMappable method), and this just documents that a change happend. When the figure is drawn, it checks if an update happened, and then it redefines the colors. If it didn't, this process is skipped.
To force this update to occur automatically, one would like to do this on every 'draw' event. To do this, one must register a method using the canvas's mpl_connect method (see linked tutorial).
This example shows how saving the figure twice preserves the color mapping, but if you uncomment the plt.show() line, it will still work (on rotation for example).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Create Map
cm = plt.get_cmap("RdYlGn")
# added a seed so consistant plotting of points
np.random.seed(101)
x = np.random.rand(30)
y = np.random.rand(30)
z = np.random.rand(30)
col = np.arange(30)
fig = plt.figure()
#ax = fig.add_subplot(111)
#scatCollection = ax.scatter(x,y,
ax3D = fig.add_subplot(111, projection='3d')
# keep track of the Patch3DCollection:
scatCollection = ax3D.scatter(x, y, z, s=30,
c=col,
marker='o',
cmap=cm
)
def forceUpdate(event):
global scatCollection
scatCollection.changed()
fig.canvas.mpl_connect('draw_event',forceUpdate)
#plt.show()
plt.savefig('image1.png')
plt.savefig('image2.png')
Ideally it should not be required to do this, and the global scatCollection should be accessed using other methods (I'm working on doing this). But this works for now...

Categories

Resources