Ingest CCE-LTER ZooScan PRPOOS

Published

2026-06-26

1 Overview

Source: ZooScan — the SIO Ocean Informatics ZooScan database (Mark Ohman Lab; interface by Marina Frants). The portal’s bulk download is disabled, so libs/download_zooscan.R scripts its public login + PRPOOS plot CGI (/cgi-bin/tssubplot_new.py), whose returned Plotly page embeds the underlying per-station values as a data:text/csv URI. Each bioclass is fetched in both plot modes and consolidated to zooscan_prpoos.csv (see the Acquire step + by_taxon/_PROVENANCE.md).

  • Provider: cce-lter (Ohman Lab / CCE-LTER; sibling of cce-lter_zoodb and cce-lter_euphausiids).
  • Grain: one row per (sample × taxon × measurement_type); measurement_value = 0 means imaged-but-absent.
  • Samples: ZooScan-imaged PRPOOS net tows — one per station occupation on CalCOFI lines 80/87/90, 2005-present.
  • Taxa (23): ZooScan machine-classified bioclasses (19 WoRMS-resolved; 4 non-taxonomic operational classes — eggs, multiples, nauplii, others).
  • Measurements: zooscan_abundance (No./m²), zooscan_biomass_carbon (mg C/m²), zooscan_feret_diameter (mm), zooscan_carbon_individual (µg C).
Code
graph LR
  A[zooscan_prpoos.csv<br/>taxon x sample rows] --> B[zooscan_sample<br/>station tows + keys]
  A --> C[zooscan_measurement<br/>long: abundance, biomass, feret, indiv-C]
  A --> D[zooscan_taxon<br/>23 bioclasses + WoRMS]
  C -.sample_id.-> B
  C -.taxon_id.-> D
  B -.ship/cruise/grid.-> E[(shared refs)]

2 Setup

Code
devtools::load_all(here::here("../calcofi4db"))
devtools::load_all(here::here("../calcofi4r"))

librarian::shelf(
  CalCOFI/calcofi4db, CalCOFI/calcofi4r,
  DBI, dplyr, DT, fs, glue, here, janitor, jsonlite, knitr, lubridate, purrr,
  readr, sf, stringr, tibble, tidyr, units, quiet = T)
options(readr.show_col_types = F)
options(DT.options = list(scrollX = TRUE))

source(here("libs/ingest.R"))   # overwrite, overwrite_all, dir_data

cc           <- read_calcofi_meta(here("ingest_cce-lter_zooscan.qmd"))
provider     <- cc$provider
dataset      <- cc$dataset
tables_owned <- cc$tables_owned
dir_label    <- glue("{provider}_{dataset}")

dir_parquet  <- here(glue("data/parquet/{dir_label}"))
db_path      <- here(glue("data/wrangling/{dir_label}.duckdb"))

if (overwrite) {
  if (file_exists(db_path))                 file_delete(db_path)
  if (file_exists(paste0(db_path, ".wal"))) file_delete(paste0(db_path, ".wal"))
  if (dir_exists(paste0(db_path, ".tmp")))  dir_delete(paste0(db_path, ".tmp"))
}
dir_create(dirname(db_path))
con <- get_duckdb_con(db_path)
load_duckdb_extension(con, "spatial")

meas_type_csv <- here("metadata/measurement_type.csv")
d_meas_type   <- read_csv(meas_type_csv)

3 Acquire Source Data

libs/download_zooscan.R scripts the ZooScan portal (public login + PRPOOS plot CGI) for the 23 bioclasses × 2 plot modes and consolidates the embedded per-station CSVs into zooscan_prpoos.csv. It runs once then caches; set overwrite_all = TRUE in libs/ingest.R to re-scrape.

Code
source(here("libs/download_zooscan.R"))
zs_dir <- path_expand(glue("{dir_data}/cce-lter/ZooScan"))
zs_csv <- download_zooscan(zs_dir, overwrite = overwrite_all)
ZooScan: using cached zooscan_prpoos.csv 

4 Read + Clean

The cruise code is YYYYMM + a 2-letter ship code (e.g. 200507NH = Jul 2005, R/V New Horizon). We parse year/month/ship_key from it, build site_key from line + station, and a sample_key per station tow.

Code
zs_dir <- path_expand(glue("{dir_data}/cce-lter/ZooScan"))

# archive source (consolidated + per-taxon extracts) to GCS for provenance
sync_to_gcs(
  local_dir  = zs_dir,
  gcs_prefix = glue("archive/{provider}/{dataset}"),
  bucket     = "calcofi-files-public",
  exclude    = c(".DS_Store", "*.tmp", "*.gdoc"))
