Curious (bad?) behavior creating all-sky projections with matplotlib - python

I am trying to make a density "all-sky" plot which is complete in RA (i.e 0 to 360 deg) but incomplete in DEC (let's say from -45 to 90 deg). If I plot this without any projection it is ok, but when I try to plot using the 'mollweide' projection I am not recovering the input, but if I do a little change in the code I do recover the expected behavior (however, I don't have a coherent explanation for this change as you'll see in the example).
Let's see a self-contained example with its outputs to be clearer:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.backends.backend_agg
from math import pi
#array between 0 and 360 deg
RA = np.random.random(10000)*360
#array between -45 and 90 degrees. By construction!
DEC= np.random.random(10000)*135-45
fig = plt.Figure((10, 4.5))
ax = fig.add_subplot(111,projection='mollweide')
ax.grid(True)
ax.set_xlabel('RA')
ax.set_ylabel('DEC')
ax.set_xticklabels(np.arange(30,331,30))
hist,xedges,yedges = np.histogram2d(DEC,RA,bins=[90,180],range=[[-90,90],[0,360]])
#TO RECOVER THE EXPECTED BEHAVIOUR, I HAVE TO CHANGE -90 FOR -80 IN THE PREVIOUS LINE:
#hist,xedges,yedges = np.histogram2d(DEC,RA,bins=[90,180],range=[[-80,90],[0,360]])
#I DO NOT WHY!
extent = (-pi,pi,-pi/2.,pi/2.)
image = ax.imshow(hist,extent=extent,clip_on=False,aspect=0.5,origin='lower')
cb = fig.colorbar(image, orientation='horizontal')
canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(fig)
fig.canvas.print_figure("image1.png")
And the output image is:
[As I am new here I am not allowed to post images, so I will post a link, if it does not work, please write me an email and I can share a Dropbox folder with the images ;)]
Output Image that I am getting
Where you can see clearly that the RA is OK so it ranges between 0 and 360, BUT the DEC ranges from -35 to 90 instead of -45 to 90. So far I do not understand why I am missing 10 deg.
However, if I do a little change in the code, replacing the line
hist,xedges,yedges = np.histogram2d(DEC,RA,bins=[90,180],range=[[-90,90],[0,360]]
for
hist,xedges,yedges = np.histogram2d(DEC,RA,bins=[90,180],range=[[-80,90],[0,360]]
I get what I think I should get, which is this plot:
Output Image 2
[Again, if the link does not work, let me know and I can share a Dropbox folder with you]
where DEC now ranges from -45 to 90 as expected because I created DEC in that way.
However the change of -90 for -80 doesn't make sense (I think).
So probably I am doing something wrong that I can't notice now, or I am misunderstanding something in the code or there is a curious bug in matplotlib??
Please any help/hint/correction would be greatly appreciate it
Eduardo

if you don't mind depending on an external package, you could do this with healpy, that provides a Mollweide projection for the Healpix sky pixellization:
https://github.com/healpy/healpy
See an example similar to your script here:
https://gist.github.com/1215159
More info about healpix:
http://healpix.jpl.nasa.gov/html/intro.htm
Ouput image:

If this is useful for someone else, this is the "corrected version" of my code, which gives as output this image. The main change is to use pcolormesh instead of imshow (as #Joe suggested):
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.backends.backend_agg
#array between 0 and 360 deg
#CAVEAT: it seems that is needed an array from -180 to 180, so is just a
#shift in the coordinates
RA = np.random.random(10000)*360-180
#array between -45 and 90 degrees
DEC= np.random.random(10000)*135-45
fig = plt.Figure((10, 5))
ax = fig.add_subplot(111,projection='mollweide')
ax.set_xlabel('RA')
ax.set_ylabel('DEC')
ax.set_xticklabels(np.arange(30,331,30))
hist,xedges,yedges = np.histogram2d(DEC,RA,bins=[60,40],range=[[-90,90],[-180,180]])
X,Y = np.meshgrid(np.radians(yedges),np.radians(xedges))
image = ax.pcolormesh(X,Y,hist)
ax.grid(True)
cb = fig.colorbar(image, orientation='horizontal')
canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(fig)
fig.canvas.print_figure("image4.png")

Related

How to format median and errors differently in corner plots?

I'm trying to format a corner plot using the corner package in Python. As far as I know, there's the command title_fmt = *arg, however it gives the same format to both the median and the errors, which is inconvenient for reporting measurement errors. I need the error to be shown with 2 significant figures, and then the median to be rounded at the last sig.fig. of its error. Here's an example of what I can do
import numpy as np
from matplotlib import pyplot as plt
import corner
np.random.seed(539)
# generate data
data = np.random.randn(5000,3)
data[:,0] = data[:,0]*20 + 150.75
data[:,1] = data[:,1] + 7.52
data[:,2] = data[:,2]*5 + 31.25
# make plot
labels = ['x','y','f']
fig=plt.figure(figsize=(7,7),dpi=100)
fig=corner.corner(
data, labels=labels, quantiles=(0.16, 0.84),show_titles=True,
title_fmt='g', use_math_text=True, fig=fig)
fig.show()
which gives
I could enter a line like title_fmt = '.2g' which gives
Where as expected shows less sig.fig. for the errors, but then I'm missing sig.fig. in the median and some of the errors don't display the last sig.fig. when it's 0. For my example I'd need to get something like
x = 151 +20
-19
y = 7.5 +1.0
-1.0
f = 31.3 +5.2
-5.0
I've read the API and it doesn't give any more explanations beyond the title_fmt option. If anyone can help, thanks in advance.
title_fmt = '.2f' should format numbers with 2 decimal places.
Here you have all possible formatting options:
https://docs.python.org/3/reference/lexical_analysis.html#f-strings

Python's Basemap doesn't align with correct coordinates?

I have a .dat file containing a list of coordinates (~100k) and a temperature at each coordinate. It has a structure like this:
-59.083 -26.583 0.2
-58.417 -26.250 0.6
-58.412 -26.417 0.4
...
To visually display the temperature ranges, I created a numpy array and plotted the datasets using the Basemap module for Python. The code I wrote is the following:
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
m = Basemap(projection='mill',llcrnrlat=-90,urcrnrlat=90,\
llcrnrlon=-180,urcrnrlon=180,resolution='c')
m.drawcoastlines(linewidth=0.15)
data = np.loadtxt('gridly.dat')
xcoordlist = []
ycoordlist = []
tempvallist = []
for i in data:
xcoord = i[0]
ycoord = i[1]
tempval = i[2]
xcoord2 = xcoord*111139 #<--- Multiplying converts each coordinate's degrees to meters)
ycoord2 = ycoord*111139
xcoordlist.append(xcoord2)
ycoordlist.append(ycoord2)
tempvallist.append(tempval)
xco = np.array(xcoordlist)
yco = np.array(ycoordlist)
tval = np.array(tempvallist)
gridsize = 100
m.hexbin(yco, xco, C=tval, gridsize=gridsize)
cb = m.colorbar()
plt.show()
When I plot the data, I'm getting almost exactly what I want, however, the hexagonal heatmap is offset for some reason, giving me the following chart:
I've been searching online for what might be wrong but unfortunately couldn't find answers or troubleshoot. Does anyone know how I can fix this issue?
After hours of digging around, I finally figured it out! What was wrong with my code was that I was trying to manually convert the geographic coordinates into point coordinates for the displaying chart (by multiplying by 111139).
While the logic for doing this makes sense, I believe this process broke down when I began to plot the data onto different kinds of charts (i.e. orthogonal, miller projection etc.) because the different projections/charts will have different point coordinates (kind of like how the pixel locations on your computer screen may not align with the pixel locations on a different computer screen).
Instead, the Basemap module has a built-in function that will convert real-world coordinates into coordinates that can be plotted on the chart, for you: m(x, y).
So, the improved and correct script would be:
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
m = Basemap(projection='mill',llcrnrlat=-90,urcrnrlat=90,\
llcrnrlon=-180,urcrnrlon=180,resolution='c')
m.drawcoastlines(linewidth=0.15)
data = np.loadtxt('gridly.dat')
xcoordlist = []
ycoordlist = []
tempvallist = []
for i in data:
lat = i[0]
lon = i[1]
tempval = i[2]
xpt, ypt = m(lon, lat)
xcoordlist.append(xpt)
ycoordlist.append(ypt)
tempvallist.append(tempval)
xco = np.array(xcoordlist)
yco = np.array(ycoordlist)
tval = np.array(tempvallist)
gridsize = 100
m.hexbin(xco, yco, C=tval, gridsize=gridsize)
cb = m.colorbar()
plt.show()
As you can see where it says xpt, ypt = m(lon, lat), the function converts the real world longitudes (lon) and latitudes (lat) from the .dat file into pottable points. Hope this helps anyone else that may have this problem in the future!

Python Basemap: Error using shadedrelief, bluemarble or etopo (false longitude format?)

I want to plot a map of the southern hemisphere centered on the pacific with some stuff drawn onto it with python matplotlib basemap.
Everything works fine unless I try to draw a background image with the basemap routines shadedrelief, bluemarble or etopo. The code (without the stuff i want to draw onto the map) looks like this:
import numpy as np
from mpl_toolkits.basemap import Basemap
from matplotlib.backends.backend_pdf import PdfPages
latmin = -72.5
latmax = 40.
lonmin = 60.
lonmax = 370.
pp = PdfPages('datamap.pdf')
m = Basemap(projection='merc', llcrnrlat=latmin, urcrnrlat=latmax, llcrnrlon=lonmin, urcrnrlon=lonmax, resolution="c")
m.drawcoastlines(linewidth=0.25)
#m.shadedrelief()
pp.savefig()
pp.close()
When I uncomment the m.shadedrelief() I get the following:
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
/xyz/datamap.py in <module>()
32
---> 33 m.shadedrelief()
34
/usr/local/lib/python2.7/site-packages/mpl_toolkits/basemap/__init__.pyc in shadedrelief(self, ax, scale, **kwargs)
3997 return self.warpimage(image='shadedrelief',ax=ax,scale=scale,**kwargs)
3998 else:
-> 3999 return self.warpimage(image='shadedrelief',scale=scale,**kwargs)
4000
4001 def etopo(self,ax=None,scale=None,**kwargs):
/usr/local/lib/python2.7/site-packages/mpl_toolkits/basemap/__init__.pyc in warpimage(self, image, scale, **kwargs)
4115 # any range of longitudes may be plotted on a world map.
4116 self._bm_lons = \
-> 4117 np.concatenate((self._bm_lons,self._bm_lons+360),1)
4118 self._bm_rgba = \
4119 np.concatenate((self._bm_rgba,self._bm_rgba),1)
IndexError: axis 1 out of bounds [0, 1)
When I choose (for test purposes) way smaller maps which also dont have longitude larger than 180 degree everything (including shadedrelief) works fine. This leads me to the assumption that something with the format of the longitude is not working here. I tried some things but i cant seem to find a way how to solve this while still plotting the same map section
Do you have any idea how i could draw a shadedrelief in the background of my map?
Best,
xilian
It seems there was a bug in the basemap/init code. In fact, if you look at the latest version on github [see https://github.com/matplotlib/basemap/blob/master/lib/mpl_toolkits/basemap/init.py#L4139 and how it is different from line 4117 of your error] you will find out that the bug has been solved already. This means you can either do:
# np.concatenate((self._bm_lons,self._bm_lons+360),1)
np.concatenate((self._bm_lons,self._bm_lons+360))
in the init.py code or get a newer version of basemap. Any of the two will solve your problem.

Plotting netCDF data with Python: How to change grid?

I'm an new one in python and plotting data with Matplotlib. I really need help and thank you in advance for the answers.
So, I have a netCDF file with v-component of wind data. Grid coordinates: points=9600 (240x40)
lon : 0 to 358.5 by 1.5 degrees_east circular
lat : 88.5 to 30 by -1.5 degrees_north
My code is:
import numpy as np
import matplotlib
matplotlib.use('Agg')
from netCDF4 import Dataset
from matplotlib.mlab import griddata
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
#read data from NETcdf file ".nc"
my_file = '/home/Era-Interim/NH-EraInt-1979.nc'
fh = Dataset(my_file, mode='r')
lons = fh.variables['lon'][:]
lats = fh.variables['lat'][:]
V = fh.variables['V'][:]
V_units = fh.variables['V'].units
fh.close()
# create figure
fig = plt.figure(figsize=(20,20))
# create a map
m = Basemap(projection='nplaea',boundinglat=30,lon_0=10,resolution='l',round=True)
#draw parallels, meridians, coastlines, countries, mapboundary
m.drawcoastlines(linewidth=0.5)
m.drawcountries(linewidth=0.5)
#m.drawmapboundary(linewidth=2)
m.drawparallels(np.arange(30,90,20), labels=[1,1,0,0]) #paral in 10 degree, right, left
m.drawmeridians(np.arange(0,360,30), labels=[1,1,1,1]) #merid in 10 degree, bottom
#Plot the data on top of the map
lon,lat = np.meshgrid(lons,lats)
x,y = m(lon,lat)
cs = m.pcolor(x,y,np.squeeze(V),cmap=plt.cm.RdBu_r)
plt.title("", fontsize=25, verticalalignment='baseline')
plt.savefig("/home/Era-Interim/1.png")
As a result, I received a map (you can find in my dropbox folder) https://www.dropbox.com/sh/nvy8wcodk9jtat0/AAC-omkPP8_7uINSSXbzImeja?dl=0
On the map, there are white pixels between 358.5 and 0 (360) lon, because I have no data between 358.5 and 0 (360) lon.
The question is: how can I change the size of the grid, regrid it, interpolate data, or something else in order to not have this white sector?
I have found a solution. At the beginning of the script, you must add
from mpl_toolkits.basemap import Basemap, addcyclic
and further
datain, lonsin = addcyclic(np.squeeze(Q), lons)
lons, Q = m.shiftdata(lonsin, datain = np.squeeze(Q), lon_0=180.)
print lons
lon, lat = np.meshgrid(lons, lats)
x,y = m(lon, lat)
cs = m.pcolor(x,y,datain,cmap=plt.cm.RdBu_r)
The difference can be seen in the figures (I still can not post images).
https://www.dropbox.com/sh/nvy8wcodk9jtat0/AAC-omkPP8_7uINSSXbzImeja?dl=0
I think in this case some kind of interpolation techniques can be applied.
Check this out. There was similar problem.
Hope it is useful.
The simple answer is 360 degrees is 0 degrees, so you can copy the 0 degrees data and it should look right. I may be interpreting this wrong though, as I believe that the data is representing the pressure levels at each of the points, not between the two points (i.e. at zero degrees, not between zero degrees and 1.5 degrees).
My interpretation means that, yes, you don't have data between 358.5 and 0, but you also don't have data between 357 and 358.5. This seems more likely than just skipping an area. This would mean that the data from 358.5 should be touching the data from 0 as it is just as far away as 0 is from 1.5 which is touching.
Copying the last bit would grant you the ability to change your m.pcolor call to an imshow call (as in Roman Dryndik's link) and use interpolation to smooth out the graph.

Mayavi showing wrong object extent

I have a mosaic of Aster GDEM tiles which I have stitched together. When I plot the data with plt.contourf(mosaic.lon1d, mosaic.lat1d, mosaic.elev, 40, cmap=plt.cm.terrain) everything is in place and is shown correctly.
However, the following code:
fig = mlab.figure(figure='ICVM', bgcolor=(1,1,1), fgcolor=(0,0,0), size=(1024,786))
mlab.clf()
topo = mlab.surf(mosaic.lat1d, mosaic.lon1d, mosaic.elev, colormap='gist_earth', warp_scale=-1e-4)
mlab.axes(xlabel='lat.', ylabel='lon.')
mlab.outline()
mlab.view(-160, 125, 10, array([26.5,35.,-0.1168]))
mlab.roll(-90)
produces this plot, which is fine except for the the fact that the latitude goes from 24 to 29 instead of 29 to 34 like it should.
any ideas why that is? can I change this?
the following link will download the data (lat1d, lon1d, elev) as .npy file to be read in with numpy.load.
download data zip file: http://goo.gl/nhCNFS
It seems that mayavi has problems if the array values are not increasing (Your lat1d data is decreasing).
If you reverse your lat1d and elev array you get a correct plot.
fig = mlab.figure(figure='ICVM', bgcolor=(1,1,1), fgcolor=(0,0,0), size=(1024,786))
mlab.clf()
topo = mlab.surf(lat1d[::-1], lon1d, elev[::-1], colormap='gist_earth', warp_scale=-1e-4)
mlab.axes(xlabel='lat.', ylabel='lon.')
mlab.outline()

Categories

Resources