Related
Alright, so I was working on a simple program to just pull coordinates out of a text pad and then graph what was in the text pad on a graph. I thought it would be pretty simple, but I am VERY new to matplotlib, so I still don't fully understand. I got most of the code done correctly, but the only thing that is not working is that when I put the values in the graph, they come all out of order. I want to order the xticks and yticks so that it actually looks like a real line graph you'd see in math, so you can see how the lower coordinates lower than the higher coordinates, and vice versa. Here is my code:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
def split(word):
return list(word)
fileIWant = open('C:/Users/JustA/Desktop/Python Shenanigans/Converting Coordinates in a .txt to a Graph/Coordinates.txt', 'r');
stopwords = ['\n']
array = fileIWant.readlines()
array = [array.replace('\n', '') for array in array if array not in stopwords]
fileIWant.close()
editFile = open('C:/Users/JustA/Desktop/Python Shenanigans/Converting Coordinates in a .txt to a Graph/Coordinates.txt', 'w')
array_length = len(array)
x = []
y = []
for i in range(array_length):
dataSplit = array[i].split()
getCoordinateX = dataSplit[1]
getCoordinateY = dataSplit[3]
x.append(getCoordinateX)
y.append(getCoordinateY)
plt.scatter(x, y)
plt.plot(x, y) #Add this line in if you want to show lines.
plt.title('Your Coordinate Graph')
plt.xlabel('X Coordinates')
plt.ylabel('Y Coordinates')
#plt.xticks([-100,-80,-60,-40,-20,0,20,40,60,80,100])
#plt.yticks([-100,-80,-60,-40,-20,0,20,40,60,80,100])
plt.show()
editFile.close()
I commented out what I put for the ticks, because it was not working at all. With those commented out, it looks okay, but it is very confusing. I think it just puts them in the order they are at in the .txt, when I want them to order themselves in the code. Here is what it is outputting right now:
Sorry if this is so simple that it has never been asked before, like I said, very new to matplotlib, and numpy if I have to use that at all. I imported it because I thought I may have to, but I don't think I really used it as of yet. Also, I am going to rewrite the coordinates into the graph in order, but I think I can do that myself later.
The problem is that your coordinates are strings, which means matplotlib is just plotting strings against strings ("categorical" axis labels). To fix, you simply have to convert your strings to numbers, e.g. x.append(int(getCoordinateX)).
Note that you also don't have to put plt.scatter/plt.plot in the loop - you only have to call one of those once on the full array. That'll probably make things a little faster too.
Maybe this will be duplicate question but I couldn't find any solution for this.
Normally what I coded should show me a curved line in python. But with this code I cant see it. Is there a problem with my code or pycharm ? This code only shows me an empty graphic with the correct axes.
And I did adding "ro" in plt.plot(at[i], st, "ro"). This showed me the spots on the graph but what I want to see the complete line.
at = [0,1,2,3,4,5,6]
for i in range(len(at)):
st = at[i]**2
plt.plot(at[i], st)
plt.show()
This is how you would normally do this:
import numpy as np
import matplotlib.pyplot as plt
at = np.array([0,1,2,3,4,5,6])
at2 = at ** 2
plt.plot(at,at2)
plt.show()
you can use something like plt.plot(at,at2, c='red', marker='o') to see the spots.
for detailed explanation please read the documentation.
Maybe rather calculate the to be plotted values entirely before plotting.
at = [0,1,2,3,4,5,6]
y = [xi**2 for xi in at]
plt.plot(at, y)
Or do it alternatively with a function
from math import pow
at = [0,1,2,3,4,5,6]
def parabolic(x):
return [pow(xi,2) for xi in x]
plt.plot(at, parabolic(at))
both return the following plot:
the other answers give fixes for your question, but don't tell you why your code is not working.
the reason for not "seeing anything" is that plt.plot(at[i], st) was trying to draw lines between the points you give it. but because you were only ever giving it single values it didn't have anything to draw lines between. as a result, nothing appeared on the plot
when you changed to call plt.plot(at[i], st, 'ro') you're telling it to draw single circles at points and these don't go between points so would appear
the other answers showed you how to pass multiple values to plot and hence matplotlib could draw lines between these values.
one of your comments says "its not parabolic still" and this is because matplotlib isn't a symbolic plotting library. you just give it numeric values and it draws these onto the output device. sympy is a library for doing symbolic computation and supports plotting, e.g:
from sympy import symbols, plot
x = symbols('x')
plot(x**2, (x, 0, 6))
does the right thing for me. the current release (1.4) doesn't handle discontinuities, but this will be fixed in the next release
I am trying to plot contour lines of pressure level. I am using a netCDF file which contain the higher resolution data (ranges from 3 km to 27 km). Due to higher resolution data set, I get lot of pressure values which are not required to be plotted (rather I don't mind omitting certain contour line of insignificant values). I have written some plotting script based on the examples given in this link http://matplotlib.org/basemap/users/examples.html.
After plotting the image looks like this
From the image I have encircled the contours which are small and not required to be plotted. Also, I would like to plot all the contour lines smoother as mentioned in the above image. Overall I would like to get the contour image like this:-
Possible solution I think of are
Find out the number of points required for plotting contour and mask/omit those lines if they are small in number.
or
Find the area of the contour (as I want to omit only circled contour) and omit/mask those are smaller.
or
Reduce the resolution (only contour) by increasing the distance to 50 km - 100 km.
I am able to successfully get the points using SO thread Python: find contour lines from matplotlib.pyplot.contour()
But I am not able to implement any of the suggested solution above using those points.
Any solution to implement the above suggested solution is really appreciated.
Edit:-
# Andras Deak
I used print 'diameter is ', diameter line just above del(level.get_paths()[kp]) line to check if the code filters out the required diameter. Here is the filterd messages when I set if diameter < 15000::
diameter is 9099.66295612
diameter is 13264.7838257
diameter is 445.574234531
diameter is 1618.74618114
diameter is 1512.58974168
However the resulting image does not have any effect. All look same as posed image above. I am pretty sure that I have saved the figure (after plotting the wind barbs).
Regarding the solution for reducing the resolution, plt.contour(x[::2,::2],y[::2,::2],mslp[::2,::2]) it works. I have to apply some filter to make the curve smooth.
Full working example code for removing lines:-
Here is the example code for your review
#!/usr/bin/env python
from netCDF4 import Dataset
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import numpy as np
import scipy.ndimage
from mpl_toolkits.basemap import interp
from mpl_toolkits.basemap import Basemap
# Set default map
west_lon = 68
east_lon = 93
south_lat = 7
north_lat = 23
nc = Dataset('ncfile.nc')
# Get this variable for later calucation
temps = nc.variables['T2']
time = 0 # We will take only first interval for this example
# Draw basemap
m = Basemap(projection='merc', llcrnrlat=south_lat, urcrnrlat=north_lat,
llcrnrlon=west_lon, urcrnrlon=east_lon, resolution='l')
m.drawcoastlines()
m.drawcountries(linewidth=1.0)
# This sets the standard grid point structure at full resolution
x, y = m(nc.variables['XLONG'][0], nc.variables['XLAT'][0])
# Set figure margins
width = 10
height = 8
plt.figure(figsize=(width, height))
plt.rc("figure.subplot", left=.001)
plt.rc("figure.subplot", right=.999)
plt.rc("figure.subplot", bottom=.001)
plt.rc("figure.subplot", top=.999)
plt.figure(figsize=(width, height), frameon=False)
# Convert Surface Pressure to Mean Sea Level Pressure
stemps = temps[time] + 6.5 * nc.variables['HGT'][time] / 1000.
mslp = nc.variables['PSFC'][time] * np.exp(9.81 / (287.0 * stemps) * nc.variables['HGT'][time]) * 0.01 + (
6.7 * nc.variables['HGT'][time] / 1000)
# Contour only at 2 hpa interval
level = []
for i in range(mslp.min(), mslp.max(), 1):
if i % 2 == 0:
if i >= 1006 and i <= 1018:
level.append(i)
# Save mslp values to upload to SO thread
# np.savetxt('mslp.txt', mslp, fmt='%.14f', delimiter=',')
P = plt.contour(x, y, mslp, V=2, colors='b', linewidths=2, levels=level)
# Solution suggested by Andras Deak
for level in P.collections:
for kp,path in enumerate(level.get_paths()):
# include test for "smallness" of your choice here:
# I'm using a simple estimation for the diameter based on the
# x and y diameter...
verts = path.vertices # (N,2)-shape array of contour line coordinates
diameter = np.max(verts.max(axis=0) - verts.min(axis=0))
if diameter < 15000: # threshold to be refined for your actual dimensions!
#print 'diameter is ', diameter
del(level.get_paths()[kp]) # no remove() for Path objects:(
#level.remove() # This does not work. produces ValueError: list.remove(x): x not in list
plt.gcf().canvas.draw()
plt.savefig('dummy', bbox_inches='tight')
plt.close()
After the plot is saved I get the same image
You can see that the lines are not removed yet. Here is the link to mslp array which we are trying to play with http://www.mediafire.com/download/7vi0mxqoe0y6pm9/mslp.txt
If you want x and y data which are being used in the above code, I can upload for your review.
Smooth line
You code to remove the smaller circles working perfectly. However the other question I have asked in the original post (smooth line) does not seems to work. I have used your code to slice the array to get minimal values and contoured it. I have used the following code to reduce the array size:-
slice = 15
CS = plt.contour(x[::slice,::slice],y[::slice,::slice],mslp[::slice,::slice], colors='b', linewidths=1, levels=levels)
The result is below.
After searching for few hours I found this SO thread having simmilar issue:-
Regridding regular netcdf data
But none of the solution provided over there works.The questions similar to mine above does not have proper solutions. If this issue is solved then the code is perfect and complete.
General idea
Your question seems to have 2 very different halves: one about omitting small contours, and another one about smoothing the contour lines. The latter is simpler, since I can't really think of anything else other than decreasing the resolution of your contour() call, just like you said.
As for removing a few contour lines, here's a solution which is based on directly removing contour lines individually. You have to loop over the collections of the object returned by contour(), and for each element check each Path, and delete the ones you don't need. Redrawing the figure's canvas will get rid of the unnecessary lines:
# dummy example based on matplotlib.pyplot.clabel example:
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
plt.figure()
CS = plt.contour(X, Y, Z)
for level in CS.collections:
for kp,path in reversed(list(enumerate(level.get_paths()))):
# go in reversed order due to deletions!
# include test for "smallness" of your choice here:
# I'm using a simple estimation for the diameter based on the
# x and y diameter...
verts = path.vertices # (N,2)-shape array of contour line coordinates
diameter = np.max(verts.max(axis=0) - verts.min(axis=0))
if diameter<1: # threshold to be refined for your actual dimensions!
del(level.get_paths()[kp]) # no remove() for Path objects:(
# this might be necessary on interactive sessions: redraw figure
plt.gcf().canvas.draw()
Here's the original(left) and the removed version(right) for a diameter threshold of 1 (note the little piece of the 0 level at the top):
Note that the top little line is removed while the huge cyan one in the middle doesn't, even though both correspond to the same collections element i.e. the same contour level. If we didn't want to allow this, we could've called CS.collections[k].remove(), which would probably be a much safer way of doing the same thing (but it wouldn't allow us to differentiate between multiple lines corresponding to the same contour level).
To show that fiddling around with the cut-off diameter works as expected, here's the result for a threshold of 2:
All in all it seems quite reasonable.
Your actual case
Since you've added your actual data, here's the application to your case. Note that you can directly generate the levels in a single line using np, which will almost give you the same result. The exact same can be achieved in 2 lines (generating an arange, then selecting those that fall between p1 and p2). Also, since you're setting levels in the call to contour, I believe the V=2 part of the function call has no effect.
import numpy as np
import matplotlib.pyplot as plt
# insert actual data here...
Z = np.loadtxt('mslp.txt',delimiter=',')
X,Y = np.meshgrid(np.linspace(0,300000,Z.shape[1]),np.linspace(0,200000,Z.shape[0]))
p1,p2 = 1006,1018
# this is almost the same as the original, although it will produce
# [p1, p1+2, ...] instead of `[Z.min()+n, Z.min()+n+2, ...]`
levels = np.arange(np.maximum(Z.min(),p1),np.minimum(Z.max(),p2),2)
#control
plt.figure()
CS = plt.contour(X, Y, Z, colors='b', linewidths=2, levels=levels)
#modified
plt.figure()
CS = plt.contour(X, Y, Z, colors='b', linewidths=2, levels=levels)
for level in CS.collections:
for kp,path in reversed(list(enumerate(level.get_paths()))):
# go in reversed order due to deletions!
# include test for "smallness" of your choice here:
# I'm using a simple estimation for the diameter based on the
# x and y diameter...
verts = path.vertices # (N,2)-shape array of contour line coordinates
diameter = np.max(verts.max(axis=0) - verts.min(axis=0))
if diameter<15000: # threshold to be refined for your actual dimensions!
del(level.get_paths()[kp]) # no remove() for Path objects:(
# this might be necessary on interactive sessions: redraw figure
plt.gcf().canvas.draw()
plt.show()
Results, original(left) vs new(right):
Smoothing by resampling
I've decided to tackle the smoothing problem as well. All I could come up with is downsampling your original data, then upsampling again using griddata (interpolation). The downsampling part could also be done with interpolation, although the small-scale variation in your input data might make this problem ill-posed. So here's the crude version:
import scipy.interpolate as interp #the new one
# assume you have X,Y,Z,levels defined as before
# start resampling stuff
dN = 10 # use every dN'th element of the gridded input data
my_slice = [slice(None,None,dN),slice(None,None,dN)]
# downsampled data
X2,Y2,Z2 = X[my_slice],Y[my_slice],Z[my_slice]
# same as X2 = X[::dN,::dN] etc.
# upsampling with griddata over original mesh
Zsmooth = interp.griddata(np.array([X2.ravel(),Y2.ravel()]).T,Z2.ravel(),(X,Y),method='cubic')
# plot
plt.figure()
CS = plt.contour(X, Y, Zsmooth, colors='b', linewidths=2, levels=levels)
You can freely play around with the grids used for interpolation, in this case I just used the original mesh, as it was at hand. You can also play around with different kinds of interpolation: the default 'linear' one will be faster, but less smooth.
Result after downsampling(left) and upsampling(right):
Of course you should still apply the small-line-removal algorithm after this resampling business, and keep in mind that this heavily distorts your input data (since if it wasn't distorted, then it wouldn't be smooth). Also, note that due to the crude method used in the downsampling step, we introduce some missing values near the top/right edges of the region under consideraton. If this is a problem, you should consider doing the downsampling based on griddata as I've noted earlier.
This is a pretty bad solution, but it's the only one that I've come up with. Use the get_contour_verts function in this solution you linked to, possibly with the matplotlib._cntr module so that nothing gets plotted initially. That gives you a list of contour lines, sections, vertices, etc. Then you have to go through that list and pop the contours you don't want. You could do this by calculating a minimum diameter, for example; if the max distance between points is less than some cutoff, throw it out.
That leaves you with a list of LineCollection objects. Now if you make a Figure and Axes instance, you can use Axes.add_collection to add all of the LineCollections in the list.
I checked this out really quick, but it seemed to work. I'll come back with a minimum working example if I get a chance. Hope it helps!
Edit: Here's an MWE of the basic idea. I wasn't familiar with plt._cntr.Cntr, so I ended up using plt.contour to get the initial contour object. As a result, you end up making two figures; you just have to close the first one. You can replace checkDiameter with whatever function works. I think you could turn the line segments into a Polygon and calculate areas, but you'd have to figure that out on your own. Let me know if you run into problems with this code, but it at least works for me.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
def checkDiameter(seg, tol=.3):
# Function for screening line segments. NB: Not actually a proper diameter.
diam = (seg[:,0].max() - seg[:,0].min(),
seg[:,1].max() - seg[:,1].min())
return not (diam[0] < tol or diam[1] < tol)
# Create testing data
x = np.linspace(-1,1, 21)
xx, yy = np.meshgrid(x,x)
z = np.exp(-(xx**2 + .5*yy**2))
# Original plot with plt.contour
fig0, ax0 = plt.subplots()
# Make sure this contour object actually has a tiny contour to remove
cntrObj = ax0.contour(xx,yy,z, levels=[.2,.4,.6,.8,.9,.95,.99,.999])
# Primary loop: Copy contours into a new LineCollection
lineNew = list()
for lineOriginal in cntrObj.collections:
# Get properties of the original LineCollection
segments = lineOriginal.get_segments()
propDict = lineOriginal.properties()
propDict = {key: value for (key,value) in propDict.items()
if key in ['linewidth','color','linestyle']} # Whatever parameters you want to carry over
# Filter out the lines with small diameters
segments = [seg for seg in segments if checkDiameter(seg)]
# Create new LineCollection out of the OK segments
if len(segments) > 0:
lineNew.append(mpl.collections.LineCollection(segments, **propDict))
# Make new plot with only these line collections; display results
fig1, ax1 = plt.subplots()
ax1.set_xlim(ax0.get_xlim())
ax1.set_ylim(ax0.get_ylim())
for line in lineNew:
ax1.add_collection(line)
plt.show()
FYI: The bit with propDict is just to automate bringing over some of the line properties from the original plot. You can't use the whole dictionary at once, though. First, it contains the old plot's line segments, but you can just swap those for the new ones. But second, it appears to contain a number of parameters that are in conflict with each other: multiple linewidths, facecolors, etc. The {key for key in propDict if I want key} workaround is my way to bypass that, but I'm sure someone else can do it more cleanly.
import scipy as sp
import scipy.misc
lena = sp.misc.lena()
plt.imshow2(lena)
What I'd like is then to add a bar indicative of distance. ie suppose this was an actual image captured with a camera and I knew that each pixel corresponds to 1cm. I would want to add a bar that is 10 x 100 pixels and add some text that says 1m above the bar. Is there a simple way to do this?
thank you
In the example bellow I made a simple solution of your problem. It should not be too hard to extend this to cover a more general case. Hardest thing to get right here is the pos_tuple.
Since pos_tuple represents the upper left corner of Rectangle you have to subtract the length of the bar itself and then still leave some padding, otherwise it will be plotted at the very edge of the graph and look ugly. So a more general pos_tuple would look something like
pos_tuple = (np.shape(lena)[0]-m2pix(1)-padding_right,
np.shape(lena)[1]-m2pix(0.1)-padding_bottom)
This whole thing could also be adapted into a neat function add_image_scale that would take in your figure and spit out a figure which has the scale "glued" on. m2pix could also be generalized to receive a scale instead of hardcoding it.
import scipy as sp
import scipy.misc
import numpy as np
lena = sp.misc.lena()
def m2pix(pix): #it takes a 100 pix to make a meter
return 100*pix
pos_tuple = (np.shape(lena)[0]-100-12, np.shape(lena)[1]-10-2)
rect = plt.Rectangle( pos_tuple, m2pix(1), m2pix(0.1))
plt.imshow2(lena)
plt.gca().add_patch(rect)
plt.show()
As far as adding text goes, you can use the annotations or text which are both very easy to use.
Suppose I have the 3x3 matrix below:
[apples 19 3.5]
[oranges 07 2.2]
[grapes 23 7.8]
Only in real life the matrix has dozens of rows, not just three.
I want to create an XY plot where the second column is the X coordinate, the third column is the Y coordinate, and the words themselves (i.e., the first column) are the markers (so no dots, lines, or any other symbols).
I also want the font size of each word to be determined by the second column (in the example above, that means making "grapes" have about three times the size of "oranges", for instance).
Finally, I want to color the words on a red-to-blue scale corresponding to the third column, with 0 = darkest red and 10 = darkest blue.
What's the best way to go about it in Python 2.x? I know I can use matplotlib's "annotate" and "text" to do many (if not all) of those things, but somehow that feels like a workaround. Surely there must be a way of declaring the words to be markers (so I don't have to treat them as "annotations")? Perhaps something outside matplotlib? Has anyone out there ever done something similar?
As you did not want to use annotate or text the next best thing is py.scatter which will accept a marker
``'$...$'`` render the string using mathtext.
For example
import pylab as py
data = [["peach", 1.0, 1.0],
["apples", 19, 3.5],
["oranges", 7, 2.2],
["grapes", 23, 7.8]]
for item in data:
py.scatter(item[1], item[2], s=700*item[1],
c=(item[2]/10.0, 0, 1 - item[2]/10.0),
marker=r"$ {} $".format(item[0]), edgecolors='none' )
py.show()
This method has several issues
Using \textrm{} in the math text so that it is not italic appears to break matplotlib
The letters sizes need to be adjusted by hand (hence the factor of 700)
It would probably be better to use a colormap rather than simply defining the RGB color value.
While looking around for a solution to the same problem, I've found one that seems a bit cleaner (or at least more in spirit to what the original question asked), namely to use TextPath:
from matplotlib import pyplot as plt
from matplotlib.text import TextPath
data = [["peach", 1.0, 1.0],
["apples", 19, 3.5],
["oranges", 7, 2.2],
["grapes", 23, 7.8]]
max_d2 = max([d[2] for d in data]) + 1e-3
max_d1 = max([d[1] for d in data]) + 1e-3
cmap = plt.get_cmap('RdBu')
for d in data:
path = TextPath((0,0), d[0])
# These dots are to display the weakness below, remove for the actual question
plt.plot(d[1],d[2],'.',color='k')
plt.plot(d[1],d[2],marker=path,markersize=100, color=cmap(d[2]/max_d2))
plt.xlim([0,max_d1+5])
plt.ylim([0,max_d2+0.5])
This solution has some advantages and disadvantages of its own:
Main disadvantage: as the dots show, I wasn't able to properly center the text as I wanted. Instead, the required value is the bottom left of the picture.
Main advantage: this has no latex issue and uses a "real" marker path, which means that it can easily be used to e.g. mark line plots (not the original question, though)
Code:
import numpy as np
x = np.cumsum(np.random.randn(100,5), axis=0)
plt.figure(figsize=(15,5))
for i in range(5):
label = TextPath((0,0), str(i), linewidth=1)
plt.plot(x[:,i], color='k')
plt.plot(np.arange(0,len(x),5),x[::5,i], color='k', marker=label, markersize=15, linewidth=0)
Doing the above via a naive loop over "text" or "annotate" would be very slow if you had many lines / markers, while this scales better.