# A tibble: 49 × 4
   file                                     action     size reason  
   <chr>                                    <chr>     <dbl> <chr>   
 1 by_taxon/_manifest.csv                   uploaded   1632 new file
 2 by_taxon/_PROVENANCE.md                  uploaded   1909 new file
 3 by_taxon/appendicularia__basic.csv       uploaded 135968 new file
 4 by_taxon/appendicularia__extra.csv       uploaded 130227 new file
 5 by_taxon/bryozoan_larvae__basic.csv      uploaded 131162 new file
 6 by_taxon/bryozoan_larvae__extra.csv      uploaded 128057 new file
 7 by_taxon/chaetognatha__basic.csv         uploaded 136459 new file
 8 by_taxon/chaetognatha__extra.csv         uploaded 131599 new file
 9 by_taxon/cnidaria_ctenophores__basic.csv uploaded 135265 new file
10 by_taxon/cnidaria_ctenophores__extra.csv uploaded 131408 new file
# ℹ 39 more rows
Code
d_raw <- read_csv(zs_csv)
num <- function(x) suppressWarnings(as.numeric(trimws(as.character(x))))

d <- d_raw |>
  transmute(
    taxon_slug         = taxon,
    cruise_orig        = trimws(cruise),
    line               = num(line),
    station            = num(station),
    latitude           = num(latitude),
    longitude          = num(longitude),
    max_depth_m        = num(max_depth_m),
    min_depth_m        = num(min_depth_m),
    cruise_mid_date    = as.Date(cruise_mid_date),
    station_date       = as.Date(station_date),
    local_time_pst     = trimws(local_time_pst),
    day_night          = trimws(day_night),
    abundance_per_m2   = num(abundance_per_m2),
    biomass_mgC_per_m2 = num(biomass_mgC_per_m2),
    feret_diameter_mm  = num(feret_diameter_mm),
    carbon_content_indiv = num(carbon_content_indiv)) |>
  mutate(
    # ZooScan reports longitude as positive degrees West — flip to negative (°E)
    longitude = -abs(longitude),
    year     = as.integer(str_sub(cruise_orig, 1, 4)),
    month    = as.integer(str_sub(cruise_orig, 5, 6)),
    ship_key = str_sub(cruise_orig, 7),
    site_key = if_else(
      is.na(line) | is.na(station), NA_character_,
      sprintf("%05.1f %05.1f", line, station)),
    datetime_local_pst = suppressWarnings(
      ymd_hm(paste(station_date, local_time_pst), quiet = TRUE)),
    sample_key = paste(cruise_orig, line, station, station_date, sep = "|"))

cat(glue(
  "Read {format(nrow(d), big.mark=',')} (sample x taxon) rows; ",
  "{n_distinct(d$sample_key)} samples; {n_distinct(d$taxon_slug)} taxa; ",
  "lines {paste(sort(unique(d$line)), collapse='/')}; ",
  "{min(d$year)}-{max(d$year)}"), "\n")
Read 34,109 (sample x taxon) rows; 1483 samples; 23 taxa; lines 80/87/90; 2005-2026 

5 Build Taxon Table (WoRMS)

zooscan_taxon is the 23 bioclasses with WoRMS AphiaIDs + classification, resolved offline and cached in metadata/cce-lter/zooscan/taxon_worms.csv (19/23 matched; 4 non-taxonomic operational classes, Q03).

Code
d_worms <- read_csv(here("metadata/cce-lter/zooscan/taxon_worms.csv"))

zooscan_taxon <- d_worms |>
  arrange(taxon_slug) |>
  transmute(
    taxon_id        = row_number(),
    taxon_zooscan, taxon_slug,
    aphia_id        = as.integer(aphia_id),
    scientific_name = scientific_name_accepted,
    rank, taxon_status = status, taxon_note,
    kingdom, phylum, class, order_taxon = order, family)

stopifnot("taxon_slug mismatch source vs WoRMS map" =
            all(unique(d$taxon_slug) %in% zooscan_taxon$taxon_slug))
dbWriteTable(con, "zooscan_taxon", zooscan_taxon, overwrite = TRUE)
cat(glue("zooscan_taxon: {nrow(zooscan_taxon)} bioclasses ",
         "({sum(!is.na(zooscan_taxon$aphia_id))} WoRMS-matched, ",
         "{sum(zooscan_taxon$taxon_status=='non-taxonomic')} non-taxonomic [Q03])"), "\n")
zooscan_taxon: 23 bioclasses (19 WoRMS-matched, 4 non-taxonomic [Q03]) 

6 Build Sample Table + Resolve Keys

One row per distinct station tow. ship_key (the cruise-code suffix) joins the shared ship registry for ship_nodc/ship_name; cruise_key is YYYY-MM-{ship_nodc} validated against the cruise registry.

Code
# position/depth are sample properties, but the per-class plot CGI leaves them
# NA on some taxa's rows (e.g. gelatinous classes) — recover the (identical)
# non-NA value per sample; identity columns are constant within sample_key
d_sample0 <- d |>
  group_by(sample_key) |>
  summarize(
    cruise_orig = first(cruise_orig), year = first(year), month = first(month),
    ship_key = first(ship_key), line = first(line), station = first(station),
    site_key = first(site_key), cruise_mid_date = first(cruise_mid_date),
    station_date = first(station_date), local_time_pst = first(local_time_pst),
    day_night = first(day_night), datetime_local_pst = first(datetime_local_pst),
    latitude    = mean(latitude,    na.rm = TRUE),
    longitude   = mean(longitude,   na.rm = TRUE),
    max_depth_m = mean(max_depth_m, na.rm = TRUE),
    min_depth_m = mean(min_depth_m, na.rm = TRUE),
    .groups = "drop")
stopifnot("sample_key not unique over sample attributes" =
            n_distinct(d_sample0$sample_key) == nrow(d_sample0))

load_prior_tables(
  con, parquet_dir = here("data/parquet/swfsc_ichthyo"),
  tables = c("ship", "cruise", "grid"), geom_tables = c("grid"), as_view = TRUE)
# A tibble: 3 × 3
  table   rows has_geom
  <chr>  <dbl> <lgl>   
1 cruise   691 FALSE   
2 grid     218 TRUE    
3 ship      48 FALSE   
Code
d_ship <- dbGetQuery(con, "SELECT ship_key, ship_nodc, ship_name FROM ship")
valid_ck <- dbGetQuery(con, "SELECT DISTINCT cruise_key FROM cruise")$cruise_key

d_sample <- d_sample0 |>
  left_join(d_ship, by = "ship_key") |>
  mutate(
    cruise_key = if_else(
      is.na(ship_nodc), NA_character_,
      sprintf("%04d-%02d-%s", year, month, ship_nodc)),
    cruise_key = if_else(cruise_key %in% valid_ck, cruise_key, NA_character_)) |>
  arrange(cruise_orig, line, station, station_date) |>
  mutate(sample_id = row_number(), .before = 1)

sample_map     <- d_sample |> select(sample_id, sample_key)
zooscan_sample <- d_sample |>
  select(sample_id, cruise_orig, cruise_key, ship_key, ship_name,
         line, station, site_key, latitude, longitude,
         max_depth_m, min_depth_m, cruise_mid_date, station_date,
         local_time_pst, datetime_local_pst, day_night)
dbWriteTable(con, "zooscan_sample", zooscan_sample, overwrite = TRUE)

n_ship   <- sum(!is.na(d_sample$ship_name))
n_cruise <- sum(!is.na(d_sample$cruise_key))
cat(glue(
  "zooscan_sample: {nrow(zooscan_sample)} samples; ",
  "ship {n_ship}/{nrow(d_sample)} ({round(100*n_ship/nrow(d_sample),1)}%), ",
  "cruise_key {n_cruise}/{nrow(d_sample)} ({round(100*n_cruise/nrow(d_sample),1)}%)"), "\n")
zooscan_sample: 1483 samples; ship 1483/1483 (100%), cruise_key 1043/1483 (70.3%) 

7 Add Spatial

Code
add_point_geom(con, "zooscan_sample", lon_col = "longitude", lat_col = "latitude")
grid_stats <- assign_grid_key(con, "zooscan_sample")
grid_stats |> datatable(caption = "Grid assignment")

8 Pivot Measurements to Long Format

The four ZooScan metrics pivot into long form, joined to sample_id (via sample_key) and taxon_id (via taxon_slug). Explicit zeros (imaged-but- absent) are retained; only NA / non-finite values are dropped.

Code
meas_recode <- c(
  abundance_per_m2     = "zooscan_abundance",
  biomass_mgC_per_m2   = "zooscan_biomass_carbon",
  feret_diameter_mm    = "zooscan_feret_diameter",
  carbon_content_indiv = "zooscan_carbon_individual")

zooscan_measurement <- d |>
  left_join(sample_map, by = "sample_key") |>
  left_join(zooscan_taxon |> select(taxon_id, taxon_slug), by = "taxon_slug") |>
  select(sample_id, taxon_id, all_of(names(meas_recode))) |>
  pivot_longer(cols = all_of(names(meas_recode)),
               names_to = "src_col", values_to = "measurement_value") |>
  filter(!is.na(measurement_value), is.finite(measurement_value)) |>
  mutate(measurement_type = unname(meas_recode[src_col])) |>
  arrange(sample_id, taxon_id, measurement_type) |>
  transmute(measurement_id = row_number(),
            sample_id, taxon_id, measurement_type, measurement_value)

