plotting 'rworldmap' shapefile in ggplot2 across pacific ocean - ggplot2

I'm having issues making a plot in ggplot using some raster data I've gathered. I'll simulate the raster data as a dataframe here:
# Set up coordinates #
lon <- seq(120, 290, 1)
lat <- seq(-30, 30, 1)
r1 <- data.frame(
"lon" = rep(lon, length(lat)),
"lat" = rep(lat, each = length(lon))
)
# Add variable #
set.seed(2022)
r1$var <- rnorm(n = nrow(r1), 0, 1)
# Plot raster #
library(ggplot2)
p1 <- ggplot(r1, aes(x = lon, y = lat, fill = var))+
geom_raster()
p1
The issue I'm having is when I try to add a shapefile (specifically from rworldmap) to this plot. Because the data is projected in longitudes between -180 and 180 (instead of 0 to 360), it's unable to plot anything east of 180 East.
library(rworldmap)
library(sf)
# Download Shapefile #
world.shp <- getMap(resolution = 'low')
world.shp <- st_as_sf(world.shp)
# Plot shapefile on top of raster data #
p2 <- ggplot()+
geom_raster(data = r1, aes(x = lon, y = lat, fill = var))+
geom_sf(data = world.shp)+
coord_sf(xlim = c(120, 290), ylim = c(-30, 30), expand = TRUE)
p2
Notice how only Australia plots, when we should also be getting South America, Latin America, and North America.
I've tried many different strategies to reproject the rworldmap shapefile (world.shp), from defining a crs in st_as_sf() to specifying a crs in the coord_sf() argument. However, I've had no no success. The solution seems very simple, but I can't seem to find it. Any help with this would be greatly appreciated.
Cheers,

Related

Plotly does not properly show axis numbers with math_format

The code below indicates that, while using math_format command in ggplot 'labels', the plot displays well if ggplot is used, but it fails if it is displayed through plotly. I need to use plotly in my code. Does somebody have some suggestion?
library(tidyverse)
library(scales)
library(plotly)
p <- mtcars %>% ggplot(aes(x=mpg, y=disp))+
geom_point() +
scale_x_continuous(trans = log_trans(),
breaks = trans_breaks("log", function(x) exp(x), n.breaks = 5),
labels = trans_format("log", math_format(e^.x, format = function(x) number(x, accuracy = 0.01, decimal.mark = ','))))
p
ggplotly(p)

How to plot a map of a semi-sphere (eg northern hemisphere) using matplotlib cartopy

How to plot a map of a semi-sphere (eg northern hemisphere) using cartopy.
I'm trying to plot a map of the northern hemisphere using cartopy. But I don't understand how should I define the extent of the map so that only this region of interest is plotted. I would like the map to be cut off at 0° latitude. I would like to have code where I could easily define any subset of the glob using the ccrs.NearsidePerspective projection, or the ccrs.Orthographic projection.
Below I leave a code for reproduction.
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
# Creating fake data
x = np.linspace(-180, 180, 361)
y = np.linspace(-90, 90, 181)
lon, lat = np.meshgrid(x, y)
values = np.random.random(lon.shape)*20
fig = plt.figure(figsize=(15, 10))
proj = ccrs.NearsidePerspective(central_longitude=-45, central_latitude=21)
ax = fig.add_subplot(121, projection=proj)
ax.set_extent([-120, 40, 0, 60])
ax.pcolormesh(lon, lat, values, transform=ccrs.PlateCarree())
ax.coastlines(linewidth=2)
gl = ax.gridlines(draw_labels=True, linestyle='--')
The code generates the following figure:
Thank you very much in advance.
Robson
To plot only the upper hemisphere part of the map projection, a polygon of that part is needed to use as the projection boundary.
That polygon is created as a matplotlib-path object. It vertices' coordinates are data coordinates in my code, so that, no transformation is required when applied to the final plot.
This is a complete code:-
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import matplotlib.path as mpath
import numpy as np
from geographiclib.geodesic import Geodesic
fig = plt.figure(figsize=[12, 12])
proj = ccrs.NearsidePerspective(central_longitude=-45, central_latitude=21, satellite_height=35785831)
ax = plt.subplot(projection=proj)
# The value of r is obtained by previous run of this code ...
# with the line .. #print(ax.get_xlim()) uncommented
r = 5476336.098
ax.set_xlim(-r, r)
ax.set_ylim(-r, r)
ax.stock_img()
ax.coastlines(lw=1, color="darkblue")
# Find the locations of points along the equatorial arc
# start location
lon_fr, lat_fr = 30, 0
# end location
lon_to, lat_to = -120, 0
# This gets geodesic between the two points, WGS84 ellipsoid is used
geodl = Geodesic.WGS84.InverseLine(lat_fr, lon_fr, lat_to, lon_to)
lonlist, latlist = [], []
num_points = 32 #for series of points on geodesic/equator
for ea in np.linspace(0, geodl.s13, num_points):
g = geodl.Position(ea, Geodesic.STANDARD | Geodesic.LONG_UNROLL)
#print("{:.0f} {:.5f} {:.5f} {:.5f}".format(g['s12'], g['lat2'], g['lon2'], g['azi2']))
lon2, lat2 = g['lon2'], g['lat2']
lonlist.append( g['lon2'] )
latlist.append( g['lat2'] )
# Get data-coords from (lonlist, latlist)
# .. as points along equatorial arc
dataxy = proj.transform_points(ccrs.PlateCarree(), np.array(lonlist), np.array(latlist))
# (Uncomment to) Plot equator line
#ax.plot(dataxy[:, 0:1], dataxy[:, 1:2], "go-", linewidth=2, markersize=5, zorder=10)
# Top semi-circle arc for map extent
theta = np.linspace(-0.5*np.pi, 0.5*np.pi, 64)
center, radius = [0, 0], r
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
# Combine vertices of the semi-circle and equatorial arcs
# These points are in data coordinates, ready to plot on the axes.
verts = np.vstack([verts*r, dataxy[:, 0:2]])
polygon = mpath.Path(verts + center)
ax.set_boundary(polygon) #This masks-out unwanted part of the plot
gl = ax.gridlines(draw_labels=True, xlocs=range(-150,180,30), ylocs=range(0, 90, 15),
y_inline=True, linestyle='--', lw= 5, color= "w", )
# Get limits, the values are the radius of the circular map extent
# The values is then used as r = 5476336.09797 on top of the code
#print(ax.get_xlim())
#print(ax.get_ylim())
plt.show()

