I'm plotting data as a bar plot in matplotlib and am trying to only show the outline of the bars, so that it appears as a 'stepped graph' of the data.
I've added my code below along with an image of the desired output.
plt.bar(x, y, align='center', width=0.1, edgecolor='black', color='none')
The plot I have:
The plot I would like:
Are there any other libraries that may be able to produce this? The bar keyword arguments don't seem to have anything that can.
Your image looks like a function that is horizontal around each x,y value. The following code simulates this:
for every x,y: create two new points one at x-0.5 and one at x+0.5, both with the same y
to close the shape at the ends, add (x[0]-0.5, 0) at the start and (x[-1]+0.5, 0) at the end.
import numpy as np
from matplotlib import pyplot as plt
x = np.arange(0, 30, 1)
y = np.random.uniform(2, 10, 30)
xs = [x[0] - 0.5]
ys = [0]
for i in range(len(x)):
xs.append(x[i] - 0.5)
xs.append(x[i] + 0.5)
ys.append(y[i])
ys.append(y[i])
xs.append(x[-1] + 0.5)
ys.append(0)
plt.plot(xs, ys, color='dodgerblue')
# optionally color the area below the curve
plt.fill_between(xs, 0, ys, color='gold')
PS: #AsishM. mentioned in the comments that matplotlib also has its own step function. If that function fulfils, please use that one. If you need some extra control or variation, this answer could give a start, such as coloring the area below the curve or handling the shape at the ends.
Related
I have two sets of points with values (x, y). One is enormous (300k) and one is small (2k). I want to show a scatter plot of the latter over a 2D-histogram of the former in log-log scale. plt.xscale('log')-like commands keep messing up the histogram and when I just take logs of x's and y's and then do all the plotting, my ticks are say -3 not 10^-3 and the pretty logarithmic minor ticks are missing altogether. What's the most elegant solution in matplotlib? Do I have to dig into the artist layer?
If you forgive a bit of self-advertisement, you may use my library physt (see https://github.com/janpipek/physt). Then, you can write code like this:
import numpy as np
import matplotlib.pyplot as plt
from physt import h2
# Data
r1 = np.random.normal(0, 1, 20000)
r2 = np.random.normal(0, .3, 20000) + r1
x = np.exp(r1)
y = np.exp(r2)
# Plot scatter
fig, ax = plt.subplots()
ax.scatter(x[:1000], y[:1000], s=2)
H = h2(x, y, "exponential")
H.plot(ax=ax, zorder=-1) # Necessary to put behind
Which, I hope is the solution to your problem:
I would like to draw a graph that looks like:
The data is given in a .csv file, which I already imported to data and used as x in the graph.
Y is calculated as following:
y = np.arange(1, len(data)+1)/len(data)
And then plotted using:
plt.step((data), y)
plt.show(block="false")
My problem is now, that the graph looks like a normal step graph like this one (not my actual data).
How do I format this to look like the one mentioned, i.e. coming from the left on the y = 0 line and extending on the right on the y = 1 line, dotted vs. solid lines and points on the graph?
I have googled around and found many solutions for the graph that I already have, but I would like to format it as mentioned.
I'm new to the general subject, so if the setup is wrong, any help there is appreciated as well!
Thanks!
I'm not sure if step() can do this; it's really just a few lines of code wrapped around plt.plot().
Alternately, you could use vlines() and hlines(). The logic of slicing varies based on how you want the steps "configured," (as in how you would specify the where argument to step(), but here is a close reproduction of the example from your question:
import numpy as np
import matplotlib.pyplot as plt
data = np.arange(0, 7)
y = np.array([.07, .21, .42, .68, 1.])
yn = np.insert(y, 0, 0)
fig, ax = plt.subplots()
ax.set_facecolor('white')
# https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.hlines.html
ax.hlines(y=yn, xmin=data[:-1], xmax=data[1:],
color='red', zorder=1)
# https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.vlines.html
ax.vlines(x=data[1:-1], ymin=yn[:-1], ymax=yn[1:], color='red',
linestyle='dashed', zorder=1)
ax.scatter(data[1:-1], y, color='red', s=18, zorder=2)
ax.scatter(data[1:-1], yn[:-1], color='white', s=18, zorder=2,
edgecolor='red')
ax.grid(False)
ax.set_xlim(data[0], data[-1])
ax.set_ylim([-0.01, 1.01])
zorder makes sure the scatter points are overlaid on top of lines.
As it stands, your creation of y doesn't really follow suite with what the image you showed looks like, but this example tries to mimic the image itself.
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()
I'm trying to make a simple 2d plot from a 3 column data sets e.g. y=f(x) and z=f(x). I want to plot xy and would like to display z using color. For example, the rectangular regions between [x1,x2, min(y), max(y)] ... will be filled by a background color depending on the value of z. I tried to use fill_between but could not associate a colormap with it. I'm new to matplotlib and python. I would very much appreciate your comments/suggestions.
Edit: I don't have an accurate plot but I'll try to explain my query with the help of following figure sample plot
say between x=0.5 to x=1, z=1
x=1.0, to x=1.5, z=2 ....
so I would like to cover x=0.5 to x=1 (min(y) to max(y)] with some color that corresponds to z=1, and between x=1, x=1.5, z=2 and so on.. I want to show this variation using a colormap and to display this colorbar at the right side.
Here's the solution those who want cannot use contourf or need fill_between for some other reason (as in this case with irregular grid data).
import numpy as np
import matplotlib.pyplot as plt
from random import randint, sample
import matplotlib.colorbar as cbar
# from Numeric import asarray
%matplotlib inline
# The edges of 2d grid
# Some x column has varying rows of y (but always the same number of rows)
# z array that corresponds a value in each xy cell
xedges = np.sort(sample(range(1, 9), 6))
yedges = np.array([np.sort(sample(range(1, 9), 6)) for i in range(5)])
z = np.random.random((5,5))
f, ax = plt.subplots(1, sharex=True, figsize=(8,8))
f.subplots_adjust(hspace=0)
ax.set_ylabel(r'y')
ax.set_xlabel(r'x')
ax.set_ylim(0,10)
ax.set_xlim(0,10)
c = ['r','g','b','y','m']
normal = plt.Normalize(z.min(), z.max())
cmap = plt.cm.jet(normal(z))
# plot showing bins, coloured arbitrarily.
# I want each cell coloured according to z.
for i in range(len(xedges)-1):
for j in range(len(yedges)):
ax.vlines(xedges[i],yedges[i][j],yedges[i][j+1],linestyle='-')
ax.hlines(yedges[i][j],xedges[i],xedges[i+1],linestyle='-')
ax.vlines(xedges[i+1],yedges[i][j],yedges[i][j+1],linestyle='-')
ax.hlines(yedges[i][j+1],xedges[i],xedges[i+1],linestyle='-')
ax.fill_between([xedges[i],xedges[i+1]],yedges[i][j],yedges[i][j+1],facecolor=cmap[i][j][:])
cax, _ = cbar.make_axes(ax)
cb2 = cbar.ColorbarBase(cax, cmap=plt.cm.jet,norm=normal)
This gives
It sound to me like you should use contourf
http://matplotlib.org/examples/pylab_examples/contourf_demo.html
This would take x as some dependant variable, produce y = y(x) and z = z(x). It seems that your z is not dependant on y but contourf can still handle this.
As a simple example:
import pylab as plt
x = plt.linspace(0,2,100)
y = plt.linspace(0,10,100)
z = [[plt.sinc(i) for i in x] for j in y]
CS = plt.contourf(x, y, z, 20, # \[-1, -0.1, 0, 0.1\],
cmap=plt.cm.rainbow)
plt.colorbar(CS)
plt.plot(x,2+plt.sin(y), "--k")
The are many variations but hopefully this captures the elements you are looking for
I am not able to draw a simple, vertical arrow in the following log-log plot:
#!/usr/bin/python2
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.yscale('log')
plt.xscale('log')
plt.ylim((1e-20,1e-10))
plt.xlim((1e-12,1))
plt.arrow(0.00006666, 1e-20, 0, 1e-8 - 1e-20, length_includes_head=True)
plt.savefig('test.pdf')
It just doesn't show. From the documentation it appears as if all the arguments, like width, height and so on relate to the scale of the axis. This is very counter-intuitive. I tried using twin() of the axisartist package to define an axis on top of mine with limits (0,1), (0,1) to have more control over the arrow's parameters, but I couldn't figure out how to have a completely independent axis on top of the primary one.
Any ideas?
I was looking for an answer to this question, and found a useful answer! You can specify any "mathtext" character (matplotlib's version of LaTeX) as a marker. Try:
plt.plot(x,y, 'ko', marker=r'$\downarrow$', markersize=20)
This will plot a downward pointing, black arrow at position (x,y) that looks good on any plot (even log-log).
See: matplotlib.org/users/mathtext.html#mathtext-tutorial for more symbols you can use.
Subplots approach
After creating the subplots do the following
Align the positions
Use set_axis_off() to turn the axis off (ticks, labels, etc)
Draw the arrow!
So a few lines gets whats you want!
E.g.
#!/usr/bin/python2
import matplotlib.pyplot as plt
hax = plt.subplot(1,2,1)
plt.yscale('log')
plt.xscale('log')
plt.ylim((1e-20,1e-10))
plt.xlim((1e-12,1))
hax2 = plt.subplot(1,2,2)
plt.arrow(0.1, 1, 0, 1, length_includes_head=True)
hax.set_position([0.1, 0.1, 0.8, 0.8])
hax2.set_position([0.1, 0.1, 0.8, 0.8])
hax2.set_axis_off()
plt.savefig('test.pdf')
Rescale data
Alternatively a possibly easier approach, though the axis labels may be tricky, is to rescale the data.
i.e.
import numpy
# Other import commands and data input
plt.plot(numpy.log10(x), numpy.log10(y)))
Not a great solution, but a decent result if you can handle the tick labels!
I know this thread has been dead for a long time now, but I figure posting my solution might be helpful for anyone else trying to figure out how to draw arrows on log-scale plots efficiently.
As an alternative to what others have already posted, you could use a transformation object to input the arrow coordinates not in the scale of the original axes but in the (linear) scale of the "axes coordinates". What I mean by axes coordinates are those that are normalized to [0,1] (horizontal range) by [0,1] (vertical range), where the point (0,0) would be the bottom-left corner and the point (1,1) would be the top-right, and so on. Then you could simply include an arrow by:
plt.arrow(0.1, 0.1, 0.9, 0.9, transform=plot1.transAxes, length_includes_head=True)
This gives an arrow that spans diagonally over 4/5 of the plot's horizontal and vertical range, from the bottom-left to the top-right (where plot1 is the subplot name).
If you want to do this in general, where exact coordinates (x0,y0) and (x1,y1) in the log-space can be specified for the arrow, this is not too difficult if you write two functions fx(x) and fy(y) that transform from the original coordinates to these "axes" coordinates. I've given an example of how the original code posted by the OP could be modified to implement this below (apologies for not including the images the code produces, I don't have the required reputation yet).
#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
# functions fx and fy take log-scale coordinates to 'axes' coordinates
ax = 1E-12 # [ax,bx] is range of horizontal axis
bx = 1E0
def fx(x):
return (np.log(x) - np.log(ax))/(np.log(bx) - np.log(ax))
ay = 1E-20 # [ay,by] is range of vertical axis
by = 1E-10
def fy(y):
return (np.log(y) - np.log(ay))/(np.log(by) - np.log(ay))
plot1 = plt.subplot(111)
plt.xscale('log')
plt.yscale('log')
plt.xlim(ax, bx)
plt.ylim(ay, by)
# transformed coordinates for arrow from (1E-10,1E-18) to (1E-4,1E-16)
x0 = fx(1E-10)
y0 = fy(1E-18)
x1 = fx(1E-4) - fx(1E-10)
y1 = fy(1E-16) - fy(1E-18)
plt.arrow(
x0, y0, x1, y1, # input transformed arrow coordinates
transform = plot1.transAxes, # tell matplotlib to use axes coordinates
facecolor = 'black',
length_includes_head=True
)
plt.grid(True)
plt.savefig('test.pdf')