dbWriteTable(con, "zooscan_measurement", zooscan_measurement, overwrite = TRUE)
cat(glue(
  "zooscan_measurement: {format(nrow(zooscan_measurement), big.mark=',')} rows ",
  "({format(sum(zooscan_measurement$measurement_value==0), big.mark=',')} explicit zeros)"), "\n")
zooscan_measurement: 126,692 rows (9,769 explicit zeros) 

9 Add Measurement Types

Code
zs_types <- tibble(
  measurement_type = c("zooscan_abundance", "zooscan_biomass_carbon",
                       "zooscan_feret_diameter", "zooscan_carbon_individual"),
  description = c(
    "Zooplankton areal abundance from ZooScan optical imaging, by image-classified bioclass.",
    "Estimated zooplankton carbon biomass from ZooScan optical imaging, by bioclass.",
    "Mean Feret diameter (organism size) from ZooScan optical imaging, by bioclass.",
    "Mean individual carbon content from ZooScan optical imaging, by bioclass."),
  units = c("count/m2", "mgC/m2", "mm", "ugC"),
  is_canonical = c(TRUE, TRUE, NA, NA),
  `_source_column` = c("abundance_per_m2", "biomass_mgC_per_m2",
                       "feret_diameter_mm", "carbon_content_indiv"),
  `_source_table` = "zooscan_measurement",
  `_source_datasets` = "cce-lter_zooscan",
  `_qual_column` = NA_character_, `_prec_column` = NA_character_)

new_types <- zs_types |> filter(!measurement_type %in% d_meas_type$measurement_type)
if (nrow(new_types) > 0) {
  d_meas_type <- bind_rows(d_meas_type, new_types)
  write_csv(d_meas_type, meas_type_csv)
  cat(glue("Added measurement type(s): {paste(new_types$measurement_type, collapse=', ')}"), "\n")
} else cat("zooscan measurement types already registered\n")
zooscan measurement types already registered
Code
dbWriteTable(con, "measurement_type", d_meas_type, overwrite = TRUE)

10 Load Dataset Metadata

Code
d_dataset <- ingest_yaml_to_dataset_df(read_ingest_yaml(here()))
dbWriteTable(con, "dataset", d_dataset, overwrite = TRUE)
cat(glue("dataset: {nrow(d_dataset)} dataset(s) registered"), "\n")
dataset: 12 dataset(s) registered 

11 Schema Documentation

Code
zooscan_rels <- list(
  primary_keys = list(
    zooscan_sample      = "sample_id",
    zooscan_measurement = "measurement_id",
    zooscan_taxon       = "taxon_id",
    measurement_type    = "measurement_type"),
  foreign_keys = list(
    list(table = "zooscan_measurement", column = "sample_id",
         ref_table = "zooscan_sample", ref_column = "sample_id"),
    list(table = "zooscan_measurement", column = "taxon_id",
         ref_table = "zooscan_taxon", ref_column = "taxon_id"),
    list(table = "zooscan_measurement", column = "measurement_type",
         ref_table = "measurement_type", ref_column = "measurement_type")))

cc_erd(
  con,
  tables = c("zooscan_sample", "zooscan_measurement", "zooscan_taxon",
             "measurement_type", "dataset"),
  rels = zooscan_rels,
  colors = list(
    lightblue   = c("zooscan_sample", "zooscan_measurement"),
    lightgreen  = "zooscan_taxon",
    lightyellow = "measurement_type",
    white       = "dataset"))

Code
build_relationships_json(
  rels = zooscan_rels, output_dir = dir_parquet,
  provider = provider, dataset = dataset)
[1] "/Users/bbest/Github/CalCOFI/workflows/data/parquet/cce-lter_zooscan/relationships.json"

12 Validate

Code
results <- validate_for_release(con, checks = "all", strict = FALSE)
cat("Validation:", ifelse(results$passed, "PASSED", "FAILED"), "\n")
Validation: FAILED 
Code
if (length(results$errors) > 0)
  cat("Errors:\n", paste("-", results$errors, collapse = "\n"), "\n")
Errors:
 - Table 'zooscan_sample' has 440 NULL values in required column 'cruise_key'
