I am trying to plot NASA GISS gridded temperature data but my maps keep showing up blank. Below is my code:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import geopandas as gpd
import xarray as xr
ncin = xr.open_dataset('GriddedAir250.nc')
lons = ncin.variables['lon'][:]
lats = ncin.variables['lat'][:]
air = ncin.air
MeanTmax=air.mean(dim='time')
m=Basemap(projection='merc',
llcrnrlon= -123.416059,
llcrnrlat=18.954443,
urcrnrlon=-61.285950,
urcrnrlat= 47.536340,
resolution='i')
lon, lat = np.meshgrid(lons, lats)
xi, yi = m(lon, lat)
# Add Coastlines, States, and Country Boundaries
m.drawcoastlines()
m.drawstates()
m.drawcountries()
# Plot Data
cs = m.pcolor(xi,yi,np.squeeze(MeanTmax))
# Add Colorbar
cbar = m.colorbar(cs, location='bottom', pad="10%")
cbar.set_label('winter')
# Add Title
plt.title('DJF Maximum Temperature')
plt.show()
All I get is a blank map that looks like this. Why isn't the temperature data showing up?
The longitude grid in the source data is from 0 to 360 rather than -180 to 180. Because of this, it's likely that you've filtered out all of the data in your basemap projection command. I haven't tested because I don't have the deprecated basemap package.
Related
Wondering how I can plot a seaborn plot onto a different matplotlib plot. Currently I have two plots (one a heatmap, the other a soccer pitch), but when I plot the heatmap onto the pitch, I get the results below. (Plotting the pitch onto the heatmap isn't pretty either.) Any ideas how to fix it?
Note: Plots don't need a colorbar and the grid structure isn't required either. Just care about the heatmap covering the entire space of the pitch. Thanks!
import pandas as pd
import numpy as np
from mplsoccer import Pitch
import seaborn as sns
nmf_shot_W = pd.read_csv('https://raw.githubusercontent.com/lucas-nelson-uiuc/datasets/main/nmf_show_W.csv').iloc[:, 1:]
nmf_shot_ThierryHenry = pd.read_csv('https://raw.githubusercontent.com/lucas-nelson-uiuc/datasets/main/nmf_show_Hth.csv')['Thierry Henry']
pitch = Pitch(pitch_type='statsbomb', line_zorder=2,
pitch_color='#22312b', line_color='#efefef')
dfdfdf = np.array(np.matmul(nmf_shot_W, nmf_shot_ThierryHenry)).reshape((24,25))
g_ax = sns.heatmap(dfdfdf)
pitch.draw(ax=g_ax)
Current output:
Desired output:
Use the built-in pitch.heatmap:
pitch.heatmap expects a stats dictionary of binned data, bin mesh, and bin centers:
stats (dict) – The keys are statistic (the calculated statistic), x_grid and y_grid (the bin's edges), and cx and cy (the bin centers).
In the mplsoccer heatmap demos, they construct this stats object using pitch.bin_statistic because they have raw data. However, you already have binned data ("calculated statistic"), so reconstruct the stats object manually by building the mesh and centers:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from mplsoccer import Pitch
nmf_shot_W = pd.read_csv('71878281/nmf_show_W.csv', index_col=0)
nmf_shot_ThierryHenry = pd.read_csv('71878281/nmf_show_Hth.csv')['Thierry Henry']
statistic = np.dot(nmf_shot_W, nmf_shot_ThierryHenry.to_numpy()).reshape((24, 25))
# construct stats object from binned data, bin mesh, and bin centers
y, x = statistic.shape
x_grid = np.linspace(0, 120, x + 1)
y_grid = np.linspace(0, 80, y + 1)
cx = x_grid[:-1] + 0.5 * (x_grid[1] - x_grid[0])
cy = y_grid[:-1] + 0.5 * (y_grid[1] - y_grid[0])
stats = dict(statistic=statistic, x_grid=x_grid, y_grid=y_grid, cx=cx, cy=cy)
# use pitch.draw and pitch.heatmap as per mplsoccer demo
pitch = Pitch(pitch_type='statsbomb', line_zorder=2, pitch_color='#22312b', line_color='#efefef')
fig, ax = pitch.draw(figsize=(6.6, 4.125))
pcm = pitch.heatmap(stats, ax=ax, cmap='plasma')
cbar = fig.colorbar(pcm, ax=ax, shrink=0.6)
cbar.outline.set_edgecolor('#efefef')
cbar.ax.yaxis.set_tick_params(color='#efefef')
plt.setp(plt.getp(cbar.ax.axes, 'yticklabels'), color='#efefef')
I am trying to show a spatial distribution of shops on a map using Geopandas and Matplotlib.
Problem:
When I am plotting the pins the base map gets distorted. Here is a sample before plotting the pins and after .
Question:
What is the source of this distortion? How can I prevent it?
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
from shapely.geometry import Polygon
# Creating the simplified polygon
latitude = [60.41125, 59.99236, 59.99236]
longitude = [24.66917, 24.66917, 25.36972]
geometry = Polygon(zip(longitude, latitude))
polygon = gpd.GeoDataFrame(index=[0], crs = 'epsg:4326', geometry=[geometry])
# ploting the basemap
ax = polygon.plot(color="#3791CB")
# Dict of sample coordinates
coordinates = {"latitude": ["60.193141", "60.292777", "60.175053", "60.163187", "60.245272", "60.154392", "60.182906"],
"longitude": ["24.934214", "24.969730", "24.831068", "24.739044", "24.860983", "24.884773", "24.959175"]}
# Creating a dataframe from coordinates
df = pd.DataFrame(coordinates)
# Creating the GeoDataFrame
shops = gpd.GeoDataFrame(coordinates, geometry=gpd.points_from_xy(df.longitude, df.latitude))
# Plotting office coordinates
shops.plot(ax=ax, color="red", markersize = 20, zorder=2)
# adding grid
plt.grid(linestyle=":", color='grey')
plt.show()
Thank you!
You're map and pins have different reference systems..
When you create your first GeoDataFrame you specify its Coordinate Reference System (crs = 'epsg:4326'). When you create the geodataframe for the shop coordinates you don't. This is where the distortion is coming from..
This should fix it:
shops = gpd.GeoDataFrame(
coordinates,
geometry = gpd.points_from_xy(
df.longitude,
df.latitude),
crs = "EPSG:4326"
)
)
Cheers!
I am using the following code to plot an additional coordinate/point on top of a matplotlib plot of a geopandas dataframe. However, as the image indicates the point is not overlapping the choropleth - it should, since the latitude & longitude lies within the geographic location for which the data has been obtained from census. Please advise.
from cenpy import products
import pandas as pd
import matplotlib.pyplot as plt
import geopandas
%matplotlib inline
tustin = products.ACS(2018).from_county('Orange County, CA', level='tract',
variables=['B23025_005E', 'B23025_003E'])
tustin['pct_unemployed'] = tustin.B23025_005E / tustin.B23025_003E * 100
print(tustin.crs)
# additional data co-ordinate
lat = 3374569.5
lon = -11782886.0
df = pd.DataFrame(
{'Place': ['X'],
'Latitude': [lat],
'Longitude': [lon]})
gdf = geopandas.GeoDataFrame(
df, geometry=geopandas.points_from_xy(df.Longitude, df.Latitude))
# setting the same crs as the main geopandas dataframe
gdf.crs = {'init': 'epsg:3857'}
# plot the first dataframe 'tustin' as a choropleth
f, ax = plt.subplots(1,1,figsize=(20,20))
tustin.dropna(subset=['pct_unemployed'], axis=0).plot('pct_unemployed', ax=ax, cmap='plasma')
ax.set_facecolor('k')
gdf.plot(ax=ax, color='red')
#ax.plot(-13144890.450, 3992372.350, "ro")
plt.show()
enter image description here
This question already has answers here:
Plot only on continent in matplotlib
(5 answers)
Closed 5 years ago.
I am trying to plot 1x1 degree data on a matplotlib.Basemap, and I want to fill the ocean with white. However, in order for the boundaries of the ocean to follow the coastlines drawn by matplotlib, the resolution of the white ocean mask should be much higher than the resolution of my data.
After searching around for a long time I tried the two possible solutions:
(1) maskoceans() and is_land() functions, but since my data is lower resolution than the map drawn by basemap it does not look good on the edges. I do not want to interpolate my data to higher resolution either.
(2) m.drawlsmask(), but since zorder cannot be assigned the pcolormesh plot always overlays the mask.
This code
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.basemap as bm
#Make data
lon = np.arange(0,360,1)
lat = np.arange(-90,91,1)
data = np.random.rand(len(lat),len(lon))
#Draw map
plt.figure()
m = bm.Basemap(resolution='i',projection='laea', width=1500000, height=2900000, lat_ts=60, lat_0=72, lon_0=319)
m.drawcoastlines(linewidth=1, color='white')
data, lon = bm.addcyclic(data,lon)
x,y = m(*np.meshgrid(lon,lat))
plt.pcolormesh(x,y,data)
plt.savefig('1.png',dpi=300)
Produces this image:
Adding m.fillcontinents(color='white') produces the following image, which is what I need but to fill the ocean and not the land.
Edit:
m.drawmapboundary(fill_color='lightblue') also fills over land and can therefore not be used.
The desired outcome is that the oceans are white, while what I plotted with plt.pcolormesh(x,y,data) shows up over the lands.
I found a much nicer solution to the problem which uses the polygons defined by the coastlines in the map to produce a matplotlib.PathPatch that overlays the ocean areas. This solution has a much better resolution and is much faster:
from matplotlib import pyplot as plt
from mpl_toolkits import basemap as bm
from matplotlib import colors
import numpy as np
import numpy.ma as ma
from matplotlib.patches import Path, PathPatch
fig, ax = plt.subplots()
lon_0 = 319
lat_0 = 72
##some fake data
lons = np.linspace(lon_0-60,lon_0+60,10)
lats = np.linspace(lat_0-15,lat_0+15,5)
lon, lat = np.meshgrid(lons,lats)
TOPO = np.sin(np.pi*lon/180)*np.exp(lat/90)
m = bm.Basemap(resolution='i',projection='laea', width=1500000, height=2900000, lat_ts=60, lat_0=lat_0, lon_0=lon_0, ax = ax)
m.drawcoastlines(linewidth=0.5)
x,y = m(lon,lat)
pcol = ax.pcolormesh(x,y,TOPO)
##getting the limits of the map:
x0,x1 = ax.get_xlim()
y0,y1 = ax.get_ylim()
map_edges = np.array([[x0,y0],[x1,y0],[x1,y1],[x0,y1]])
##getting all polygons used to draw the coastlines of the map
polys = [p.boundary for p in m.landpolygons]
##combining with map edges
polys = [map_edges]+polys[:]
##creating a PathPatch
codes = [
[Path.MOVETO] + [Path.LINETO for p in p[1:]]
for p in polys
]
polys_lin = [v for p in polys for v in p]
codes_lin = [c for cs in codes for c in cs]
path = Path(polys_lin, codes_lin)
patch = PathPatch(path,facecolor='white', lw=0)
##masking the data:
ax.add_patch(patch)
plt.show()
The output looks like this:
Original solution:
You can use an array with greater resolution in basemap.maskoceans, such that the resolution fits the continent outlines. Afterwards, you can just invert the mask and plot the masked array on top of your data.
Somehow I only got basemap.maskoceans to work when I used the full range of the map (e.g. longitudes from -180 to 180 and latitudes from -90 to 90). Given that one needs quite a high resolution to make it look nice, the computation takes a while:
from matplotlib import pyplot as plt
from mpl_toolkits import basemap as bm
from matplotlib import colors
import numpy as np
import numpy.ma as ma
fig, ax = plt.subplots()
lon_0 = 319
lat_0 = 72
##some fake data
lons = np.linspace(lon_0-60,lon_0+60,10)
lats = np.linspace(lat_0-15,lat_0+15,5)
lon, lat = np.meshgrid(lons,lats)
TOPO = np.sin(np.pi*lon/180)*np.exp(lat/90)
m = bm.Basemap(resolution='i',projection='laea', width=1500000, height=2900000, lat_ts=60, lat_0=lat_0, lon_0=lon_0, ax = ax)
m.drawcoastlines(linewidth=0.5)
x,y = m(lon,lat)
pcol = ax.pcolormesh(x,y,TOPO)
##producing a mask -- seems to only work with full coordinate limits
lons2 = np.linspace(-180,180,10000)
lats2 = np.linspace(-90,90,5000)
lon2, lat2 = np.meshgrid(lons2,lats2)
x2,y2 = m(lon2,lat2)
pseudo_data = np.ones_like(lon2)
masked = bm.maskoceans(lon2,lat2,pseudo_data)
masked.mask = ~masked.mask
##plotting the mask
cmap = colors.ListedColormap(['w'])
pcol = ax.pcolormesh(x2,y2,masked, cmap=cmap)
plt.show()
The result looks like this:
I'm trying to use the Basemap function to create a plot like the one shown here, but using this data.
This is my code:
west, south, east, north = -74.26, 40.50, -73.70, 40.92
fig = plt.figure(figsize=(14,10))
m = Basemap(projection='merc', llcrnrlat=south, urcrnrlat=north,
llcrnrlon=west, urcrnrlon=east, lat_ts=south, resolution='c')
x, y = m(df['pickup_longitude'].values, df['pickup_latitude'].values)
m.hexbin(x, y, gridsize=1900, cmap=cm.YlOrRd_r)
However, my result is nothing but weird.
I'm wondering what I'm missing.
Thanks.
It seems the data comprises much more data than in the range inside the Basemap plot.
You will get the desired plot by using a lot more gridpoints, e.g. gridsize=10000. This will however cost a lot of memory.
A better option would probably be to first select from the dataframe those values that are in the range to be shown in the map.
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib import cm
df = pd.read_csv("train.csv")
west, south, east, north = -74.26, 40.50, -73.70, 40.92
df = df[(df['pickup_longitude'] > west) & (df['pickup_longitude'] < east)]
df = df[(df['pickup_latitude'] > south) & (df['pickup_latitude'] < north)]
fig = plt.figure(figsize=(14,8))
m = Basemap(projection='merc', llcrnrlat=south, urcrnrlat=north,
llcrnrlon=west, urcrnrlon=east, lat_ts=south, resolution='c')
x, y = m(df['pickup_longitude'].values, df['pickup_latitude'].values)
m.hexbin(x, y, gridsize=100, bins='log', cmap=cm.YlOrRd_r, lw=0.4)
plt.show()
Using a more gridpoints then allows for even finer resolution. E.g. gridsize=1000: