Skip to contents

One object and five axes

oceancube represents rectilinear ocean products with the canonical order longitude, latitude, depth, time, and variable. The logical contract is always five-dimensional, including surface fields, which use a singleton depth axis.

library(oceancube)

lon <- c(-80, -79)
lat <- c(-12, -11)
depth <- c(5, 15)
time <- as.Date(c("2020-01-01", "2020-02-01", "2021-01-01", "2021-02-01"))
values <- array(seq_len(2 * 2 * 2 * 4), c(2, 2, 2, 4, 1))
x <- ocean_cube(
  lon, lat, time, values, depth = depth,
  vars = "temperature", units = "degC", source = "offline example"
)
x
#> <ocean_cube>
#>   backend    : memory
#>   source     : offline example
#>   dimensions : 2 x 2 x 2 x 4 x 1 [lon x lat x depth x time x var]
#>   lon        : -80 to -79 (n = 2)
#>   lat        : -12 to -11 (n = 2)
#>   depth      : 5 to 15 (n = 2)
#>   time       : 2020-01-01 to 2021-02-01 (n = 4)
#>   variables  : temperature
summary(x)
#>       field n        min        max
#> 1 longitude 2        -80        -79
#> 2  latitude 2        -12        -11
#> 3     depth 2          5         15
#> 4      time 4 2020-01-01 2021-02-01
#> 5  variable 1       <NA>       <NA>

validation <- cube_validate(x)
inspection <- cube_inspect(x, missing = "none")
stopifnot(nrow(validation) > 0L)
stopifnot(inherits(inspection, "ocean_cube_inspection"))

Memory cubes contain their values. Local NetCDF products can be materialized with read_nc(). The package also has an internal, serializable, read-only NetCDF descriptor backend; cube_collect() turns that representation into an independent memory cube. No network connection is needed for these workflows.

Selection and extraction

Slices select stored positions or coordinates, while crops use closed ranges. Nearest matching is discrete and never interpolates.

slice <- cube_slice(x, longitude = -79, latitude = -11, by = "value")
crop <- cube_crop(x, longitude = c(-80, -79), depth = c(5, 15))
point <- cube_extract(
  x, longitude = -79, latitude = -11, depth = 5,
  time = time[1], variable = "temperature", by = "value"
)
profile <- cube_extract(
  x, longitude = -79, latitude = -11, time = time[1],
  variable = "temperature", by = "value", mode = "profile"
)
series <- cube_extract(
  x, longitude = -79, latitude = -11, depth = 5,
  variable = "temperature", by = "value", mode = "series"
)
dim(slice$data)
#> [1] 1 1 2 4 1
nrow(cube_extract(crop))
#> [1] 32
point
#>   longitude latitude depth       time    variable unit value
#> 1       -79      -11     5 2020-01-01 temperature degC     4
profile
#>   longitude latitude depth       time    variable unit value
#> 1       -79      -11     5 2020-01-01 temperature degC     4
#> 2       -79      -11    15 2020-01-01 temperature degC     8
series
#>   longitude latitude depth       time    variable unit value
#> 1       -79      -11     5 2020-01-01 temperature degC     4
#> 2       -79      -11     5 2020-02-01 temperature degC    12
#> 3       -79      -11     5 2021-01-01 temperature degC    20
#> 4       -79      -11     5 2021-02-01 temperature degC    28

cube_transect() extracts ordered path-depth combinations. Name important arguments so matching, tolerance, mode, and distance semantics remain explicit.

path <- data.frame(station = c("A", "B"), lon = lon, lat = lat)
cube_transect(
  x, path, lon_col = "lon", lat_col = "lat", id_col = "station",
  depth = 5, time = time[1], variable = "temperature", by = "value"
)
#> Warning: `cube_transect()` preserved its legacy implicit nearest matching.
#> Specify `match = "exact"` or `match = "nearest"` explicitly.
#>   point_id point_order longitude_requested latitude_requested longitude
#> 1        A           1                 -80                -12       -80
#> 2        B           2                 -79                -11       -79
#>   latitude match_distance_km requested_distance_km matched_distance_km depth
#> 1      -12                 0                 0.000               0.000     5
#> 2      -11                 0               155.682             155.682     5
#>         time    variable unit value
#> 1 2020-01-01 temperature degC     1
#> 2 2020-01-01 temperature degC     4