- Table 'zooscan_taxon' has 4 NULL values in required column 'aphia_id' 
Code
# NULLs in cross-dataset keys (cruise_key for cruises absent from the registry)
# are the EXPECTED unmatched remainder (Q02), not a hard failure.
cov <- dbGetQuery(con,
  "SELECT AVG(CASE WHEN ship_key   IS NOT NULL THEN 1 ELSE 0 END) ship,
          AVG(CASE WHEN cruise_key IS NOT NULL THEN 1 ELSE 0 END) cruise,
          AVG(CASE WHEN grid_key   IS NOT NULL THEN 1 ELSE 0 END) grid
   FROM zooscan_sample")
cat(glue("Match coverage: ship_key {round(100*cov$ship,1)}%, ",
         "cruise_key {round(100*cov$cruise,1)}%, grid_key {round(100*cov$grid,1)}%"), "\n")
Match coverage: ship_key 100%, cruise_key 70.3%, grid_key 100% 
Code
orphan <- dbGetQuery(con,
  "SELECT SUM(CASE WHEN s.sample_id IS NULL THEN 1 ELSE 0 END) AS no_sample,
          SUM(CASE WHEN t.taxon_id  IS NULL THEN 1 ELSE 0 END) AS no_taxon
   FROM zooscan_measurement m
   LEFT JOIN zooscan_sample s USING (sample_id)
   LEFT JOIN zooscan_taxon  t USING (taxon_id)")
cat(glue("Orphan measurements: {orphan$no_sample} w/o sample, {orphan$no_taxon} w/o taxon"), "\n")
Orphan measurements: 0 w/o sample, 0 w/o taxon 

13 Data Preview

Code
samp_cols <- dbGetQuery(con,
  "SELECT column_name FROM information_schema.columns
   WHERE table_name='zooscan_sample' AND data_type NOT LIKE 'GEOMETRY%'")$column_name
dbGetQuery(con, glue("SELECT {paste(samp_cols, collapse=', ')} FROM zooscan_sample LIMIT 100")) |>
  datatable(caption = "zooscan_sample — first 100 rows", rownames = FALSE, filter = "top")
Code
dbGetQuery(con,
  "SELECT m.measurement_id, m.sample_id, x.taxon_zooscan, m.measurement_type, m.measurement_value
   FROM zooscan_measurement m JOIN zooscan_taxon x USING (taxon_id)
   ORDER BY m.measurement_id LIMIT 100") |>
  datatable(caption = "zooscan_measurement — first 100 rows", rownames = FALSE)
Code
dbGetQuery(con,
  "SELECT t.taxon_id, t.taxon_zooscan, t.scientific_name, t.aphia_id, t.rank,
          t.taxon_note, COUNT(DISTINCT m.sample_id) AS n_samples
   FROM zooscan_taxon t LEFT JOIN zooscan_measurement m USING (taxon_id)
   GROUP BY ALL ORDER BY t.taxon_id") |>
  datatable(caption = "zooscan_taxon — 23 bioclasses", rownames = FALSE)

14 Write Parquet Outputs

Code
dir_create(dir_parquet)

mismatches <- list(
  measurement_types = collect_measurement_type_mismatches(
    con, here("metadata/measurement_type.csv")),
  cruise_keys = collect_cruise_key_mismatches(con, "zooscan_sample"))

parquet_stats <- write_parquet_outputs(
  con        = con,
  output_dir = dir_parquet,
  tables     = c("zooscan_sample", "zooscan_measurement", "zooscan_taxon",
                 "measurement_type", "dataset"),
  sort_by    = list(zooscan_measurement = "sample_id"),
  strip_provenance = FALSE,
  mismatches = mismatches)

parquet_stats |> mutate(file = basename(path)) |> select(-path) |>
  datatable(caption = "Parquet export statistics")

15 Write Metadata

Code
d_tbls_rd <- read_csv(here("metadata/cce-lter/zooscan/tbls_redefine.csv"))
d_flds_rd <- read_csv(here("metadata/cce-lter/zooscan/flds_redefine.csv"))

metadata_path <- build_metadata_json(
  con        = con,
  d_tbls_rd  = d_tbls_rd,
  d_flds_rd  = d_flds_rd,
  metadata_derived_csv = here("metadata/cce-lter/zooscan/metadata_derived.csv"),
  output_dir = dir_parquet,
  tables     = c("zooscan_sample", "zooscan_measurement", "zooscan_taxon"),
  set_comments = TRUE,
  provider   = provider,
  dataset    = dataset,
  workflow_url = cc$workflow_url,
  tables_owned = tables_owned)

16 Upload to GCS

Code
sync_to_gcs(
  local_dir  = dir_parquet,
  gcs_prefix = glue("ingest/{dir_label}"),
  bucket     = "calcofi-db")
# A tibble: 8 × 4
  file                        action     size reason  
  <chr>                       <chr>     <dbl> <chr>   
1 dataset.parquet             uploaded   8904 new file
2 manifest.json               uploaded   2721 new file
3 measurement_type.parquet    uploaded   5793 new file
4 metadata.json               uploaded   7922 new file
5 relationships.json          uploaded    745 new file
6 zooscan_measurement.parquet uploaded 667710 new file
7 zooscan_sample.parquet      uploaded  15234 new file
8 zooscan_taxon.parquet       uploaded   3820 new file

17 Questions for Data Providers

Follow-up questions for CCE-LTER (Mark Ohman, Marina Frants), tracked in metadata/cce-lter/zooscan/questions.csv.

Code
read_csv(here(glue("metadata/{provider}/{dataset}/questions.csv"))) |>
  arrange(factor(priority, c("blocker", "high", "normal", "low")), id) |>
  select(priority, question, context, related_field, status) |>
  datatable(
    caption = "Questions for data providers (ranked by importance)",
    options = list(dom = "t", pageLength = 20), rownames = FALSE)

18 Cleanup

Code
close_duckdb(con)
cat(glue("Parquet outputs written to: {dir_parquet}"), "\n")
Parquet outputs written to: /Users/bbest/Github/CalCOFI/workflows/data/parquet/cce-lter_zooscan 
Code
devtools::session_info()
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.5.2 (2025-10-31)
 os       macOS Sequoia 15.7.1
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       Europe/Rome
 date     2026-06-26
 pandoc   3.8.3 @ /opt/homebrew/bin/ (via rmarkdown)
 quarto   1.8.25 @ /usr/local/bin/quarto

─ Packages ───────────────────────────────────────────────────────────────────
 ! package             * version    date (UTC) lib source
   abind                 1.4-8      2024-09-12 [1] CRAN (R 4.5.0)
   arrow                 24.0.0     2026-04-29 [1] CRAN (R 4.5.2)
   askpass               1.2.1      2024-10-04 [1] CRAN (R 4.5.0)
   assertthat            0.2.1      2019-03-21 [1] CRAN (R 4.5.0)
   backports             1.5.1      2026-04-03 [1] CRAN (R 4.5.2)
   base64enc             0.1-6      2026-02-02 [1] CRAN (R 4.5.2)
   bit                   4.6.0      2025-03-06 [1] CRAN (R 4.5.0)
   bit64                 4.8.2      2026-05-19 [1] CRAN (R 4.5.2)
   blob                  1.3.0      2026-01-14 [1] CRAN (R 4.5.2)
   broom                 1.0.13     2026-05-14 [1] CRAN (R 4.5.2)
   bslib                 0.11.0     2026-05-16 [1] CRAN (R 4.5.2)
   cachem                1.1.0      2024-05-16 [1] CRAN (R 4.5.0)
 P calcofi4db          * 2.8.2      2026-06-07 [?] load_all()
 P calcofi4r           * 1.3.0      2026-06-05 [?] load_all()
   class                 7.3-23     2025-01-01 [1] CRAN (R 4.5.2)
   classInt              0.4-11     2025-01-08 [1] CRAN (R 4.5.0)
   cli                   3.6.6      2026-04-09 [1] CRAN (R 4.5.2)
   codetools             0.2-20     2024-03-31 [1] CRAN (R 4.5.2)
   crayon                1.5.3      2024-06-20 [1] CRAN (R 4.5.0)
   crosstalk             1.2.2      2025-08-26 [1] CRAN (R 4.5.0)
   curl                  7.1.0      2026-04-22 [1] CRAN (R 4.5.2)
   data.table            1.18.4     2026-05-06 [1] CRAN (R 4.5.2)
   DBI                 * 1.3.0      2026-02-25 [1] CRAN (R 4.5.2)
   dbplyr                2.5.2      2026-02-13 [1] CRAN (R 4.5.2)
   desc                  1.4.3      2023-12-10 [1] CRAN (R 4.5.0)
   devtools              2.5.0      2026-03-14 [1] CRAN (R 4.5.2)
   DiagrammeR            1.0.12     2026-04-27 [1] CRAN (R 4.5.2)
   DiagrammeRsvg         0.1        2016-02-04 [1] CRAN (R 4.5.0)
   digest                0.6.39     2025-11-19 [1] CRAN (R 4.5.2)
   dm                    1.1.2      2026-05-17 [1] CRAN (R 4.5.2)
   dplyr               * 1.2.1      2026-04-03 [1] CRAN (R 4.5.2)
   DT                  * 0.34.0     2025-09-02 [1] CRAN (R 4.5.0)
   duckdb                1.5.2      2026-04-13 [1] CRAN (R 4.5.2)
   dygraphs              1.1.1.6    2018-07-11 [1] CRAN (R 4.5.0)
   e1071                 1.7-17     2025-12-18 [1] CRAN (R 4.5.2)
   ellipsis              0.3.2      2021-04-29 [1] CRAN (R 4.5.0)
   evaluate              1.0.5      2025-08-27 [1] CRAN (R 4.5.0)
   farver                2.1.2      2024-05-13 [1] CRAN (R 4.5.0)
   fastmap               1.2.0      2024-05-15 [1] CRAN (R 4.5.0)
   fs                  * 2.1.0      2026-04-18 [1] CRAN (R 4.5.2)
   fuzzyjoin             0.1.8      2026-02-20 [1] CRAN (R 4.5.2)
   gargle                1.6.1      2026-01-29 [1] CRAN (R 4.5.2)
   generics              0.1.4      2025-05-09 [1] CRAN (R 4.5.0)
   geojsonsf             2.0.5      2025-11-26 [1] CRAN (R 4.5.2)
   ggplot2               4.0.3      2026-04-22 [1] CRAN (R 4.5.2)
   glue                * 1.8.1      2026-04-17 [1] CRAN (R 4.5.2)
   googleAuthR           2.0.2.1    2026-01-09 [1] CRAN (R 4.5.2)
   googleCloudStorageR   0.7.0      2021-12-16 [1] CRAN (R 4.5.0)
   googledrive           2.1.2      2025-09-10 [1] CRAN (R 4.5.0)
   gtable                0.3.6      2024-10-25 [1] CRAN (R 4.5.0)
   here                * 1.0.2      2025-09-15 [1] CRAN (R 4.5.0)
   highcharter           0.9.5      2026-04-22 [1] CRAN (R 4.5.2)
   hms                   1.1.4      2025-10-17 [1] CRAN (R 4.5.0)
   htmltools             0.5.9      2025-12-04 [1] CRAN (R 4.5.2)
   htmlwidgets           1.6.4      2023-12-06 [1] CRAN (R 4.5.0)
   httpuv                1.6.17     2026-03-18 [1] CRAN (R 4.5.2)
   httr                  1.4.8      2026-02-13 [1] CRAN (R 4.5.2)
   httr2                 1.2.2      2025-12-08 [1] CRAN (R 4.5.2)
   igraph                2.3.2      2026-05-29 [1] CRAN (R 4.5.2)
   isoband               0.3.0      2025-12-07 [1] CRAN (R 4.5.2)
   janitor             * 2.2.1      2024-12-22 [1] CRAN (R 4.5.0)
   jquerylib             0.1.4      2021-04-26 [1] CRAN (R 4.5.0)
   jsonlite            * 2.0.0      2025-03-27 [1] CRAN (R 4.5.0)
   KernSmooth            2.23-26    2025-01-01 [1] CRAN (R 4.5.2)
   knitr               * 1.51       2025-12-20 [1] CRAN (R 4.5.2)
   later                 1.4.8      2026-03-05 [1] CRAN (R 4.5.2)
   lattice               0.22-9     2026-02-09 [1] CRAN (R 4.5.2)
   lazyeval              0.2.3      2026-04-04 [1] CRAN (R 4.5.2)
   leafem                0.2.5      2025-08-28 [1] CRAN (R 4.5.0)
   leaflet               2.2.3      2025-09-04 [1] CRAN (R 4.5.0)
   librarian             1.8.1      2021-07-12 [1] CRAN (R 4.5.0)
   lifecycle             1.0.5      2026-01-08 [1] CRAN (R 4.5.2)
   lubridate           * 1.9.5      2026-02-04 [1] CRAN (R 4.5.2)
   magrittr              2.0.5      2026-04-04 [1] CRAN (R 4.5.2)
   mapgl                 0.5.0      2026-06-24 [1] Github (bbest/mapgl@1e52f60)
   mapview               2.11.4     2025-09-08 [1] CRAN (R 4.5.0)
   markdown              2.0        2025-03-23 [1] CRAN (R 4.5.0)
   Matrix                1.7-5      2026-03-21 [1] CRAN (R 4.5.2)
   memoise               2.0.1      2021-11-26 [1] CRAN (R 4.5.0)
   mgcv                  1.9-4      2025-11-07 [1] CRAN (R 4.5.0)
   mime                  0.13       2025-03-17 [1] CRAN (R 4.5.0)
   nlme                  3.1-169    2026-03-27 [1] CRAN (R 4.5.2)
   openssl               2.4.1      2026-05-14 [1] CRAN (R 4.5.2)
   otel                  0.2.0      2025-08-29 [1] CRAN (R 4.5.0)
   pillar                1.11.1     2025-09-17 [1] CRAN (R 4.5.0)
   pkgbuild              1.4.8      2025-05-26 [1] CRAN (R 4.5.0)
   pkgconfig             2.0.3      2019-09-22 [1] CRAN (R 4.5.0)
   pkgload               1.5.1      2026-04-01 [1] CRAN (R 4.5.2)
   plotly                4.12.0     2026-01-24 [1] CRAN (R 4.5.2)
   png                   0.1-9      2026-03-15 [1] CRAN (R 4.5.2)
   promises              1.5.0      2025-11-01 [1] CRAN (R 4.5.0)
   proxy                 0.4-29     2025-12-29 [1] CRAN (R 4.5.2)
   purrr               * 1.2.2      2026-04-10 [1] CRAN (R 4.5.2)
   quantmod              0.4.28     2025-06-19 [1] CRAN (R 4.5.0)
   R6                    2.6.1      2025-02-15 [1] CRAN (R 4.5.0)
   rappdirs              0.3.4      2026-01-17 [1] CRAN (R 4.5.2)
   raster                3.6-32     2025-03-28 [1] CRAN (R 4.5.0)
   RColorBrewer          1.1-3      2022-04-03 [1] CRAN (R 4.5.0)
   Rcpp                  1.1.1-1.1  2026-04-24 [1] CRAN (R 4.5.2)
   readr               * 2.2.0      2026-02-19 [1] CRAN (R 4.5.2)
   rlang                 1.2.0      2026-04-06 [1] CRAN (R 4.5.2)
   rlist                 0.4.6.2    2021-09-03 [1] CRAN (R 4.5.0)
   rmarkdown             2.31       2026-03-26 [1] CRAN (R 4.5.2)
   rnaturalearth         1.2.0      2026-01-19 [1] CRAN (R 4.5.2)
   rnaturalearthhires    1.0.0.9000 2025-10-02 [1] Github (ropensci/rnaturalearthhires@e4736f6)
   RPostgres             1.4.10     2026-02-16 [1] CRAN (R 4.5.2)
   rprojroot             2.1.1      2025-08-26 [1] CRAN (R 4.5.0)
   rstudioapi            0.18.0     2026-01-16 [1] CRAN (R 4.5.2)
   S7                    0.2.2      2026-04-22 [1] CRAN (R 4.5.2)
   sass                  0.4.10     2025-04-11 [1] CRAN (R 4.5.0)
   satellite             1.0.6      2025-08-21 [1] CRAN (R 4.5.0)
   scales                1.4.0      2025-04-24 [1] CRAN (R 4.5.0)
   sessioninfo           1.2.3      2025-02-05 [1] CRAN (R 4.5.0)
   sf                  * 1.1-1      2026-05-06 [1] CRAN (R 4.5.2)
   shiny                 1.14.0     2026-06-21 [1] CRAN (R 4.5.2)
   shinyWidgets          0.9.1      2026-03-09 [1] CRAN (R 4.5.2)
   snakecase             0.11.1     2023-08-27 [1] CRAN (R 4.5.0)
   sp                    2.2-1      2026-02-13 [1] CRAN (R 4.5.2)
   stars                 0.7-2      2026-04-03 [1] CRAN (R 4.5.2)
   stringi               1.8.7      2025-03-27 [1] CRAN (R 4.5.0)
   stringr             * 1.6.0      2025-11-04 [1] CRAN (R 4.5.0)
   terra                 1.9-34     2026-06-19 [1] CRAN (R 4.5.2)
   tibble              * 3.3.1      2026-01-11 [1] CRAN (R 4.5.2)
   tidyr               * 1.3.2      2025-12-19 [1] CRAN (R 4.5.2)
   tidyselect            1.2.1      2024-03-11 [1] CRAN (R 4.5.0)
   timechange            0.4.0      2026-01-29 [1] CRAN (R 4.5.2)
   TTR                   0.24.4     2023-11-28 [1] CRAN (R 4.5.0)
   tzdb                  0.5.0      2025-03-15 [1] CRAN (R 4.5.0)
   units               * 1.0-1      2026-03-11 [1] CRAN (R 4.5.2)
   usethis               3.2.1      2025-09-06 [1] CRAN (R 4.5.0)
   utf8                  1.2.6      2025-06-08 [1] CRAN (R 4.5.0)
   uuid                  1.2-2      2026-01-23 [1] CRAN (R 4.5.2)
   V8                    8.2.0      2026-04-21 [1] CRAN (R 4.5.2)
   vctrs                 0.7.3      2026-04-11 [1] CRAN (R 4.5.2)
   viridisLite           0.4.3      2026-02-04 [1] CRAN (R 4.5.2)
   visNetwork            2.1.4      2025-09-04 [1] CRAN (R 4.5.0)
   vroom                 1.7.1      2026-03-31 [1] CRAN (R 4.5.2)
   withr                 3.0.3      2026-06-19 [1] CRAN (R 4.5.2)
   xfun                  0.59       2026-06-19 [1] CRAN (R 4.5.2)
   xtable                1.8-8      2026-02-22 [1] CRAN (R 4.5.2)
   xts                   0.14.2     2026-02-28 [1] CRAN (R 4.5.2)
   yaml                  2.3.12     2025-12-10 [1] CRAN (R 4.5.2)
   zip                   2.3.3      2025-05-13 [1] CRAN (R 4.5.0)
   zoo                   1.8-15     2025-12-15 [1] CRAN (R 4.5.2)

 [1] /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library

 * ── Packages attached to the search path.
 P ── Loaded and on-disk path mismatch.

──────────────────────────────────────────────────────────────────────────────