ggplot multiple densities with common density

I would like to plot something that is "between" a histogram and a density plot. Here is an example:
library(ggplot2)
set.seed(1)
f1 <- rep(1, 100)
v1 <- rnorm(100)
df1 <- data.frame(f1, v1)
f1 <- rep(2, 10)
v1 <- (rnorm(10)+1*2)
df2 <- data.frame(f1, v1)
df <- rbind(df1, df2)
df$f1 <- as.factor(df$f1)
ggplot(df, aes(x = v1, colour = f1)) +
geom_density(position="identity", alpha = 0.6, fill = NA, size = 1)
You will see that the area under each curve is 1.0, which is OK for a density. BUT notice that the second distribution is made up of just 10 observations rather than the 100 of the first. What I would like is that the area under curve 2 reflects this, e.g. is a tenth of that of curve 1. Thanks.
There is a computed variable for stat_density that you can use, called count.
ggplot(df, aes(x = v1, colour = f1)) +
geom_density(position="identity", alpha = 0.6, fill = NA, size = 1,
aes(y = after_stat(count)))
Note for ggplot2 <3.3.0 use stat(count) instead of after_stat(count).
You can find these tricks in the documentation of ?geom_density() under the section "Computed Variables".

How to adjust plot areas in ggplot?

I am trying to use grid.arrange to display multiple graphs on the same page generated by ggplot.Each subplot with difference x and y scale. Two subplot share the legend. My perpose is that to display the plot areas same size. Are there parameter to adjust plot area ( except legend area)? The facet is inadequate to arrange it.
df <- data.frame(class=paste0('a',1:20),
x1=runif(20),
x2=runif(20),
y1=runif(20),
y2=runif(20))
p1 <- ggplot(df,aes(x=x1,y=y1))+
geom_point(aes(color=class),size=2,show.legend=TRUE)+
stat_smooth(method='lm',color='black')+
theme_bw()
p2 <- ggplot(df,aes(x=x2,y=y2))+
geom_point(aes(color=class),size=2,show.legend=FALSE)+
stat_smooth(method='lm',color='black')+
theme_bw()
grid.arrange(p1,p2,nrow=2)
Using patchwork package
# install.packages("devtools", dependencies = TRUE)
# devtools::install_github("thomasp85/patchwork")
library(patchwork)
p1 / p2 + plot_annotation(title = "Plot title",
subtitle = "Plot subtitle",
tag_levels = 'A',
tag_suffix = ')')
Created on 2018-11-20 by the reprex package (v0.2.1.9000)

Plotting Lat/Long Points Using Basemap

