I'm trying to produce a plot which uses the same colorscale as the Met Office, so I can easily compare my plots to theirs. An example of theirs is at Here
My current closest effort is here:
Here
I appreciate my code is messy - I couldn't find a way to set a color for values above a certain threshold (otherwise it goes white),hence the loop.
I would upload the NetCDF File but I haven't got a high enough rep to do this.
Many, many thanks in advance for any help.
My code for plotting is shown below;
from Scientific.IO.NetCDF import NetCDFFile
from mpl_toolkits.basemap import Basemap
from matplotlib import pyplot as plt
import numpy as np
myfile = NetCDFFile('ERA_Dec_89-94.nc', 'r')
Lat = NetCDFFile('/home/james/Documents/Lat_Lon_NC_Files/latitudes_d02.nc','r')
Long = NetCDFFile('/home/james/Documents/Lat_Lon_NC_Files/longitudes_d02.nc','r')
XLAT = Lat.variables['XLAT'][:]
XLONG = Long.variables['XLONG'][:]
ERA_Data = myfile.variables['Monthlyrain'][:]
plot = np.zeros([1000,1730])
plot[:,:] = np.average(ERA_Data[:,:,:],axis=0)
m = Basemap(projection='merc',resolution='f',llcrnrlat=49,llcrnrlon=-11,urcrnrlat=61,urcrnrlon=3)
m.drawparallels(np.arange(-90., 91., 5.), labels=[1,0,0,0], fontsize=11)
m.drawmeridians(np.arange(-180., 181., 5.), labels=[0,0,0,1], fontsize=11)
m.drawcoastlines()
X, Y = m(XLONG, XLAT)
for i in range(0,1729):
for j in range(0,999):
if plot[j,i] >250:
plot[j,i] = 250.001
if plot[j,i] < 40:
plot[j,i] = 40
scale = [40,40.001,60,80,100,125,150,200,250, 250.001]
cs = m.contourf(X,Y,plot,scale, cmap='PuOr')
cbar = m.colorbar(cs, ticks= [40.0005,50,70,90,112.5,137.5,175,225,250.0005])
cbar.set_ticklabels(['<40','40-60', '60-80', '80-100', '100-125', '125-150', '150-200', '200-250', '>250'])
plt.title('Some Title')
cbar.set_label('Monthly average rainfall (mm)')
print "Finished"
plt.show()
If the issue is simply the colormap, you can pick the RGB components of the colors off your screen and turn them into a ListedColormap, mapped to the boundaries of the rainfall in the chart you give as an example. For example,
bounds = [0, 40, 60, 80, 100, 125, 150, 200, 250, 1000]
rgblist = [(51,0,0), (102,51,0), (153,102,51), (204,153,102), (255, 255, 255),
(204,204,255), (153,153,255), (51,102,255), (0,0,153)]
clist = [[c/255 for c in rgb] for rgb in rgblist]
from matplotlib import colors
cmap = colors.ListedColormap(clist)
norm = colors.BoundaryNorm(bounds, cmap.N)
ax.imshow(arr, cmap=cmap, norm=norm)
plt.show()
The first part (getting the colors right) was already answered. In order to restrict the values to a certain range you have several options.
Use cmap.set_over and cmap.set_under to set out-of-bounds colors, as described here
use np.clip instead of the loop to restrict the values to a certian range:
plot = np.clip(plot, 40, 250)
Related
I have plotted a CartoPy contour plot which looks like this:
using the following script:
precip_full1 = xr.open_dataset('era_yr1979.nc')
precip_full2 = xr.open_dataset('era_yr1980.nc')
precip_full3 = xr.open_dataset('era_yr1981.nc')
precip_full4 = xr.open_dataset('era_yr1982.nc')
precip_full5 = xr.open_dataset('era_yr1983.nc')
precip_full6 = xr.open_dataset('era_yr1984.nc')
precip_full = xr.concat([precip_full1,precip_full2,precip_full3,precip_full4,precip_full5,precip_full6],dim = 'time')
output = []
for x in np.arange(6.5,10.25,0.25):
for y in np.arange(-15,-9.75,0.25):
precip = precip_full.where((precip_full.latitude==x)&(precip_full.longitude==y),drop=True)
roll = precip.rolling(time=6,center=False).sum()
annual = roll.groupby('time.year').max()
tab = annual.to_dataframe().rename(columns={'tp':6})
output = pd.concat(output,1)
mean = output.mean()
data_mean = pd.DataFrame(mean, columns=['mean'])
df = data_mean.to_numpy()
new = [df[i:i+21] for i in range(0,len(df),21)]
new = np.reshape(new, [-1, 21])
df = pd.DataFrame(data=new, dtype=object)
lon2d, lat2d = np.meshgrid(lon, lat)
plt.figure(figsize=(6,5))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent([-15,-10,6.5,10])
ax.coastlines()
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.LAKES)
ax.add_feature(cfeature.RIVERS)
ax.add_feature(cfeature.BORDERS)
gl = ax.gridlines(draw_labels=True, xlocs=np.arange(-180,180,0.25), ylocs=np.arange(-90,90,0.25),linewidth=0.4)
gl.top_labels = False
gl.right_labels = False
plot = plt.contourf(lon2d, lat2d, df, cmap = 'jet', transform=ccrs.PlateCarree())
I've now realized I'd prefer a box plot, with one solid colour in each grid box, I no longer want interpolation between grid points.
I found that I can use pcolormesh instead of contour to do this. However, when I change the last line of code:
plot = plt.pcolormesh(lon2d, lat2d, df, cmap = 'jet', transform=ccrs.PlateCarree())
I get the following error:
TypeError: Dimensions of C (15, 21) are incompatible with X (15) and/or Y (15)
I can't see what this error means to know how to fix it. Has anyone done anything similar?
OK, since you've asked... here's a quick example how you can do it with EOmaps...
Note that data and coordinates can be provided as 1D or 2D arrays (or mixtures of 1D and 2D as below) or as a pandas.DataFrames.
It's also possible to plot directly from a NetCDFs (or GeoTIFFs) via m.new_layer_from_file.NetCDF(...)
from eomaps import Maps
import numpy as np
# create some data in a regular lon/lat grid (=epsg 4326)
x, dx = np.linspace(-45, 45, 55, retstep=True)
y, dy = np.linspace(-20, 30, 25, retstep=True)
vals = np.random.randint(0,100, (x.size, y.size))
# plot the data as lon/lat rectangles on a map displayed in Orthographic projection.
m = Maps(Maps.CRS.Orthographic())
m.add_feature.preset.coastline()
m.set_data(vals, x, y, crs=4326)
m.set_shape.rectangles(radius=(dx/2, dy/2), radius_crs=4326)
m.plot_map()
I have a dataset of N=910 probabilities, and hte probabilites are represented as all integers between 5 and 90 that are divisible by 5. This constitutes my x input. Each probability has a boolean response associated with it, the booleans being encoded using a 0 for false and a 1 for true. Some code to recreate this.
x_inpt = np.random.choice(np.arange(5, 91, 5), 910)
y_inpt = np.random.choice([0, 1], 910)
A lot of the line plots for my actual data look like this.
(and for curiosity sake, here's the original code used for this plot)
plt.scatter(x_inpt, y_inpt)
plt.ylabel("Decisions On Adminstering Experimental Treatment")
plt.xlabel("Harm probabilities")
plt.xticks(range(0, 101, 10))
plt.yticks([0.0, 1.0], labels=["No", "Yes"])
title_str = "Pilot Data From " + str(exp_count) + " Experiments / " + str(num_trials) + " trials"
plt.title(title_str)
plt.tight_layout()
plt.show()
Even though this image has 910 data points, they all get placed on top of one another other. There's multiple instances of the same data point, or multiple instances of the same x y coordinate being plotted in my data.
I wanted to find a way to make data points that have the most instances be darker (or lighter) just to make this graph more clearly informative.
But I'm not really sure how to, and my code is stuck looking like the code sample I posted for the above plot. I seem to be having a rough time parsing matplotlib documentation and figuring out how to implement this.
A perhaps silly solution to this would be something like hashing each point based on (x,y) so it always is unique and counting this up:
# hash (x_inpt,y_input)
def hash(x,y):
# Dummy sum since we have two nice integer arrays
return x+y
hashed_output = hash(x_inpt, y_inpt)
x_y_weights = np.bincount(hashed_output)
color_for_each_sample = x_y_weights[hashed_output]
...
plt.scatter(x_inpt, y_inpt, c=color_for_each_sample)
plt.colorbar()
...
I'm working on a more elegant version now
If you don't mind pandas, you could use something like this
import pandas as pd
df = pd.DataFrame({'x':x_inpt, 'y':y_inpt})
grp = df.groupby(['x','y']).size().reset_index()
a = plt.scatter(grp['x'], grp['y'], c=grp[0], cmap='cool')
cbar = plt.colorbar()
cbar.ax.set_ylabel('Number of points', rotation=-90, va="bottom")
plt.ylabel("Decisions On Adminstering Experimental Treatment")
plt.xlabel("Harm probabilities")
plt.xticks(range(0, 101, 10))
plt.yticks([0.0, 1.0], labels=["No", "Yes"])
title_str = "Pilot Data"
plt.title(title_str)
plt.tight_layout()
plt.show()
Here is a solution using a counter to count each x,y pair. And then use scatter to either change the color or the size of the dots. Or even a number in text form. The size is proportional to the area of the dot, so I squared it in the demo below.
Just to show the possibilities, the three ways are combined in the experimental code. In practise, you'd probably only use one of the methods.
from matplotlib import pyplot as plt
import numpy as np
from collections import Counter
num_trials = 910
x_inpt = np.random.choice(np.arange(5, 91, 5), num_trials)
y_inpt = np.random.choice([0, 1], num_trials)
count = Counter(zip(x_inpt, y_inpt))
xs = np.array([x for (x, y), c in count.items()])
ys = np.array([y for (x, y), c in count.items()])
cs = np.array([c for (x, y), c in count.items()])
cmin = cs.min()
cmax = cs.max()
cmid = (cmin + cmax) / 2
fig, ax = plt.subplots(figsize=(12, 3))
plt.scatter(xs, ys, c=cs, cmap='plasma', s=1200*cs*cs/(cmax * cmax))
for (x, y), c in count.items():
# the maximum fontsize is set to 22
# the color is either white or black the contrast with the color of the scatter dot
ax.text(x, y, c, color='w' if c<cmid else 'k', fontsize=22*c/cmax, ha='center', va='center')
cbar = plt.colorbar()
cbar.ax.set_title('Counts')
plt.ylabel("Decisions On Adminstering\nExperimental Treatment")
plt.xlabel("Harm probabilities")
plt.xticks(range(0, 91, 10))
plt.ylim(-0.5, 1.5)
plt.yticks([0, 1], labels=["No", "Yes"])
title_str = f"Pilot Data From {20} Experiments / {num_trials} trials"
plt.title(title_str)
plt.tight_layout()
plt.show()
Here is another example, supposing the data has a binomial distribution and using the reversed colormap without the numbers.
y_inpt = np.random.choice([0, 1], num_trials)
x_inpt = np.where(y_inpt == 0,
np.random.binomial(20, 0.5, num_trials),
np.random.binomial(20, 0.3, num_trials)) * 5
When plotting with Basemap's readshapefile, if the defined map is centered anywhere else than the longitudinal center of the shapefile, only a portion of it it's plotted. Here's an example using Natural Earth's coastlines:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
shpf = './NaturalEarth/ne_50m_land/ne_50m_land'
fig, ax = plt.subplots(nrows=1, ncols=1, dpi=100)
m = Basemap(
ax = ax,
projection = 'cyl',
llcrnrlon = 0, llcrnrlat = -90,
urcrnrlon = 360, urcrnrlat = 90
)
m.readshapefile(shpf,'ne_50m_land')
m.drawmeridians(np.arange(0,360,45),labels=[True,False,False,True])
Which produces:
Is there a workaround for this with Basemap or Python? I know some people re-center the shapefile in QGIS or similar, but it seems unpractical to do so every time you create a new map, and my QGIS skills are extremely basic.
One way to do it would be to tell readshapefile not to plot the coastlines directly and then to manipulate the line segments before plotting them yourself. Here an example based on your use case:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
shpf = 'shapefiles/ne_50m_land'
fig, ax = plt.subplots(nrows=1, ncols=1, dpi=100)
m = Basemap(
ax = ax,
projection = 'cyl',
llcrnrlon = 0, llcrnrlat = -90,
urcrnrlon = 360, urcrnrlat = 90
)
m.readshapefile(shpf,'ne_50m_land', drawbounds = False)
boundary = 0.0
for info, shape in zip(m.ne_50m_land_info, m.ne_50m_land):
lons, lats = map(np.array, zip(*shape))
sep = (lons <= boundary).astype(int)
roots = np.where(sep[:-1]+sep[1:] == 1)[0]+1
lower = np.concatenate([[0],roots]).astype(int)
upper = np.concatenate([roots,[len(lons)]]).astype(int)
for low, high in zip(lower,upper):
lo_patch = lons[low:high]
la_patch = lats[low:high]
lo_patch[lo_patch<0] += 360
x,y = m(lo_patch,la_patch)
ax.plot(x,y,'k',lw=0.5)
m.drawmeridians(np.arange(0,360,45),labels=[True,False,False,True])
plt.show()
In the example above, I iterate through the line segments of the shape file the way it is explained in the Basemap documentation. First I thought it would be enough to just add 360 to each point with a longitude smaller 0, but then you would get horizontal lines whenever a coast line crosses the 0 degree line. So, instead, one has to cut the lines into smaller segments whenever such a crossing appears. This is quite easily accomplished with numpy. I then use the plot command to draw the coast lines. If you want to do something more complex have a look at the Basemap documentation.
The final result looks like this:
Hope this helps.
I'm plotting wind speed as a contour plot, with latitude as the x co-ordinate and pressure as the y axis.
I want the y axis as a log scale, and this works as shown below
http://i.stack.imgur.com/aoSGN.png
How do I specify where the y ticks are, i.e at the moment it plots a tick at 1000mb, 100mb etc. How would I specify it so it plots ticks at say 1000,900,700,500,200mb etc.
I tried making an array of numbers and putting this in the ytick code but it didn't work!
Many thanks in advance.
PS Ideally it'd have a similar log tick scale as this here;
http://i.stack.imgur.com/dJCxB.jpg
Here is my code;
from netCDF4 import Dataset
import numpy as np
from matplotlib import pyplot as plt
myfile = '/home/ubuntu/Python-Postburn_Tools/out.nc'
Import = Dataset(myfile, mode='r')
print Import.variables #Display contents of NC File (i.e list variables)
print Import.variables['ua'] #Lists shape of variable e.g(time, lev, lat, lon)
lons = Import.variables['lon'][:] #Longitude
lats = Import.variables['lat'][:] #Latitude
time = Import.variables['time'][:] #Time
height = Import.variables['lev'][:] #Levels
wind = Import.variables['ua'][:]
#relax = Import.variables['var154'][:]
temp = Import.variables['ta'][:]
display = np.average(wind[12:131,:,:,0],axis=0) #Use this to plot a variable
#Visual changes
uscale = [-40,-35,-30,-25,-20,-15,-10,-5,0,5,10,15,20,25,30,35,40]
CS = plt.contourf(lats,height,display,uscale,cmap=plt.cm.jet)
cbar = plt.colorbar(CS)
cbar.ax.set_ylabel('Temperature (C)')
plt.gca().invert_yaxis()
plt.yscale('log', nonposy='clip')
plt.xticks(np.round((np.arange(-90, 91, 30)), 2))
plt.xlim(xmin=-90)
plt.xlabel('Latitude')
plt.ylabel('Pressure (hPa)')
plt.title('Year 2-11 control Temp - TRelax (C)')
plt.show()
Hi Im currently wishing to label my polar bar chart in the form whereby the labels are all rotating by differing amounts so they can be read easily much like a clock. I know there is a rotation in plt.xlabel however this will only rotate it by one amount I have many values and thus would like to not have them all crossing my graph.
This is figuratively what my graph is like with all the orientations in the same way, however I would like something akin to this; I really need this just using matplotlib and pandas if possible. Thanks in advance for the help!
Some example names might be farming, generalists, food and drink if these are not correctly rotated they will overlap the graph and be difficult to read.
from pandas import DataFrame,Series
import pandas as pd
import matplotlib.pylab as plt
from pylab import *
import numpy as np
data = pd.read_csv('/.../data.csv')
data=DataFrame(data)
N = len(data)
data1=DataFrame(data,columns=['X'])
data1=data1.get_values()
plt.figure(figsize=(8,8))
ax = plt.subplot(projection='polar')
plt.xlabel("AAs",fontsize=24)
ax.set_theta_zero_location("N")
bars = ax.bar(theta, data1,width=width, bottom=0.0,color=colours)
I would then like to label the bars according to their names which I can obtain in a list, However there are a number of values and i would like to be able to read the data names.
The very meager beginnings of an answer for you (I was doing something similar, so I just threw a quick hack to go in the right direction):
# The number of labels you'd like
In [521]: N = 5
# Where on the circle it will show up
In [522]: theta = numpy.linspace(0., 2 * numpy.pi, N + 1, endpoint = True)
In [523]: theta = theta[1:]
# Create the figure
In [524]: fig = plt.figure(figsize = (6,6), facecolor = 'white', edgecolor = None)
# Create the axis, notice polar = True
In [525]: ax = plt.subplot2grid((1, 1), (0,0), polar = True)
# Create white bars so you're really just focusing on the labels
In [526]: ax.bar(theta, numpy.ones_like(theta), align = 'center',
...: color = 'white', edgecolor = 'white')
# Create the text you're looking to add, here I just use numbers from counter = 1 to N
In [527]: counter = 1
In [528]: for t, o in zip(theta, numpy.ones_like(theta)):
...: ax.text(t, 1 - .1, counter, horizontalalignment = 'center', verticalalignment = 'center', rotation = t * 100)
...: counter += 1
In [529]: ax.set_yticklabels([])
In [530]: ax.set_xticklabels([])
In [531]: ax.grid(False)
In [531]: plt.show()