I am trying to generate contour plots on a polar plot and did some quick scripting in matlab to get some results. Out of curiosity I also wanted to try out the same thing in python using the matplotlib but somehow I am seeing different sets of contour plots for the same input data. I am trying to figure out whats going on and if there is anything I could tweak in my python code to get similar results in both cases.
A screenshot of the matlab results is here:
In the matlab code I used the scatteredinterpolant function to get the interpolated data, I am assuming the differences are occurring due to the interpolation function used?
The input data is -
Angles = [-180, -90, 0 , 90, 180, -135, -45,45, 135, 180,-90, 0, 90, 180 ]
Radii = [0,0.33,0.33,0.33,0.33,0.5,0.5,0.5,0.5,0.5,0.6,0.6,0.6,0.6]
Values = [30.42,24.75, 32.23, 34.26, 26.31, 20.58, 23.38, 34.15,27.21, 22.609, 16.013, 22.75, 27.062, 18.27]
This was done using python 2.7, on spyder. I have tried both scipy.interpolate.griddata as well as matplotlib.mlab.griddata and the results are similar. I was unable to get the nn method working in mlab.griddata because it kept giving me masked data.
Apologies if I am missing anything relevant - please let me know if anyother info is required I will update my post.
Edit:
The linear scipt griddata image looks like:
And the cubic scipy image looks like
As for the code, here is the code - I pass the interpolation type string into the function where this code is present. So 'linear' and 'cubic' are the 2 inputs.
val = np.array(list(values[i]))
radius = np.array(list(gamma[i]))
ang = [math.radians(np.array(list(theta[i]))[x]) for x in xrange(0,len(theta[i]))]
radiiGrid = np.linspace(min(radius),max(radius),100)
anglesGrid = np.linspace(min(ang),max(ang),100)
radiiGrid, anglesGrid = np.meshgrid(radiiGrid, anglesGrid)
zgrid = griddata((ang,radius),val,(anglesGrid,radiiGrid), method=interpType)
The angle input is what comes out of np.array(list(theta[i]))[x] - this is because the angle information is stored in a list of tuples (this is because I am reading in and sorting data). I took a look at the code to make sure the data is correct and it seems to line up. gamma corresponds to radii and values are the values in the sample data I provided.
Hope this helps!
Polar plots in matplotlib can get tricky. When that happens, a quick solution is to convert radii and angle to x,y, plot in a normal projection. Then make a empty polar axis to superimpose on it:
from scipy.interpolate import griddata
Angles = [-180, -90, 0 , 90, 180, -135,
-45,45, 135, 180,-90, 0, 90, 180 ]
Radii = [0,0.33,0.33,0.33,0.33,0.5,0.5,
0.5,0.5,0.5,0.6,0.6,0.6,0.6]
Angles = np.array(Angles)/180.*np.pi
x = np.array(Radii)*np.sin(Angles)
y = np.array(Radii)*np.cos(Angles)
Values = [30.42,24.75, 32.23, 34.26, 26.31, 20.58,
23.38, 34.15,27.21, 22.609, 16.013, 22.75, 27.062, 18.27]
Xi = np.linspace(-1,1,100)
Yi = np.linspace(-1,1,100)
#make the axes
f = plt.figure()
left, bottom, width, height= [0,0, 1, 0.7]
ax = plt.axes([left, bottom, width, height])
pax = plt.axes([left, bottom, width, height],
projection='polar',
axisbg='none')
cax = plt.axes([0.8, 0, 0.05, 1])
ax.set_aspect(1)
ax.axis('Off')
# grid the data.
Vi = griddata((x, y), Values, (Xi[None,:], Yi[:,None]), method='cubic')
cf = ax.contour(Xi,Yi,Vi, 15, cmap=plt.cm.jet)
#make a custom colorbar, because the default is ugly
gradient = np.linspace(1, 0, 256)
gradient = np.vstack((gradient, gradient))
cax.xaxis.set_major_locator(plt.NullLocator())
cax.yaxis.tick_right()
cax.imshow(gradient.T, aspect='auto', cmap=plt.cm.jet)
cax.set_yticks(np.linspace(0,256,len(cf1.get_array())))
cax.set_yticklabels(map(str, cf.get_array())[::-1])
Related
I am a beginner in Python and I'll try to visualize a function depending on x and y. With the contour plot the maximum should be seen easier.
That is my code and the output. The question comes below.
# Generate synthetic plot
x_fine=np.linspace(-4,10,100).reshape(-1,1)
y_fine=np.linspace(-3,3,100)
f_xy=1.5*np.sin(x_fine) - 0.1*(x_fine-3)**2 +10 - 0.5*(y_fine**2-2) + np.sin(y_fine)*2
#f_xy= -(x_fine-2)**2 - (y_fine)**2 +20
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(x_fine, y_fine, f_xy, cmap=cm.coolwarm, linewidth=0, antialiased=False, alpha=0.6)
ax.contourf(x_fine.flatten(),y_fine, f_xy, zdir='z', offset=-0, cmap=cm.coolwarm,alpha=0.7)
ax.set_xlabel("$x$")
ax.set_ylabel("$y$")
ax.set_zlabel("Objective")
plt.show()
So, if you might see, the plot contour plot reveals the contours, but somehow rotated by 90 degrees. If I just change the x and y input in the contour plot method, the graphs deforms. If I change the x and y input both in the contour plot and in the plot_surface command, the graph is shown correctly. But then, I need declare my x-axis as "y" and vice versa, which I would like to avoid.
I hope I made my problem clear. I am interested in reading your answers what I might have done wrong or why the code behaves as is behaves :D
In numpy, the axes are ordered row-column, e.g.
a = np.zeros((3, 2))
a
>>> [[0, 0],
[0, 0],
[0, 0]]
When you reshape x_fine to (-1, 1) you give it a width of 1 and a height of N. Something like.
x_fine
>> [[-4],
[-3.96],
...
[10]]
Following the convention of x across and y up/down, this is the wrong orientation.
Put the reshap(-1, 1) on y_fine instead. Then when you draw the contours call flatten() on y_fine instead of x_fine.
Now the two plots are orientated the same. If you want to verify that they are rotated correctly, and not BOTH off by 90 degrees, set f_xy to something simple like f_xy = x_fine + np.zeros_like(y_fine) and you'll see both increasing in the x direction.
I am drawing a 2d line (x1, y1) -> (x2,y2) and rotating by angle theta in matplotlib using Affine2D.rotate_deg_around.
start = (120, 0)
ht = 100
coords = currentAxis.transData.transform([start[0],start[1]])
trans1 = mpl.transforms.Affine2D().rotate_deg_around(coords[0],coords[1], 45)
line1 = lines.Line2D([start[0], start[0]], [start[1], ht+start[1]], color='r', linewidth=2)
line1.set_transform(currentAxis.transData + trans1)
currentAxis.add_line(line1)
Now (x2, y2) wont be (120, 100) after rotation. I need to find new (x2, y2) after rotation.
The matplotlib transformation functions might not be the most comfortable solution here.
Since you are rotating and translating your original data points you might be better off using a "common" 3 x 3 rotation matrix and a separate translation. Or a 4 x 4 matrix with holds both, rotation and translation.
Check the function rotation_matrix(angle, direction, point=None) from
http://www.lfd.uci.edu/~gohlke/code/transformations.py.html
This one returns a 4 x 4 matrix for rotation, but you can set the upper three components in the right column with the translation.
It might look scary at first :)
But once you get used to it is a very handy tool for that sort of things.
A bit more info
http://www.dirsig.org/docs/new/affine.html
http://www.euclideanspace.com/maths/geometry/affine/matrix4x4/
https://en.wikipedia.org/wiki/Transformation_matrix
You are first transforming to display coordinates and then rotate about the point in display coordinates. However, what I think you want to do is to perform the rotation in data coordinates and then transform to display coordinates.
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.lines as lines
start = (120, 0)
ht = 100
fig, ax = plt.subplots()
trans1 = mpl.transforms.Affine2D().rotate_deg_around(start[0],start[1], 45)
line1 = lines.Line2D([start[0], start[0]], [start[1], ht+start[1]], color='r', linewidth=2)
line1.set_transform(trans1 + ax.transData)
ax.add_line(line1)
ax.relim()
ax.autoscale_view()
plt.show()
The transform can then also be used to obtain the rotated coordinates
newpoint = trans1.transform([start[0], ht+start[1]])
# in this case newpoint would be [ 49.28932188 70.71067812]
I could not get new co ordinates after transformation. So did the following.
shifted axis and Rotated around point of rotation.
dot product( matrix of above operation, point which need to be transformed(P))
Result of dot product gives new co-ordinates of P after rotation
trans1 = mpl.transforms.Affine2D().rotate_deg_around(120, 100, 45)
txn = np.dot(trans1.get_matrix(), [120, 200, 1])
line1 = lines.Line2D([120, txn[0]], [100, txn[1]], color='r', linewidth=line_width)
currentAxis.add_line(line1)
I know that matplotlib and scipy can do bicubic interpolation:
http://matplotlib.org/examples/pylab_examples/image_interp.html
http://docs.scipy.org/doc/scipy/reference/tutorial/interpolate.html
http://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp2d.html
I also know that it is possible to draw a map of the world with matplotlib:
http://matplotlib.org/basemap/users/geography.html
http://matplotlib.org/basemap/users/examples.html
http://matplotlib.org/basemap/api/basemap_api.html
But can I do a bicubic interpolation based on 4 data points and only color the land mass?
For example using these for 4 data points (longitude and latitude) and colors:
Lagos: 6.453056, 3.395833; red HSV 0 100 100 (or z = 0)
Cairo: 30.05, 31.233333; green HSV 90 100 100 (or z = 90)
Johannesburg: -26.204444, 28.045556; cyan HSV 180 100 100 (or z = 180)
Mogadishu: 2.033333, 45.35; purple HSV 270 100 100 (or z = 270)
I am thinking that it must be possible to do the bicubic interpolation across the range of latitudes and longitudes and then add oceans, lakes and rivers on top of that layer? I can do this with drawmapboundary. Actually there is an option maskoceans for this:
http://matplotlib.org/basemap/api/basemap_api.html#mpl_toolkits.basemap.maskoceans
I can interpolate the data like this:
xnew, ynew = np.mgrid[-1:1:70j, -1:1:70j]
tck = interpolate.bisplrep(x, y, z, s=0)
znew = interpolate.bisplev(xnew[:,0], ynew[0,:], tck)
Or with scipy.interpolate.interp2d:
http://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp2d.html
Here it is explained how to convert to map projection coordinates:
http://matplotlib.org/basemap/users/mapcoords.html
But I need to figure out how to do this for a calculated surface instead of individual points. Actually there is an example of such a topographic map using external data, which I should be able to replicate:
http://matplotlib.org/basemap/users/examples.html
P.S. I am not looking for a complete solution. I would much prefer to solve this myself. Rather I am looking for suggestions and hints. I have been using gnuplot for more than 10 years and only switched to matplotlib within the past few weeks, so please don't assume I know even the simplest things about matplotlib.
I think this is what you are looking for (roughly). Note the crucial things are masking the data array before you plot the pcolor and passing in the hsv colormap (Docs: cmap parameter for pcolormesh and available colormaps).
I've kept the code for plotting the maps quite close to the examples so it should be easy to follow. I've kept your interpolation code for the same reason. Note that the interpolation is linear rather than cubic - kx=ky=1 - because you don't give enough points to do cubic interpolation (you'd need at least 16 - scipy will complain with less saying that "m must be >= (kx+1)(ky+1)", although the constraint is not mentioned in the documentation).
I've also extended the range of your meshgrid and kept in lat / lon for x and y throughout.
Code
from mpl_toolkits.basemap import Basemap,maskoceans
import matplotlib.pyplot as plt
import numpy as np
from scipy import interpolate
# set up orthographic map projection with
# perspective of satellite looking down at 0N, 20W (Africa in main focus)
# use low resolution coastlines.
map = Basemap(projection='ortho',lat_0=0,lon_0=20,resolution='l')
# draw coastlines, country boundaries
map.drawcoastlines(linewidth=0.25)
map.drawcountries(linewidth=0.25)
# Optionally (commented line below) give the map a fill colour - e.g. a blue sea
#map.drawmapboundary(fill_color='aqua')
# draw lat/lon grid lines every 30 degrees.
map.drawmeridians(np.arange(0,360,30))
map.drawparallels(np.arange(-90,90,30))
data = {'Lagos': (6.453056, 3.395833,0),
'Cairo': (30.05, 31.233333,90),
'Johannesburg': (-26.204444, 28.045556,180),
'Mogadishu': (2.033333, 45.35, 270)}
x,y,z = zip(*data.values())
xnew, ynew = np.mgrid[-30:60:0.1, -50:50:0.1]
tck = interpolate.bisplrep(x, y, z, s=0,kx=1,ky=1)
znew = interpolate.bisplev(xnew[:,0], ynew[0,:], tck)
znew = maskoceans(xnew, ynew, znew)
col_plot = map.pcolormesh(xnew, ynew, znew, latlon=True, cmap='hsv')
plt.show()
Output
Observe that doing the opposite, that is putting a raster on the sea and lay a mask over the continents, is easy as pie. Simply use map.fillcontinents(). So the basic idea of this solution is to modify the fillcontinents function so that it lays polygons over the oceans.
The steps are:
Create a large circle-like polygon that covers the entire globe.
Create a polygon for each shape in the map.coastpolygons array.
Cut the shape of the landmass polygon away from the circle using shapely and its difference method.
Add the remaining polygons, which have the shape of the oceans, on the top, with a high zorder.
The code:
from mpl_toolkits.basemap import Basemap
import numpy as np
from scipy import interpolate
from shapely.geometry import Polygon
from descartes.patch import PolygonPatch
def my_circle_polygon( (x0, y0), r, resolution = 50 ):
circle = []
for theta in np.linspace(0,2*np.pi, resolution):
x = r * np.cos(theta) + x0
y = r * np.sin(theta) + y0
circle.append( (x,y) )
return Polygon( circle[:-1] )
def filloceans(the_map, color='0.8', ax=None):
# get current axes instance (if none specified).
if not ax:
ax = the_map._check_ax()
# creates a circle that covers the world
r = 0.5*(map.xmax - map.xmin) # + 50000 # adds a little bit of margin
x0 = 0.5*(map.xmax + map.xmin)
y0 = 0.5*(map.ymax + map.ymin)
oceans = my_circle_polygon( (x0, y0) , r, resolution = 100 )
# for each coastline polygon, gouge it out of the circle
for x,y in the_map.coastpolygons:
xa = np.array(x,np.float32)
ya = np.array(y,np.float32)
xy = np.array(zip(xa.tolist(),ya.tolist()))
continent = Polygon(xy)
## catches error when difference with lakes
try:
oceans = oceans.difference(continent)
except:
patch = PolygonPatch(continent, color="white", zorder =150)
ax.add_patch( patch )
for ocean in oceans:
sea_patch = PolygonPatch(ocean, color="blue", zorder =100)
ax.add_patch( sea_patch )
########### DATA
x = [3.395833, 31.233333, 28.045556, 45.35 ]
y = [6.453056, 30.05, -26.204444, 2.033333]
z = [0, 90, 180, 270]
# set up orthographic map projection
map = Basemap(projection='ortho', lat_0=0, lon_0=20, resolution='l')
## Plot the cities on the map
map.plot(x,y,".", latlon=1)
# create a interpolated mesh and set it on the map
interpol_func = interpolate.interp2d(x, y, z, kind='linear')
newx = np.linspace( min(x), max(x) )
newy = np.linspace( min(y), max(y) )
X,Y = np.meshgrid(newx, newy)
Z = interpol_func(newx, newy)
map.pcolormesh( X, Y, Z, latlon=1, zorder=3)
filloceans(map, color="blue")
VoilĂ :
I am stuck in a rather complicated situation. I am plotting some data as an image with imshow(). Unfortunately my script is long and a little messy, so it is difficult to make a working example, but I am showing the key steps. This is how I get the data for my image from a bigger array, written in a file:
data = np.tril(np.loadtxt('IC-heatmap-20K.mtx'), 1)
#
#Here goes lot's of other stuff, where I define start and end
#
chrdata = data[start:end, start:end]
chrdata = ndimage.rotate(chrdata, 45, order=0, reshape=True,
prefilter=False, cval=0)
ax1 = host_subplot(111)
#I don't really need host_subplot() in this case, I could use something more common;
#It is just divider.append_axes("bottom", ...) is really convenient.
plt.imshow(chrdata, origin='lower', interpolation='none',
extent=[0, length*resolution, 0, length*resolution]) #resolution=20000
So the values I am interested in are all in a triangle with the top angle in the middle of the top side of a square. At the same time I plot some data (lot's of coloured lines in this case) along with the image near it's bottom.
So at first this looks OK, but is actually is not: all pixels in the image are not square, but elongated with their height being bigger, than their width. This is how they look if I zoom in:
This doesn't happen, If I don't set extent when calling imshow(), but I need it so that coordinates in the image and other plots (coloured lines at the bottom in this case), where identical (see Converting coordinates of a picture in matplotlib?).
I tried to fix it using aspect. I tried to do that and it fixed the pixels' shape, but I got a really weird picture:
The thing is, later in the code I explicitly set this:
ax1.set_ylim(0*resolution, length*resolution) #resolution=20000
But after setting aspect I get absolutely different y limits. And the worst thing: ax1 is now wider, than axes of another plot at the bottom, so that their coordinates do not match anymore! I add it in this way:
axPlotx = divider.append_axes("bottom", size=0.1, pad=0, sharex=ax1)
I would really appreciate help with getting it fixed: square pixels, identical coordinates in two (or more, in other cases) plots. As I see it, the axes of the image need to become wider (as aspect does), the ylims should apply and the width of the second axes should be identical to the image's.
Thanks for reading this probably unclear explanation, please, let me know, if I should clarify anything.
UPDATE
As suggested in the comments, I tried to use
ax1.set(adjustable='box-forced')
And it did help with the image itself, but it caused two axes to get separated by white space. Is there any way to keep them close to each other?
Re-edited my entire answer as I found the solution to your problem. I solved it using the set_adjustable("box_forced") option as suggested by the comment of tcaswell.
import numpy
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot, make_axes_locatable
#Calculate aspect ratio
def determine_aspect(shape, extent):
dx = (extent[1] - extent[0]) / float(shape[1])
dy = (extent[3] - extent[2]) / float(shape[0])
return dx / dy
data = numpy.random.random((30,60))
shape = data.shape
extent = [-10, 10, -20, 20]
x_size, y_size = 6, 6
fig = plt.figure(figsize = (x_size, y_size))
ax = host_subplot(1, 1, 1)
ax.imshow(data, extent = extent, interpolation = "None", aspect = determine_aspect(shape, extent))
#Determine width and height of the subplot frame
bbox = ax.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
width, height = bbox.width, bbox.height
#Calculate distance, the second plot needs to be elevated by
padding = (y_size - (height - width)) / float(1 / (2. * determine_aspect(shape, extent)))
#Create second image in subplot with shared x-axis
divider = make_axes_locatable(ax)
axPlotx = divider.append_axes("bottom", size = 0.1, pad = -padding, sharex = ax)
#Turn off yticks for axPlotx and xticks for ax
axPlotx.set_yticks([])
plt.setp(ax.get_xticklabels(), visible=False)
#Make the plot obey the frame
ax.set_adjustable("box-forced")
fig.savefig("test.png", dpi=300, bbox_inches = "tight")
plt.show()
This results in the following image where the x-axis is shared:
Hope that helps!
Update: I've done a full write-up of the way I found to do this on my blog at http://blog.rtwilson.com/producing-polar-contour-plots-with-matplotlib/ - you may want to check there first.
I'm trying to plot a polar contour plot in matplotlib. I've found various resources on the internet, (a) I can't seem to get my code to work and (b) many of the resources appear rather old, and I'm wondering if there is a better way now. For example, http://www.mail-archive.com/matplotlib-users#lists.sourceforge.net/msg01953.html suggests that something may be done to improve things soon, and that was in 2006!
I'd love to be able to plot proper polar contour plots - like pcolor lets you do for its type of plot (see commented out section below), but I can't seem to find any way to do that, so I'm converting to cartesian co-ordinates first.
Anyway, I have the code that follows:
from pylab import *
import numpy as np
azimuths = np.arange(0, 360, 10)
zeniths = np.arange(0, 70, 10)
values = []
for azimuth in azimuths:
for zenith in zeniths:
print "%i %i" % (azimuth, zenith)
# Run some sort of model and get some output
# We'll just use rand for this example
values.append(rand())
theta = np.radians(azimuths)
values = np.array(values)
values = values.reshape(len(zeniths), len(azimuths))
# This (from http://old.nabble.com/2D-polar-surface-plot-td28896848.html)
# works fine
##############
# Create a polar axes
# ax = subplot(111, projection='polar')
# pcolor plot onto it
# c = ax.pcolor(theta, zeniths, values)
# show()
r, t = np.meshgrid(zeniths, azimuths)
x = r*np.cos(t)
y = r*np.sin(t)
contour(x, y, values)
When I run that I get an error TypeError: Inputs x and y must be 1D or 2D.. I'm not sure why I get this, as both x and y are 2D. Am I doing something wrong?
Also, it seems rather clunky to be putting my values returned from my model into a list and then reshaping it. Is there a better way to do this?
You should just be able to use ax.contour or ax.contourf with polar plots just as you normally would... You have a few bugs in your code, though. You convert things to radians, but then use the values in degrees when you plot. Also, you're passing in r, theta to contour when it expects theta, r.
As a quick example:
import numpy as np
import matplotlib.pyplot as plt
#-- Generate Data -----------------------------------------
# Using linspace so that the endpoint of 360 is included...
azimuths = np.radians(np.linspace(0, 360, 20))
zeniths = np.arange(0, 70, 10)
r, theta = np.meshgrid(zeniths, azimuths)
values = np.random.random((azimuths.size, zeniths.size))
#-- Plot... ------------------------------------------------
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.contourf(theta, r, values)
plt.show()
the shape of x, y and values must be the same. Your data shape is:
>>> x.shape, y.shape, values.shape
((36, 7), (36, 7), (7, 36))
so change contour(x, y, values) to contour(x, y, values.T).