I am trying to plot points on a map using matplotlib and Basemap, where the points represent the lat/long for specific buildings. My map does indeed plot the points, but puts them in the wrong location. When I use the same data and do the same thing using Bokeh, instead of matplotlib and basemap, I get the correct plot.
Here is the CORRECT result in Bokeh:
Bokeh Version
And here is the INCORRECT result in Basemap:
Basemap Version
I have seen discussion elsewhere on StackOverflow that suggested this might be related to the fact that plot() "shifts" the longitude somehow. I've tried the suggestion from there, which was to include the line:
lons, lats = m.shiftdata(long, lat)
and then use the shifted data. That didn't have any visible impact.
My full sample code which generates both of the plots in Basemap and Bokeh is here:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.sampledata.us_states import data as states
from bokeh.models import ColumnDataSource, Range1d
# read in data to use for plotted points
buildingdf = pd.read_csv('buildingdata.csv')
lat = buildingdf['latitude'].values
long = buildingdf['longitude'].values
# determine range to print based on min, max lat and long of the data
margin = .2 # buffer to add to the range
lat_min = min(lat) - margin
lat_max = max(lat) + margin
long_min = min(long) - margin
long_max = max(long) + margin
# create map using BASEMAP
m = Basemap(llcrnrlon=long_min,
llcrnrlat=lat_min,
urcrnrlon=long_max,
urcrnrlat=lat_max,
lat_0=(lat_max - lat_min)/2,
lon_0=(long_max-long_min)/2,
projection='merc',
resolution = 'h',
area_thresh=10000.,
)
m.drawcoastlines()
m.drawcountries()
m.drawstates()
m.drawmapboundary(fill_color='#46bcec')
m.fillcontinents(color = 'white',lake_color='#46bcec')
# convert lat and long to map projection coordinates
lons, lats = m(long, lat)
# plot points as red dots
m.scatter(lons, lats, marker = 'o', color='r')
plt.show()
# create map using Bokeh
source = ColumnDataSource(data = dict(lat = lat,lon = long))
# get state boundaries
state_lats = [states[code]["lats"] for code in states]
state_longs = [states[code]["lons"] for code in states]
p = figure(
toolbar_location="left",
plot_width=1100,
plot_height=700,
)
# limit the view to the min and max of the building data
p.y_range = Range1d(lat_min, lat_max)
p.x_range = Range1d(long_min, long_max)
p.xaxis.visible = False
p.yaxis.visible = False
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.patches(state_longs, state_lats, fill_alpha=0.0,
line_color="black", line_width=2, line_alpha=0.3)
p.circle(x="lon", y="lat", source = source, size=4.5,
fill_color='red',
line_color='grey',
line_alpha=.25
)
show(p)
I don't have enough reputation points to post a link to the data or to include it here.
In the basemap plot the scatter points are hidden behind the fillcontinents. Removing the two lines
#m.drawmapboundary(fill_color='#46bcec')
#m.fillcontinents(color = 'white',lake_color='#46bcec')
would show you the points. Because this might be undesired, the best solution would be to place the scatter on top of the rest of the map by using the zorder argument.
m.scatter(lons, lats, marker = 'o', color='r', zorder=5)
Here is the complete code (and I would like to ask you to include this kind of runnable minimal example with hardcoded data next time asking a question, as it saves everyone a lot of work inventing the data oneself):
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import pandas as pd
import io
u = u"""latitude,longitude
42.357778,-71.059444
39.952222,-75.163889
25.787778,-80.224167
30.267222, -97.763889"""
# read in data to use for plotted points
buildingdf = pd.read_csv(io.StringIO(u), delimiter=",")
lat = buildingdf['latitude'].values
lon = buildingdf['longitude'].values
# determine range to print based on min, max lat and lon of the data
margin = 2 # buffer to add to the range
lat_min = min(lat) - margin
lat_max = max(lat) + margin
lon_min = min(lon) - margin
lon_max = max(lon) + margin
# create map using BASEMAP
m = Basemap(llcrnrlon=lon_min,
llcrnrlat=lat_min,
urcrnrlon=lon_max,
urcrnrlat=lat_max,
lat_0=(lat_max - lat_min)/2,
lon_0=(lon_max-lon_min)/2,
projection='merc',
resolution = 'h',
area_thresh=10000.,
)
m.drawcoastlines()
m.drawcountries()
m.drawstates()
m.drawmapboundary(fill_color='#46bcec')
m.fillcontinents(color = 'white',lake_color='#46bcec')
# convert lat and lon to map projection coordinates
lons, lats = m(lon, lat)
# plot points as red dots
m.scatter(lons, lats, marker = 'o', color='r', zorder=5)
plt.show()