I'm trying to create a 1D-Dotplot with python, similar to this:
https://owncloud.tu-berlin.de/public.php?service=files&t=9ead31dfc988757321c7ac391920c48a
I tried using the plot.scatter method from matplotlib, but it nees data for the x-axis. I tried setting all x-values to '1', but it turns out as kind of a 2d-diagram, anyway:
https://owncloud.tu-berlin.de/public.php?service=files&t=ab9f0f521f57526e871259f3a520d94a
How can I draw a real 1d-dotplot? I found nothing in the matplotlib-docs...
I would like to use matplotlib but am also open to other suggestions.
Thanks in advance!
Cheers, Jakob
As far as I can see, also the 1D-Dotplot you're showing is two dimensional but only strongly limited in x direction.
I don't know whether there already exists something like that but the following code is doing what you ask for.
import numpy as np
import matplotlib.pyplot as mpl
# your data
data = 3. + 0.7 * np.random.randn(N)
# a small spreading of the data in x direction
x = 0.2 * np.random.randn(data.size)
# the plotting
fig,ax = mpl.subplots(1,figsize=(0.5,5))
ax.set_axis_bgcolor('#FFD7B1')
ax.scatter(x,data,alpha=0.2,c='k')
ax.plot([-1,1],[np.mean(data),np.mean(data)],'r',linewidth=2)
ax.set_xlim((-1,1))
ax.set_ylim((1,6))
ax.set_xticks([])
ax.grid(True,axis='y')
ax.set_ylabel('Note')
Your own solution is close! Just play with the aspect ratio to "squash" down the size along the x-axis:
import numpy as np
import matplotlib.pyplot as plt
x = np.random.random(100)
y = np.random.randn(100)
fh, ax = plt.subplots(1,1)
ax.scatter(x,y)
ax.set_xlim(-.5, 1.5)
ax.axes.get_xaxis().set_visible(False) # remove the x-axis and its ticks
ax.set_aspect(5, adjustable='box') # adjustable='box' is important here
plt.show()
Related
I've been trying to plot a graph of Epoch vs Accuracy and val_accuracy from a train log I have generated. Whenever I try to plot it, the y-axis starts from 0.93 rather than it being in 0, 0.1 ,0.2... intervals. I'm new at using matplotlib or any plot function.
Here's the code for it:
import pandas as pd
import matplotlib.pyplot as plt
acc = pd.read_csv("train_log", sep = ',')
acc.plot("epoch", ["accuracy","val_accuracy"])
plt.savefig('acc' , dpi = 300)
I'm open to suggestion in complete different ways to do this.
Picture of plot :
[1]: https://i.stack.imgur.com/lgg0W.png
This has already been discussed here. There are a couple of different ways you can do this (using plt.ylim() or making a new variable like axes and then axes.set_ylim()), but the easiest is to use the set_ylim function as it gives you heaps of other handles to manipulate the plot. You can also handle the x axis values using the set_xlim function.
You can use the set_ylim([ymin, ymax]) as follows:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,5)
y = np.arange(5,10)
axes = plt.gca()
axes.plot(x,y)
axes.set_ylim([0,10])
You can use the plt.ylim() like this:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,5)
y = np.arange(5,10)
plt.plot(x,y)
plt.ylim([0,10])
This will produce the same plot.
You need to set the lower/bottom limit using ylim().
For details please refer:
https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.ylim.html
I frequently find myself working in log units for my plots, for example taking np.log10(x) of data before binning it or creating contour plots. The problem is, when I then want to make the plots presentable, the axes are in ugly log units, and the tick marks are evenly spaced.
If I let matplotlib do all the conversions, i.e. by setting ax.set_xaxis('log') then I get very nice looking axes, however I can't do that to my data since it is e.g. already binned in log units. I could manually change the tick labels, but that wouldn't make the tick spacing logarithmic. I suppose I could also go and manually specify the position of every minor tick such it had log spacing, but is that the only way to achieve this? That is a bit tedious so it would be nice if there is a better way.
For concreteness, here is a plot:
I want to have the tick labels as 10^x and 10^y (so '1' is '10', 2 is '100' etc.), and I want the minor ticks to be drawn as ax.set_xaxis('log') would draw them.
Edit: For further concreteness, suppose the plot is generated from an image, like this:
import matplotlib.pyplot as plt
import scipy.misc
img = scipy.misc.face()
x_range = [-5,3] # log10 units
y_range = [-55, -45] # log10 units
p = plt.imshow(img,extent=x_range+y_range)
plt.show()
and all we want to do is change the axes appearance as I have described.
Edit 2: Ok, ImportanceOfBeingErnest's answer is very clever but it is a bit more specific to images than I wanted. I have another example, of binned data this time. Perhaps their technique still works on this, though it is not clear to me if that is the case.
import numpy as np
import pandas as pd
import datashader as ds
from matplotlib import pyplot as plt
import scipy.stats as sps
v1 = sps.lognorm(loc=0, scale=3, s=0.8)
v2 = sps.lognorm(loc=0, scale=1, s=0.8)
x = np.log10(v1.rvs(100000))
y = np.log10(v2.rvs(100000))
x_range=[np.min(x),np.max(x)]
y_range=[np.min(y),np.max(y)]
df = pd.DataFrame.from_dict({"x": x, "y": y})
#------ Aggregate the data ------
cvs = ds.Canvas(plot_width=30, plot_height=30, x_range=x_range, y_range=y_range)
agg = cvs.points(df, 'x', 'y')
# Create contour plot
fig = plt.figure()
ax = fig.add_subplot(111)
ax.contourf(agg, extent=x_range+y_range)
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.show()
The general answer to this question is probably given in this post:
Can I mimic a log scale of an axis in matplotlib without transforming the associated data?
However here an easy option might be to scale the content of the axes and then set the axes to a log scale.
A. image
You may plot your image on a logarithmic scale but make all pixels the same size in log units. Unfortunately imshow does not allow for such kind of image (any more), but one may use pcolormesh for that purpose.
import numpy as np
import matplotlib.pyplot as plt
import scipy.misc
img = scipy.misc.face()
extx = [-5,3] # log10 units
exty = [-45, -55] # log10 units
x = np.logspace(extx[0],extx[-1],img.shape[1]+1)
y = np.logspace(exty[0],exty[-1],img.shape[0]+1)
X,Y = np.meshgrid(x,y)
c = img.reshape((img.shape[0]*img.shape[1],img.shape[2]))/255.0
m = plt.pcolormesh(X,Y,X[:-1,:-1], color=c, linewidth=0)
m.set_array(None)
plt.gca().set_xscale("log")
plt.gca().set_yscale("log")
plt.show()
B. contour
The same concept can be used for a contour plot.
import numpy as np
from matplotlib import pyplot as plt
x = np.linspace(-1.1,1.9)
y = np.linspace(-1.4,1.55)
X,Y = np.meshgrid(x,y)
agg = np.exp(-(X**2+Y**2)*2)
fig, ax = plt.subplots()
plt.gca().set_xscale("log")
plt.gca().set_yscale("log")
exp = lambda x: 10.**(np.array(x))
cf = ax.contourf(exp(X), exp(Y),agg, extent=exp([x.min(),x.max(),y.min(),y.max()]))
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.show()
How to draw something like this?
There's kind of like a horizontal line until next data point show up, then a vertical line to adjust the location y. The usual plot function in matplotlib just plot a straight line between two data point, which doesn't satisfy what I need.
You may use one of the drawstyles "steps-pre", "steps-mid", "steps-post" to get a a step-like appearance of your curve.
plt.plot(x,y, drawstyle="steps-pre")
Full example:
import matplotlib.pyplot as plt
import numpy as np; np.random.seed()
x = np.arange(12)
y = np.random.rand(12)
styles = ["default","steps-pre","steps-mid", "steps-post"]
fig, axes = plt.subplots(nrows=len(styles), figsize=(4,7))
for ax, style in zip(axes, styles):
ax.plot(x,y, drawstyle=style)
ax.set_title("drawstyle={}".format(style))
fig.tight_layout()
plt.show()
Just as #cricket_007 said in the comments -- make each y value repeat at the next x value. Below a way how to achieve this with numpy.
EDIT:
Thanks to the comment by #ImportanceOfBeingErnest I replaced the original code that extended the data with a much simpler solution.
from matplotlib import pyplot as plt
import numpy as np
#producing some sample data
x = np.linspace(0,1,20)
y = np.random.rand(x.shape[0])
#extending data to repeat each y value at the next x value
##x1 = np.zeros(2*x.shape[0]-1)
##x1[::2] = x
##x1[1::2] = x[1:]
x1 = np.repeat(x,2)[1:]
##y1 = np.zeros(2*y.shape[0]-1)
##y1[::2] = y
##y1[1::2] = y[:-1]
y1 = np.repeat(y,2)[:-1]
plt.plot(x1, y1)
plt.show()
The result looks like this:
I am trying to simply fill the area under the curve of a plot in Python using MatPlotLib.
Here is my SSCCE:
import json
import pprint
import numpy as np
import matplotlib.pyplot as plt
y = [0,0,0,0,0,0,0,0,0,0,0,863,969,978,957,764,767,1009,1895,980,791]
x = np.arange(len(y))
fig2, ax2 = plt.subplots()
ax2.fill(x, y)
plt.savefig('picForWeb.png')
plt.show()
The attached picture shows the output produced.
Does anyone know why Python is not filling the entire area in between the x-axis and the curve?
I've done Google and StackOverflow searches, but could not find a similar example. Intuitively it seems that it should fill the entire area under the curve.
I usually use the fill_between function for these kinds of plots. Try something like this instead:
import numpy as np
import matplotlib.pyplot as plt
y = [0,0,0,0,0,0,0,0,0,0,0,863,969,978,957,764,767,1009,1895,980,791]
x = np.arange(len(y))
fig, (ax1) = plt.subplots(1,1);
ax1.fill_between(x, 0, y)
plt.show()
See more examples here.
If you want to use this on a pd.DataFrame use this:
df.abs().interpolate().plot.area(grid=1, linewidth=0.5)
interpolate() is optional.
plt.fill assumes that you have a closed shape to fill - interestingly if you add a final 0 to your data you get a much more sensible looking plot.
import numpy as np
import matplotlib.pyplot as plt
y = [0,0,0,0,0,0,0,0,0,0,0,863,969,978,957,764,767,1009,1895,980,791,0]
x = np.arange(len(y))
fig2, ax2 = plt.subplots()
ax2.fill(x, y)
plt.savefig('picForWeb.png')
plt.show()
Results in:
Hope this helps to explain your odd plot.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
Setting the aspect ratio works for 2d plots:
ax = plt.axes()
ax.plot([0,1],[0,10])
ax.set_aspect('equal','box')
But does not for 3d:
ax = plt.axes(projection='3d')
ax.plot([0,1],[0,1],[0,10])
ax.set_aspect('equal','box')
Is there a different syntax for the 3d case, or it's not implemented?
As of matplotlib 3.3.0, Axes3D.set_box_aspect seems to be the recommended approach.
import numpy as np
import matplotlib.pyplot as plt
xs, ys, zs = ...
ax = plt.axes(projection='3d')
ax.set_box_aspect((np.ptp(xs), np.ptp(ys), np.ptp(zs))) # aspect ratio is 1:1:1 in data space
ax.plot(xs, ys, zs)
I didn't try all of these answers, but this kludge did it for me:
def axisEqual3D(ax):
extents = np.array([getattr(ax, 'get_{}lim'.format(dim))() for dim in 'xyz'])
sz = extents[:,1] - extents[:,0]
centers = np.mean(extents, axis=1)
maxsize = max(abs(sz))
r = maxsize/2
for ctr, dim in zip(centers, 'xyz'):
getattr(ax, 'set_{}lim'.format(dim))(ctr - r, ctr + r)
Looks like this feature has since been added so thought I'd add an answer for people who come by this thread in the future like I did:
fig = plt.figure(figsize=plt.figaspect(0.5)*1.5) #Adjusts the aspect ratio and enlarges the figure (text does not enlarge)
ax = fig.add_subplot(projection='3d')
figaspect(0.5) makes the figure twice as wide as it is tall. Then the *1.5 increases the size of the figure. The labels etc won't increase so this is a way to make the graph look less cluttered by the labels.
I think setting the correct "box aspect" is a good solution:
ax.set_box_aspect(aspect = (1,1,1))
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_box_aspect(aspect = (1,1,1))
ax.plot(dataX,dataY,dataZ)
https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.html?highlight=3d%20set_box_aspect#mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect
If you know the bounds, eg. +-3 centered around (0,0,0), you can add invisible points like this:
import numpy as np
import pylab as pl
from mpl_toolkits.mplot3d import Axes3D
fig = pl.figure()
ax = fig.add_subplot(projection='3d')
ax.set_aspect('equal')
MAX = 3
for direction in (-1, 1):
for point in np.diag(direction * MAX * np.array([1,1,1])):
ax.plot([point[0]], [point[1]], [point[2]], 'w')
If you know the bounds you can also set the aspect ratio this way:
ax.auto_scale_xyz([minbound, maxbound], [minbound, maxbound], [minbound, maxbound])
Another helpful (hopefully) solution when, for example, it is necessary to update an already existing figure:
world_limits = ax.get_w_lims()
ax.set_box_aspect((world_limits[1]-world_limits[0],world_limits[3]-world_limits[2],world_limits[5]-world_limits[4]))
get_w_lims()
set_box_aspect()
My understanding is basically that this isn't implemented yet (see this bug in GitHub). I'm also hoping that it is implemented soon. See This link for a possible solution (I haven't tested it myself).
A follow-up to Matt Panzer's answer. (This was originally a comment on said answer.)
limits = np.array([getattr(ax, f'get_{axis}lim')() for axis in 'xyz'])
ax.set_box_aspect(np.ptp(limits, axis=1))
Now that this pull request has been merged, when the next release of Matplotlib drops, you should be able to just use ax.set_aspect('equal'). I will try to remember and update this answer when that happens.
Update: Matplotlib 3.6 has been released; ax.set_aspect('equal') will now work as expected.
As of matplotlib 3.6.0, this feature has been added with the shortcut
ax.set_aspect('equal'). Other options are 'equalxy', 'equalxz', and 'equalyz', to set only two directions to equal aspect ratios. This changes the data limits, example below.
In the upcoming 3.7.0, you will be able to change the plot box aspect ratios rather than the data limits via the command ax.set_aspect('equal', adjustable='box'). (Thanks to #tfpf on another answer here for implementing that!) To get the original behavior, use adjustable='datalim'.
Matt Panzer's answer worked for me, but it took me a while to figure out an issue I had.
If you're plotting multiple datasets into the same graph, you have to calculate the peak-to-peak values for the entire range of datapoints.
I used the following code to solve it for my case:
x1, y1, z1 = ..., ..., ...
x2, y2, z2 = ..., ..., ...
ax.set_box_aspect((
max(np.ptp(x1), np.ptp(x2)),
max(np.ptp(y1), np.ptp(y2)),
max(np.ptp(z1), np.ptp(y2))
))
ax.plot(x1, y1, z1)
ax.scatter(x2, y2, z2)
Note that this solution is not perfect. It will not work if x1 contains the most negative number and x2 contains the most positive one. Only if either x1 or x2 contains the greatest peak-to-peak range.
If you know numpy better than I do, feel free to edit this answer so it works in a more general case.
I tried several methods, such as ax.set_box_aspect(aspect = (1,1,1)) and it does not work. I want a sphere to show up as a sphere -- not ellipsoid. I wrote this function and tried it on a variety of data. It is a hack and it is not perfect, but pretty close.
def set_aspect_equal(ax):
"""
Fix the 3D graph to have similar scale on all the axes.
Call this after you do all the plot3D, but before show
"""
X = ax.get_xlim3d()
Y = ax.get_ylim3d()
Z = ax.get_zlim3d()
a = [X[1]-X[0],Y[1]-Y[0],Z[1]-Z[0]]
b = np.amax(a)
ax.set_xlim3d(X[0]-(b-a[0])/2,X[1]+(b-a[0])/2)
ax.set_ylim3d(Y[0]-(b-a[1])/2,Y[1]+(b-a[1])/2)
ax.set_zlim3d(Z[0]-(b-a[2])/2,Z[1]+(b-a[2])/2)
ax.set_box_aspect(aspect = (1,1,1))