Why will geom_tile plot a subset of my data, but not more? - r

I am trying to plot a map, but I can not figure out why the following will not work:
Here is a minimal example
testdf <- structure(list(x = c(48.97, 44.22, 44.99, 48.87, 43.82, 43.16, 38.96, 38.49, 44.98, 43.9), y = c(-119.7, -113.7, -109.3, -120.6, -109.6, -121.2, -114.2, -118.9, -109.7, -114.1), z = c(0.001216, 0.001631, 0.001801, 0.002081, 0.002158, 0.002265, 0.002298, 0.002334, 0.002349, 0.00249)), .Names = c("x", "y", "z"), row.names = c(NA, 10L), class = "data.frame")
This works for 1-8 rows:
ggplot(data = testdf[1,], aes(x,y,fill = z)) + geom_tile()
ggplot(data = testdf[1:8,], aes(x,y,fill = z)) + geom_tile()
But not for 9 rows:
ggplot(data = testdf[1:9,], aes(x,y,fill = z)) + geom_tile()
Ultimately, I am seeking a way to plot data on a non-regular grid. It is not essential that I use geom_tile, but any space-filling interpolation over the points will do.
The full dataset is available as a gist
testdf above was a small subset of the full dataset, a high-resolution raster of the US (>7500 rows)
require(RCurl) # requires libcurl; sudo apt-get install libcurl4-openssl-dev
tmp <- getURL("https://gist.github.com/raw/4635980/f657dcdfab7b951c7b8b921b3a109c7df1697eb8/test.csv")
testdf <- read.csv(textConnection(x))
What I have tried:
using geom_point works, but does not have the desired effect:
ggplot(data = testdf, aes(x,y,color=z)) + geom_point()
if I convert either x or y to a vector 1:10, the plot works as expected:
newdf <- transform(testdf, y =1:10)
ggplot(data = newdf[1:9,], aes(x,y,fill = z)) + geom_tile()
newdf <- transform(testdf, x =1:10)
ggplot(data = newdf[1:9,], aes(x,y,fill = z)) + geom_tile()
sessionInfo()R version 2.15.2 (2012-10-26) Platform: x86_64-pc-linux-gnu (64-bit)
> attached base packages: [1] stats graphics grDevices utils
> datasets methods base
> other attached packages: [1] reshape2_1.2.2 maps_2.3-0
> betymaps_1.0 ggmap_2.2 ggplot2_0.9.3
> loaded via a namespace (and not attached): [1] colorspace_1.2-0
> dichromat_1.2-4 digest_0.6.1 grid_2.15.2
> gtable_0.1.2 labeling_0.1 [7] MASS_7.3-23
> munsell_0.4 plyr_1.8 png_0.1-4
> proto_0.3-10 RColorBrewer_1.0-5 [13] RgoogleMaps_1.2.0.2
> rjson_0.2.12 scales_0.2.3 stringr_0.6.2
> tools_2.15.2

