Skip to contents

Why this matters

sptrends analyses spatiotemporal trends in gridded environmental data. These datasets commonly exhibit serial correlation and spatial dependence, while analysing many cells simultaneously creates a large-scale multiple-testing problem. Because these challenges interact to affect statistical inference, sptrends keeps the main analytical stages explicit and separate: serial-correlation assessment and treatment, trend testing, slope estimation and multiple-testing correction. These stages may be used independently or combined within complete analytical workflows.

This is not simply a sequence of steps assembled for convenience. True Significant Trends (TST), introduced by Gutiérrez-Hernández and García (2025), is the methodological origin of this design: it identified these three challenges as interconnected facets of the same underlying problem, not as separate issues to be patched independently, and sptrends inherits that understanding rather than just its pipeline.

This vignette shows how to recognise, inspect and visualise the input, and how the main analytical stages fit together. Later vignettes explain each methodological decision.

What read_ordered_stack() and read_netcdf_stack() do

The usual analytical input is a terra::SpatRaster with one layer per time step, ordered from earliest to latest. It can be created from chronologically ordered raster files with read_ordered_stack() or imported from NetCDF datasets with read_netcdf_stack(). Across these layers, each valid raster cell defines an individual time series embedded within a spatially structured dataset. Analytical results are returned as structured sptrends objects with familiar print(), summary() and plot() methods.

Basic workflow

The bundled example is an annual NDVI series derived from the NOAA STAR Blended Vegetation Health Product (Blended-VHP). The data were spatially resampled to a coarser 100 km resolution to enable faster execution of the examples and reprojected to an Eckert IV equal-area grid so that every raster cell represents the same surface area. An equal-area projection is not required by sptrends, but this consideration is often overlooked and becomes important when interpreting cell counts, spatial proportions or area-based summaries.

r <- read_ordered_stack(example_data("vhp_ndvi"))
#> Temporal order auto-detected with pattern '(19[0-9]{2}|20[0-9]{2})'.
#> Automatic mode: order detected from file names. For higher reliability -- especially if the series is not annual -- supplying 'files' explicitly (with 'time' or 'cycle_type') is recommended. See ?read_ordered_stack.
#> Temporal order verification (mandatory, cannot be skipped):
#>  stack_position detected_number                         file
#>               1            1982 VHP_SMN_annual_ndvi_1982.tif
#>               2            1983 VHP_SMN_annual_ndvi_1983.tif
#>               3            1984 VHP_SMN_annual_ndvi_1984.tif
#>               4            1985 VHP_SMN_annual_ndvi_1985.tif
#>               5            1986 VHP_SMN_annual_ndvi_1986.tif
#>               6            1987 VHP_SMN_annual_ndvi_1987.tif
#>               7            1988 VHP_SMN_annual_ndvi_1988.tif
#>               8            1989 VHP_SMN_annual_ndvi_1989.tif
#>               9            1990 VHP_SMN_annual_ndvi_1990.tif
#>              10            1991 VHP_SMN_annual_ndvi_1991.tif
#>              11            1992 VHP_SMN_annual_ndvi_1992.tif
#>              12            1993 VHP_SMN_annual_ndvi_1993.tif
#>              13            1994 VHP_SMN_annual_ndvi_1994.tif
#>              14            1995 VHP_SMN_annual_ndvi_1995.tif
#>              15            1996 VHP_SMN_annual_ndvi_1996.tif
#>              16            1997 VHP_SMN_annual_ndvi_1997.tif
#>              17            1998 VHP_SMN_annual_ndvi_1998.tif
#>              18            1999 VHP_SMN_annual_ndvi_1999.tif
#>              19            2000 VHP_SMN_annual_ndvi_2000.tif
#>              20            2001 VHP_SMN_annual_ndvi_2001.tif
#>              21            2002 VHP_SMN_annual_ndvi_2002.tif
#>              22            2003 VHP_SMN_annual_ndvi_2003.tif
#>              23            2004 VHP_SMN_annual_ndvi_2004.tif
#>              24            2005 VHP_SMN_annual_ndvi_2005.tif
#>              25            2006 VHP_SMN_annual_ndvi_2006.tif
#>              26            2007 VHP_SMN_annual_ndvi_2007.tif
#>              27            2008 VHP_SMN_annual_ndvi_2008.tif
#>              28            2009 VHP_SMN_annual_ndvi_2009.tif
#>              29            2010 VHP_SMN_annual_ndvi_2010.tif
#>              30            2011 VHP_SMN_annual_ndvi_2011.tif
#>              31            2012 VHP_SMN_annual_ndvi_2012.tif
#>              32            2013 VHP_SMN_annual_ndvi_2013.tif
#>              33            2014 VHP_SMN_annual_ndvi_2014.tif
#>              34            2015 VHP_SMN_annual_ndvi_2015.tif
#>              35            2016 VHP_SMN_annual_ndvi_2016.tif
#>              36            2017 VHP_SMN_annual_ndvi_2017.tif
#>              37            2018 VHP_SMN_annual_ndvi_2018.tif
#>              38            2019 VHP_SMN_annual_ndvi_2019.tif
#>              39            2020 VHP_SMN_annual_ndvi_2020.tif
#>              40            2021 VHP_SMN_annual_ndvi_2021.tif
#>              41            2022 VHP_SMN_annual_ndvi_2022.tif
#>              42            2023 VHP_SMN_annual_ndvi_2023.tif

