How to overlay observation points onto geom_line()?

Viewed 43

I would like to add observations of sea lion species along a graph of tidal height. I have already graphed tidal height using geom_line and been able to add observations by overlaying count data with geom_point. However, I would like these points of observation with category being represented by shape to overlap the y-value of the tide height. The following example is my goal.

Example graph with tide height and observations

I am new to R, so I would appreciate any and all criticism. I have copied a subset of the tide & observation data below. The following code is how I currently have the data graphed with overlayed geom_line() and geom_point() graphs and a secondary axis. TIA

structure(list(date = structure(c(1607601600, 1607605200, 1607608800, 
1607612400, 1607616000, 1607619600, 1607623200, 1607626800, 1607630400, 
1607634000, 1607637600, 1607641200, 1607644800), class = c("POSIXct", 
"POSIXt"), tzone = ""), height = c(2.93, 3.97, 5.37, 6.77, 7.75, 
8.03, 7.64, 6.82, 5.75, 4.67, 4.06, 4.27, 5.41), heightDiff = c(0.54, 
1.04, 1.4, 1.4, 0.98, 0.279999999999999, -0.39, -0.819999999999999, 
-1.07, -1.08, -0.61, 0.21, 1.14), current = c("Flood", "Flood", 
"Flood", "Flood", "Flood", "Flood", "Ebb", "Ebb", "Ebb", "Ebb", 
"Ebb", "Flood", "Flood")), row.names = 29:41, class = "data.frame")


structure(list(countDate = structure(c(1607602800, 1607604600, 
1607606400, 1607608200, 1607610000, 1607611800, 1607613600, 1607615400, 
1607617200, 1607619000, 1607620800, 1607622600, 1607624400, 1607626200, 
1607628000, 1607629800, 1607631600, 1607633400, 1607635200, 1607637000, 
1607638800, 1607640600, 1607642400, 1607644200), class = c("POSIXct", 
"POSIXt"), tzone = ""), variable = structure(c(1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L), levels = c("totCSL", "totSSL", "totUnk"), class = "factor"), 
    value = c(NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 
    17L, 17L, 14L, 0L, NA, NA, 15L, 0L, NA, NA, NA, NA)), row.names = 34:57, class = "data.frame")
library(ggplot2)
library(scales)

Count Data Frame

countDF <- data.frame(totCSL = totCSL,
                      totSSL = totSSL,
                      totUnk = totUnk,
                      countDate = countDate)

countDF.melt <- melt(countDF, id = "countDate")

Tide Data Frame

tidedata <- data.frame(date = dateTime, 
                       height = tideData$Verified..ft., stringsAsFactors = FALSE)

Plot

plot <- ggplot() + theme_light() + 
  
  geom_line(tidedata, mapping=aes(x=date, y=height, group = 1), color = 'blue') + 
  xlab("Date") + 
  
  geom_point(countDF.melt, mapping=aes(x=countDate,
                                       y= value, 
                                       shape = factor(variable, 
                                       labels = c("California Sea Lion","Steller Sea Lion","Unknown"))))  + 
  labs(shape = "Pinniped Species") +

  scale_y_continuous(name = "Tide Height (ft)", 
                     sec.axis = sec_axis(trans = ~./1,
                     name = "Pinniped Count")) + 
  
  ggtitle("Pinniped Count on Barge and Tide Height") +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_x_datetime(breaks = date_breaks("1 days"),
                   labels = date_format("%d - %h"),
                   expand=c(0,0),  
                   limits = c(
                     as.POSIXct("2020-12-09 00:00:00"),
                     as.POSIXct("2020-12-15 23:00:00")))  + 

  theme(axis.text.x = element_text(angle = 45, hjust = 1 ))
1 Answers

Here's a rough working answer of what you might be able to do:

  • join the two datasets together to borrow the 'height' values from tidedata to set where other points should be
  • interpolate height values between time points using approx to place points on line
  • pass as one dataset to ggplot code
library(tidyverse)
library(scales)


joined_data <- full_join(tidedata, countDF.melt, by = c("date" = "countDate"))

joined_data |>
  arrange(date) |>
  mutate(variable = factor(
    variable,
    levels = c("totCSL", "totSSL", "totUnk"),
    labels = c("California Sea Lion", "Steller Sea Lion", "Unknown")
  ),
  height = if_else(is.na(height), approx(date, height, date)$y, height)) |> 
ggplot(aes(x = date, y = height)) +
  theme_light() +
  geom_line(color = 'blue') +
  xlab("Date") +
  geom_point(aes(
    shape = variable
  ))  +
  labs(shape = "Pinniped Species") +
  
  scale_y_continuous(name = "Tide Height (ft)",
                     sec.axis = sec_axis(trans = ~ . / 1,
                                         name = "Pinniped Count")) +
  
  ggtitle("Pinniped Count on Barge and Tide Height") +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_x_datetime(
    breaks = date_breaks("1 days"),
    labels = date_format("%d - %h"),
    expand = c(0, 0)
  )  +
  scale_shape_discrete(breaks = c("California Sea Lion", "Steller Sea Lion", "Unknown")) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
#> Warning: Removed 13 rows containing missing values (geom_point).

Do note thought that this makes the secondary x axis coding "Pinniped Count" explicitly wrong!

Related