Static visualization

The six visualization functions return ggplot objects and preserve the scientific cube. They select stored cells without interpolation, smoothing, or imputation. Specialized D3 visualizations are not part of this release.

plots <- list(
  map = viz.map(x, "temperature", time = time[1], depth = 5),
  section = viz.section(
    x, "temperature", time = time[1], latitude = lat[1]
  ),
  profile = viz.profile(
    x, "temperature", longitude = lon[1], latitude = lat[1], time = time[1]
  ),
  transect = viz.transect(
    x, path, "temperature", time = time[1], depth = 5,
    lon_col = "lon", lat_col = "lat", id_col = "station"
  ),
  timeseries = viz.timeseries(
    x, "temperature", longitude = lon[1], latitude = lat[1], depth = 5
  ),
  hovmoller = viz.hovmoller(
    x, "temperature", axis = "depth", longitude = lon[1], latitude = lat[1]
  )
)
stopifnot(all(vapply(plots, inherits, logical(1), what = "ggplot")))

Masks, climatologies, anomalies, and geometry

cube_mask() classifies horizontal cell centres against polygons and requires the optional sf package. cube_aggregate_time() performs time-only calendar aggregation with equal observation weights and selective NetCDF reads. cube_climatology() describes recurrent day, month, or meteorological-season baselines through equally weighted period-year replicates and bounded NetCDF reads. Its data contains the climatological mean and climatology$sd contains aligned sample SD. clim_month() and clim_day() remain compatibility wrappers. New workflows use the canonical climatology directly with cube_anomaly(); legacy anomaly wrappers delegate to the same engine. cube_trend() adds descriptive per-cell OLS slopes against actual elapsed historical time. It accepts raw, aggregated, and anomaly cubes, but rejects recurrent climatology pseudo-time.

monthly <- cube_aggregate_time(x, by = "month")
#> Warning: Temporal aggregation uses equal observation weighting; irregular or
#> gapped sampling can make observation-weighted summaries differ from
#> duration-weighted temporal summaries.
clim_cube <- suppressWarnings(cube_climatology(x, by = "month"))
clim <- suppressWarnings(clim_month(x))
difference <- cube_anomaly(x, clim_cube, type = "difference")
standardized <- cube_anomaly(x, clim_cube, type = "z")
legacy_difference <- anom_diff(x, clim)
#> Warning: `anom_diff()` is deprecated as of oceancube 0.3.0. Use
#> `cube_anomaly(x, climatology, type = "difference")` instead.
clim_cube
#> <ocean_cube>
#>   backend    : memory
#>   source     : offline example
#>   dimensions : 2 x 2 x 2 x 12 x 1 [lon x lat x depth x time x var]
#>   lon        : -80 to -79 (n = 2)
#>   lat        : -12 to -11 (n = 2)
#>   depth      : 5 to 15 (n = 2)
#>   time       : 2001-01-01 to 2001-12-01 (n = 12)
#>   variables  : temperature
clim
#> <ocean_clim> month climatology
#>   period    : 2020-01-01 to 2021-02-01
#>   variables : temperature
#>   dimensions: 2 x 2 x 2 x 12 x 1 [lon x lat x depth x month x var]
dim(monthly$data)
#> [1]  2  2  2 14  1
dim(difference$data)
#> [1] 2 2 2 4 1
dim(standardized$data)
#> [1] 2 2 2 4 1
trend <- cube_trend(x, time_unit = "year")
dim(trend$data)
#> [1] 2 2 2 1 1

Geometry functions provide cell areas, vertical thicknesses, volumes, and polygon weights without calculating indicators. See the geometry vignette for the optional-sf workflow.

Responsibility and limitations

oceancube owns representation, local reading, selection, extraction, transects, masks, climatologies, anomalies, descriptive per-cell temporal slopes, geometry, and weights. Spatial indicators, trend inference, change points, and regimes belong to downstream analysis such as spatind.

The current core is limited to local rectilinear data. It does not write NetCDF, read OPeNDAP or THREDDS, interpolate, regrid, process curvilinear meshes, perform Sen/Mann–Kendall inference, detect breakpoints or regimes, or provide general antimeridian support.