#> Stack built: 42 layers, 146 x 338 cells.
#> >> [read_ordered_stack()] elapsed: 2.16 s
r
#> class       : SpatRaster
#> size        : 146, 338, 42  (nrow, ncol, nlyr)
#> resolution  : 100000, 100000  (x, y)
#> extent      : -1.691185e+07, 1.688815e+07, -6569957, 8030043  (xmin, xmax, ymin, ymax)
#> coord. ref. : World_Eckert_IV
#> source(s)   : memory
#> names       : VHP_S~_1982, VHP_S~_1983, VHP_S~_1984, VHP_S~_1985, VHP_S~_1986, VHP_S~_1987, ...
#> min values  :    0.000019,    0.000038,    0.000019,    0.000038,    0.000019,    0.000019, ...
#> max values  :    0.535432,    0.513679,    0.520081,    0.550945,    0.526776,    0.526885, ...
#> time (years): 1982-00-00 to 2023-00-00 (42 steps)
terra::nlyr(r)
#> [1] 42
terra::time(r)
#>  [1] 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996
#> [16] 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
#> [31] 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023

The imported object contains 42 annual observations spanning 1982–2023. Before beginning any trend analysis, it is good practice to verify that the temporal ordering has been detected correctly and that the raster series matches the expected study period.

Start by viewing the complete series and checking the temporal order:

ndvi_col <- rev(grDevices::hcl.colors(50, "Greens 3"))
terra::plot(
  r, col = ndvi_col, colNA = "transparent", nc = 6,
  maxnl = terra::nlyr(r)
)

Mosaic of all annual mean NDVI layers in temporal order

The same layers can be displayed sequentially in an interactive R session:

Annual mean NDVI, 1982–2023
Annual mean NDVI, 1982–2023

The embedded animation uses the same 42 layers shown in the mosaic. To reproduce it interactively from the original raster series, run:

terra::animate(
  r,
  pause = 0.2,
  main = as.character(terra::time(r)),
  col = ndvi_col,
  colNA = "transparent"
)

Understanding the results

Declaring seasonal input

For seasonal data, supply files in their known chronological order. A file may contain several layers; time must contain one value per layer.

# ordered_files is your chronological vector of file paths.
monthly <- read_ordered_stack(
  files = ordered_files, cycle_type = "monthly",
  start = as.Date("2001-08-01"), report = FALSE
)
seasonal <- compute_anomalies(monthly, cycle_type = "monthly",
                              start_position = 8)
result <- workflow_trends(seasonal$anomalies, report = FALSE)

start is the beginning of the first period. Monthly and composite periods use their centre as the default assigned date; annual layers always use 1 January. Optional end must be the inclusive end of the last period and checks the expected layer count. For other calendars, use read_ordered_stack(files = ordered_files, time = layer_dates).

compute_anomalies() uses positions, not date metadata. Supply the cycle length and the first position correctly: August is position 8 in a monthly annual cycle. Its climatology and optional climatology_sd are ordered from position 1 to cycle; $anomalies retains the input layer order. Daily input with leap days does not have a fixed 365-position annual cycle.

The anomaly result is a plain list of rasters. Pass $anomalies to the next analytical step or inspect a component with terra::plot().

Analytical outputs

So far you have only looked at the raw data. Once an analytical function has actually been run – in any of the vignettes that follow – its output presents itself the same way throughout the package: analytical functions return structured objects with familiar print(), summary() and plot() methods. Complete workflows retain their intermediate results, so users can examine every analytical stage rather than treating the workflow as a black box.

Choosing the main options

Your question Where to continue
Is temporal dependence a problem? Prewhitening vignette
Is there evidence of a trend? Trend-test vignette
How large is the change? Slope-estimation vignette
Which findings survive multiple testing? Multiple-testing vignette
How do I combine the stages? Trend-workflows vignette

Common mistakes

  • Do not assume layers are in chronological order; confirm it directly before analysis.
  • Do not treat missing-value codes as valid observations.
  • Do not interpret raster-cell counts or proportions as surface area without considering the projection and cell size; use an equal-area grid when area-based comparisons or summaries are required.
  • Do not assume that observations are independent, whether across time (serial correlation, see prewhitening vignette) or across neighbouring cells (spatial dependence, see trend-test vignette); both are common in gridded environmental time series and affect inference.
  • Do not treat cell-wise tests as isolated analyses; testing many raster cells simultaneously creates a large-scale multiple-testing problem (see multiple-testing vignette).

Next steps

Continue to vignette("b-prewhitening"), or go directly to vignette("c-trend-test") if temporal preprocessing is unnecessary.

Further details

See ?sptrends for the function index and quality-assurance protocol, ?read_ordered_stack and ?read_netcdf_stack for data import, and ?inspect_ts_cell for interactive exploration.

References