The reason you can't use geom_tile() (or the more appropriate geom_raster() is because these two geoms rely on your tiles being evenly spaced, which they are not. You will need to coerce your data to points, and resample these to an evenly spaced raster which you can then plot with geom_raster(). You will have to accept that you will need to resample your original data slightly in order to plot this as you wish.
You should also read up on raster:::projection and rgdal:::spTransform for more information on map projections.
require( RCurl )
require( raster )
require( sp )
require( ggplot2 )
tmp <- getURL("https://gist.github.com/geophtwombly/4635980/raw/f657dcdfab7b951c7b8b921b3a109c7df1697eb8/test.csv")
testdf <- read.csv(textConnection(tmp))
spdf <- SpatialPointsDataFrame( data.frame( x = testdf$y , y = testdf$x ) , data = data.frame( z = testdf$z ) )
# Plotting the points reveals the unevenly spaced nature of the points
spplot(spdf)
# You can see the uneven nature of the data even better here via the moire pattern
plot(spdf)
# Make an evenly spaced raster, the same extent as original data
e <- extent( spdf )
# Determine ratio between x and y dimensions
ratio <- ( e#xmax - e#xmin ) / ( e#ymax - e#ymin )
# Create template raster to sample to
r <- raster( nrows = 56 , ncols = floor( 56 * ratio ) , ext = extent(spdf) )
rf <- rasterize( spdf , r , field = "z" , fun = mean )
# Attributes of our new raster (# cells quite close to original data)
rf
class : RasterLayer
dimensions : 56, 135, 7560 (nrow, ncol, ncell)
resolution : 0.424932, 0.4248191 (x, y)
extent : -124.5008, -67.13498, 25.21298, 49.00285 (xmin, xmax, ymin, ymax)
# We can then plot this using `geom_tile()` or `geom_raster()`
rdf <- data.frame( rasterToPoints( rf ) )
ggplot( NULL ) + geom_raster( data = rdf , aes( x , y , fill = layer ) )
# And as the OP asked for geom_tile, this would be...
ggplot( NULL ) + geom_tile( data = rdf , aes( x , y , fill = layer ) , colour = "white" )
Of course I should add that this data is quite meaningless. What you really must do is take the SpatialPointsDataFrame, assign the correct projection information to it, and then transform to latlong coordinates via spTransform and then rasterzie the transformed points. Really you need to have more information about your raster data. What you have here is a close approximation, but ultimately it is not a true reflection of the data.

This will not be answer to geom_tile() problem but another way to plot data.
As you have x and y coordinates of 30 km grid (I assume middle of that grid) then you can used geom_point() and plot data. You should select appropriate shape= value. Shape 15 will plot rectangles.
Another problem is x and y values - when plotting data they should be plotted as x=y and y=x to correspond to latitude and longitude.
coord_equal() will ensure that there is a correct aspect ratio (I found this solution with ratio as example on net).
ggplot(data = testdf, aes(y,x,colour=z)) + geom_point(shape=15)+
coord_equal(ratio=1/cos(mean(testdf$x)*pi/180))

answer:
data is plotted but is just very small.
From here:
"Tile plot as densely as possible, assuming that every tile is the same size.
Consider this plot
ggplot(data = testdf[1:2,], aes(x,y,fill = z)) + geom_tile()
There are two tiles in the plot above. geom_tile is trying to make the plot as dense as possible considering that every tile is the same size. Here we can make two tiles this big without overlapping. making enough space for 4 tiles.
Have a go at the following plots and see what the resulting plots tell you:
df1 <- data.frame(x=c(1:3),y=(1:3))
# df1
# x y
#1 1 1
#2 2 2
#3 3 3
ggplot(data = df1[1,], aes(x,y)) + geom_tile()
ggplot(data = df1[1:2,], aes(x,y)) + geom_tile()
ggplot(data = df1[1:3,], aes(x,y)) + geom_tile()
compare to this example:
df2 <- data.frame(x=c(1:3),y=c(1,20,300))
df2
# x y
#1 1 1
#2 2 20
#3 3 300
ggplot(data = df2[1,], aes(x,y)) + geom_tile()
ggplot(data = df2[1:2,], aes(x,y)) + geom_tile()
ggplot(data = df2[1:3,], aes(x,y)) + geom_tile()
Note that for the first two plots are same for df1 and df2 but the third plot for df2 is different. This is because the biggest we can make the tiles is between (x[1],y[1]) and (x[2],y[2]). Any more and they would overlap which leaves lots of space between these two tiles and the last 3rd tile at y=300.
There is also a width parameter in geom_tile although I am not sure how sensible this is here. are you sure you dont fancy another option with such sparse data ?
(Your full data is still plotted: see ggplot(data = testdf, aes(x,y)) + geom_tile(width=1000)

If you want to use geom_tile I think you will need to aggregate first:
# NOTE: tmp.csv downloaded from https://gist.github.com/geophtwombly/4635980/raw/f657dcdfab7b951c7b8b921b3a109c7df1697eb8/test.csv
testdf <- read.csv("~/Desktop/tmp.csv")
# combine x,y coordinates by rounding
testdf$x2 <- round(testdf$x, digits=0)
testdf$y2 <- round(testdf$y, digits=0)
# aggregate on combined coordinates
library(plyr)
testdf <- ddply(testdf, c("x2", "y2"), summarize,
z = mean(z))
# plot aggregated data using geom_tile
ggplot(data = testdf, aes(y2,x2,fill=z)) +
geom_tile() +
coord_equal(ratio=1/cos(mean(testdf$x2)*pi/180)) # copied from #Didzis Elferts answer--nice!
Once we have done all this we will probably conclude that geom_point() is better, as suggested by #Didzis Elferts.

Related

ggplot gridded SpatialPolygonsDataFrame producing odd triangles and subsetting data based on point data

Using the code below I can plot the following:
This code is adapted from here
As you can see there are few issues with the plot. I am struggling to
Remove weird lines in plot
Only plot cells (grids) where there are data
Plot ID (see gridSpatialPolygons$values) on top of the grid cell
I realise there are a few points to this question but I hope one solution solves all.
# Load libraries
library(sp)
library(raster)
library(ggplot2)
# Projection
wgs.84 <- CRS("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0")
# Load data
x <- c(76.82973, 76.82972, 76.82969, 76.83076, 76.83075, 76.83071, 76.83129, 76.83126, 76.83125)
y <- c(28.26734, 28.26644, 28.26508, 28.26778, 28.26733, 28.26507, 28.26912, 28.26732, 28.26687)
z <- c(-56.7879, -58.22462, -58.4211, -55.75333, -58.55153, -56.38619, -56.11011, -58.17415, -59.77212)
# Create data frame
dataset <- data.frame("LONGITUDE" = x, "LATITUDE" = y, "VALUES" = z)
# Create SpatialPointsDataFrame object
datasetSP <- SpatialPointsDataFrame(coords = dataset[,c(1,2)], data = data.frame("id" = 1:nrow(dataset), "values" = dataset$VALUES), proj4string = wgs.84)
# Extent
extentDatasetSP <-extent(datasetSP)
# Make grid options
# Cell size (map units)
xCellSizeGrid <- 0.001
yCellSizeGrid <- 0.001
# Grid
grid <- GridTopology(cellcentre.offset = c(extentDatasetSP#xmin, extentDatasetSP#ymin),
cellsize = c(xCellSizeGrid, yCellSizeGrid),
cells.dim = c(3, 7))
# Create SpatialGrid object
gridSpatial <- SpatialGrid(grid = grid, proj4string = wgs.84)
# Convert to SpatialPixels object
gridSpatialPixels <- as(gridSpatial, "SpatialPixels")
# Convert to SpatialPolygons object
gridSpatialPolygons <- as(gridSpatialPixels, "SpatialPolygons")
# Add 'id' and 'values' to every polygon
gridSpatialPolygons$id <- 1:nrow(coordinates(gridSpatialPolygons))
gridSpatialPolygons$values <- paste("Gridvalue", 1:nrow(coordinates(gridSpatialPolygons)), sep = ":")
# Get attributes from polygons
samplePointsInPolygons2 <- datasetSP %over% gridSpatialPolygons
ggplot(gridSpatialPolygons, aes(x = long, y = lat)) +
geom_polygon(color = "red") +
geom_point(data = dataset,
aes(x = LONGITUDE,
y = LATITUDE))
When it comes to spatial objects, ggplot2 (and tidyverse in general) seems to play nicer with sf than sp. The advice below is taken from one of the help files in the associated broom package:
Note that the sf package now defines tidy spatial objects and is the
recommended approach to spatial data. sp tidiers are likely to be
deprecated in the near future in favor of sf::st_as_sf(). Development
of sp tidiers has halted in broom.
Things should be fairly straightforward after conversion to sf.
library(dplyr)
sf::st_as_sf(gridSpatialPolygons) %>%
filter(id %in% samplePointsInPolygons2$id) %>% # keep only grid cells with data
ggplot() +
geom_sf(colour = "red") +
geom_sf_text(aes(label = values), # label cells
nudge_y = 0.0003, colour = "grey40") +
geom_point(data = dataset,
aes(x = LONGITUDE,
y = LATITUDE))

Get multiple polygons for scattered data in R

I have point cloud data of an area (x,y,z coordinates)
The plot of X and Y looks like:
I am trying to get polygons of different clusters in this data. I tried the following:
points <- df [,1:2] # x and y coordinates
pts <- st_as_sf(points, coords=c('X','Y'))
conc <- concaveman(pts, concavity = 0.5, length_threshold = 0)
Seems like I just get a single polygon binding the whole data. conc$polygons is a list of one variable.
How can I define multiple polygons? What am I missing when I am using concaveman and what all it can provide?
It's hard to tell from your example what variable defines your clusters. Below is an example with some simulated clusters using ggplot2 and data.table (adapted from here).
library(data.table)
library(ggplot2)
# Simulate data:
set.seed(1)
n_cluster = 50
centroids = cbind.data.frame(
x=rnorm(5, mean = 0, sd=5),
y=rnorm(5, mean = 0, sd=5)
)
dt = rbindlist(
lapply(
1:nrow(centroids),
function(i) {
cluster_dt = data.table(
x = rnorm(n_cluster, mean = centroids$x[i]),
y = rnorm(n_cluster, mean = centroids$y[i]),
cluster = i
)
}
)
)
dt[,cluster:=as.factor(cluster)]
# Find convex hull of each point by cluster:
hulls = dt[,.SD[chull(x,y)],by=.(cluster)]
# Plot:
p = ggplot(data = dt, aes(x=x, y=y, colour=cluster)) +
geom_point() +
geom_polygon(data = hulls,aes(fill=cluster,alpha = 0.5)) +
guides(alpha=F)
This produces the following output:
Edit
If you don't have predefined clusters, you can use a clustering algorithm. As a simple example, see below for a solution using kmeans with 5 centroids.
# Estimate clusters (e.g. kmeans):
dt[,km_cluster := as.factor(kmeans(.SD,5)$cluster),.SDcols=c("x","y")]
# Find convex hull of each point:
hulls = dt[,.SD[chull(x,y)],by=.(km_cluster)]
# Plot:
p = ggplot(data = dt, aes(x=x, y=y, colour=km_cluster)) +
geom_point() +
geom_polygon(data = hulls,aes(fill=km_cluster,alpha = 0.5)) +
guides(alpha=F)
In this case the output for the estimated clusters is almost equivalent to the constructed ones.

Overlay raster layer on map in ggplot2 in R?

I am trying to overlay a raster layer onto a map in ggplot. The raster layer contains likelihood surfaces for each time point from a satellite tag. I also want to set cumulative probabilities(95%, 75%, 50%) on the raster layer.
I have figured out how to show the raster layer on the ggplot map, but the coordinates are not aligned with one another. I tried making each have the same projection but it does not seem to be working... I want them both to fit the boundaries of my model (xmin = 149, xmax = 154, ymin = -14, ymax = -8.75
Attached is my r code and the figure result:
#load data
ncname <- "152724-13-GPE3"
ncfname <- paste(ncname, ".nc", sep = "")
ncin <- nc_open(ncfname)
StackedObject<-stack("152724-13-GPE3.nc", varname = "monthly_residency_distributions")
MergedObject<-overlay(StackedObject,fun=mean )
MergedObject[is.na(MergedObject)]<-0
Boundaries<-extent(c(149, 154, -14, -8.75))
ExtendedObject<-extend(MergedObject, Boundaries)
Raster.big<-raster(ncol=1200,nrow=900,ext=Boundaries)
Raster.HR<-resample(x=ExtendedObject, y=Raster.big, method="bilinear")
Raster.HR#data#values<- Raster.HR#data#values/sum(Raster.HR#data#values)
RasterVals<-sort(Raster.HR#data#values)
Raster.breaks <- c(RasterVals[max(which(cumsum(RasterVals)<= 0.05 ))], RasterVals[max(which(cumsum(RasterVals)<= 0.25 ))], RasterVals[max(which(cumsum(RasterVals)<= 0.50 ))], 1)
Raster.cols<-colorRampPalette(c("yellow","orange","red"))
RasterCols<- c(Raster.cols(3))
#Create Map
shape2 <- readOGR(dsn = "/Users/shannonmurphy/Desktop/PNG_adm/PNG_adm1.shp", layer = "PNG_adm1")
map<- crop(shape2, extent(149, 154, -14, -8.75))
projection(map)<- CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
p <- ggplot() + geom_polygon(data = map, aes(x = long, y = lat, group = group), color = "black", size = 0.25) + coord_map()
projection(Raster.HR)<- CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
#plot raster and ggplot
par(mfrow=c(1,1))
plot(p)
par(mfrow=c(1,1), new = TRUE)
plot(Raster.HR, col=RasterCols, breaks=Raster.breaks, legend = NULL, bbox(map))
Please let me know if there is another package/line of code I should be using to do this! Appreciate any help
Ok I understand. You want to plot multiple raster layers on the ggplot or you want that the raster object is over your background polygon object. The problem with rasterVis::gplot is that it directly plot the raster and does not allow to add another one under or over. You remind me that I already had this need and modified function gplot to retrieve the data as a tibble so that you can then play with it as much as you want with dplyr and then ggplot2. Thanks for the reminder, I added it in my current github library for later use!
Let's use a reproducible example to show this function:
Create datasets
Create a map of the world as a Raster to be use as background Raster map
Create a raster of data, here a distance from a point (limited to a maximum distance)
The code:
library(raster)
# Get world map
library(maptools)
data(wrld_simpl)
# Transform World as raster
r <- raster(wrld_simpl, res = 1)
wrld_r <- rasterize(wrld_simpl, r)
# Lets create a raster of data
pt1 <- matrix(c(100,0), ncol = 2)
dist1 <- distanceFromPoints(r, pt1)
values(dist1)[values(dist1) > 5e6] <- NA
plot(dist1)
# Plot both
plot(wrld_r, col = "grey")
plot(dist1, add = TRUE)
Function to extract (part of) raster values and transform as a tibble
#' Transform raster as data.frame to be later used with ggplot
#' Modified from rasterVis::gplot
#'
#' #param x A Raster* object
#' #param maxpixels Maximum number of pixels to use
#'
#' #details rasterVis::gplot is nice to plot a raster in a ggplot but
#' if you want to plot different rasters on the same plot, you are stuck.
#' If you want to add other information or transform your raster as a
#' category raster, you can not do it. With `SDMSelect::gplot_data`, you retrieve your
#' raster as a tibble that can be modified as wanted using `dplyr` and
#' then plot in `ggplot` using `geom_tile`.
#' If Raster has levels, they will be joined to the final tibble.
#'
#' #export
gplot_data <- function(x, maxpixels = 50000) {
x <- raster::sampleRegular(x, maxpixels, asRaster = TRUE)
coords <- raster::xyFromCell(x, seq_len(raster::ncell(x)))
## Extract values
dat <- utils::stack(as.data.frame(raster::getValues(x)))
names(dat) <- c('value', 'variable')
dat <- dplyr::as.tbl(data.frame(coords, dat))
if (!is.null(levels(x))) {
dat <- dplyr::left_join(dat, levels(x)[[1]],
by = c("value" = "ID"))
}
dat
}
Plot multiple rasters in ggplot
You can use gplot_data to transform any raster as a tibble. You are then able to add any modification using dplyr and plot on ggplot with geom_tile. The interesting point is that you can use geom_tile as many time as you want with different raster data, provided that fill option is comparable. Otherwise, you can use the trick below to remove NA values in the background raster map and use a unique fill colour.
# With gplot_data
library(ggplot2)
# Transform rasters as data frame
gplot_wrld_r <- gplot_data(wrld_r)
gplot_dist1 <- gplot_data(dist1)
# To define a unique fill colour for the world map,
# you need to remove NA values in gplot_wrld_r which
# can be done with dplyr::filter
ggplot() +
geom_tile(data = dplyr::filter(gplot_wrld_r, !is.na(value)),
aes(x = x, y = y), fill = "grey20") +
geom_tile(data = gplot_dist1,
aes(x = x, y = y, fill = value)) +
scale_fill_gradient("Distance",
low = 'yellow', high = 'blue',
na.value = NA) +
coord_quickmap()
Plot raster over polygons
Of course, with a background map as a polygon object, this trick also let you add your raster over it:
wrld_simpl_sf <- sf::st_as_sf(wrld_simpl)
ggplot() +
geom_sf(data = wrld_simpl_sf, fill = "grey20",
colour = "white", size = 0.2) +
geom_tile(data = gplot_dist1,
aes(x = x, y = y, fill = value)) +
scale_fill_gradient("Distance",
low = 'yellow', high = 'blue',
na.value = NA)
EDIT: gplot_data is now in this simple R package: https://github.com/statnmap/cartomisc

R Subset of pam, Arrange multiple figures in one

I'm struggling with the following problem:
I use pam to cluster my dataset v in 7 clusters:
x <- pam(v,7)
I know that there is a vector clustering in x which contains the according numbers of clusters.
I would like to get a subset of x which only contains cluster 1.
Is this possible?
Edit:
Here is an example. Cluster iris in three clusters and plot them.
library(ggfortify)
library(cluster)
v <- iris[-5]
x <- pam(v,3)
autoplot(x, frame = TRUE, frame.type = 'norm')
The question: How can I plot only the first cluster? It should look like the first plot without cluster 2 and 3.
Edit: I think I found a solution. Therefore I don't use autoplot anymore but calculate the convex hull of every cluster and plot it.
library(cluster)
library(plyr)
library(ggplot2)
library(ggrepel)
find_hull <- function(df) df[chull(df$x, df$y),]
v<-iris[-5]
pp <- pam(v,3)
n<-princomp(pp$data, scores = TRUE, cor = ncol(pp$data) != 2)$scores
df<-data.frame(n[,1],n[,2],pp$clustering)
colnames(df)<-c("x","y","z")
hulls <- ddply(df, "z", find_hull)
p<-qplot(x,y,data=df,color=as.factor(z))+
geom_polygon(data=hulls, alpha=1, fill=NA)+
geom_text_repel(aes(label = rownames(df)),arrow = arrow(length = unit(0.00, 'inches'), angle = 0.00),size=5.5,colour="grey55")+
theme_classic(base_size = 16)+
theme(axis.line=element_blank(),axis.text.x=element_blank(),axis.text.y=element_blank(),axis.ticks=element_blank(),
axis.title.x=element_blank(),axis.title.y=element_blank(),legend.position="none",
panel.background=element_blank(),panel.border=element_blank(),panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),plot.background=element_blank())
p
df2<-df[df$z==1,]
hulls <- ddply(df2, "z", find_hull)
p1<-qplot(x,y,data=df2,color=as.factor(z))+
geom_polygon(data=hulls, alpha=0.8, fill=NA)+
geom_text_repel(aes(label = rownames(df2)),arrow = arrow(length = unit(0.00, 'inches'), angle = 0.00),size=5.5,colour="grey25")+
theme_classic(base_size = 16)+
theme(axis.line=element_blank(),axis.text.x=element_blank(),axis.text.y=element_blank(),axis.ticks=element_blank(),
axis.title.x=element_blank(),axis.title.y=element_blank(),legend.position="none",
panel.background=element_blank(),panel.border=element_blank(),panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),plot.background=element_blank())+
p1
Now I want to plot both figures in one device. I have already tried the multiplot from cookbook-r but it gives the error
Error: Aesthetics must be either length 1 or the same as the data (26): label, x, y
It must be because of the labels I guess.
I also tried
grid.arrange(p,p1, ncol=1)
from the gridExtra package but it gives the same error.
Is there any other option to arrange multiple figures with labels in one figure?

How to map a sphere of influence in R

I don't have any code yet because I am trying to figure out where to begin.
I am using map('state, 'texas) to draw Texas and am geoplotting universities on it. I want R to figure out the sphere of influence that university has with in the state and map it out.
Eventually I will geoplot high schools on the map as well and I would like for R to see who's sphere of influence that high school is in.
Does anyone know what package to begin with?
Your description matches with the concept of a voronoi diagram. It partitions an area into polygons based on the locations of points (e.g. your high schools). All the points in the polygon are closer to that particular high school than to all other high schools.
An example using ggplot2, copied from this link:
library(ggplot2)
library(deldir)
library(scales)
library(reshape2)
library(plyr)
# make fake points
n <- 50
k <- 4
mat <- cbind(rnorm(n), rnorm(n))
df <- as.data.frame(mat)
names(df) <- c('x','y')
# triangulate
xrng <- expand_range(range(df$x), .05)
yrng <- expand_range(range(df$y), .05)
deldir <- deldir(df, rw = c(xrng, yrng))
# voronoi
qplot(x, y, data = df) +
geom_segment(
aes(x = x1, y = y1, xend = x2, yend = y2), size = .25,
data = deldir$dirsgs, linetype = 2
) +
scale_x_continuous(expand = c(0,0)) +
scale_y_continuous(expand = c(0,0))

Resources