I'd like to show you an alternative approach using terra
instead of raster
since this a little bit more elegant from my point of view:
# list files available, I created 12 files beforehand
files <- list.files(pattern = "*.tif")
# using filenames following your pattern
head(files)
#> [1] "NDVI-2017-07-09.tif"
#> [2] "NDVI-2017-08-09.tif"
#> [3] "NDVI-2017-09-09.tif"
#> [4] "NDVI-2017-10-09.tif"
#> [5] "NDVI-2017-11-09.tif"
#> [6] "NDVI-2017-12-09.tif"
# create a SpatRast object using terra
stack <- terra::rast(files)
# note that nlyr = 12, so this is basically a raster stack
stack
#> class : SpatRaster
#> dimensions : 900, 900, 12 (nrow, ncol, nlyr)
#> resolution : 0.001111111, 0.001111111 (x, y)
#> extent : 0, 1, 0, 1 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84
#> sources : NDVI-2017-07-09.tif
#> NDVI-2017-08-09.tif
#> NDVI-2017-09-09.tif
#> ... and 9 more source(s)
#> names : NDVI-~07-09, NDVI-~08-09, NDVI-~09-09, NDVI-~10-09, NDVI-~11-09, NDVI-~12-09, ...
#> min values : -3840, -3840, -3840, -3840, -3840, -3840, ...
#> max values : 3881, 3881, 3881, 3881, 3881, 3881, ...
# parse timestamps from names attribute and set the time attribute
terra::time(stack) <- names(stack) |> stringr::str_sub(start = 6, end = 15) |> strptime(format = "%Y-%m-%d", tz = "UTC")
# your stack now has time information ranging from 2017-07-09 to 2018-06-09
stack
#> class : SpatRaster
#> dimensions : 900, 900, 12 (nrow, ncol, nlyr)
#> resolution : 0.001111111, 0.001111111 (x, y)
#> extent : 0, 1, 0, 1 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84
#> sources : NDVI-2017-07-09.tif
#> NDVI-2017-08-09.tif
#> NDVI-2017-09-09.tif
#> ... and 9 more source(s)
#> names : NDVI-~07-09, NDVI-~08-09, NDVI-~09-09, NDVI-~10-09, NDVI-~11-09, NDVI-~12-09, ...
#> min values : -3840, -3840, -3840, -3840, -3840, -3840, ...
#> max values : 3881, 3881, 3881, 3881, 3881, 3881, ...
#> time : 2017-07-09 to 2018-06-09 UTC
# you can either make use of partial matching using `[]` querying layer names
# holding timestamps in your case...
stack["2017"]
#> class : SpatRaster
#> dimensions : 900, 900, 6 (nrow, ncol, nlyr)
#> resolution : 0.001111111, 0.001111111 (x, y)
#> extent : 0, 1, 0, 1 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84
#> sources : NDVI-2017-07-09.tif
#> NDVI-2017-08-09.tif
#> NDVI-2017-09-09.tif
#> ... and 3 more source(s)
#> names : NDVI-~07-09, NDVI-~08-09, NDVI-~09-09, NDVI-~10-09, NDVI-~11-09, NDVI-~12-09
#> min values : -3840, -3840, -3840, -3840, -3840, -3840
#> max values : 3881, 3881, 3881, 3881, 3881, 3881
#> time : 2017-07-09 to 2017-12-09 UTC
# ... or construct an index vector as input based on your time attribute
stack[[time(stack) >= "2017-01-01" & time(stack) < "2018-01-01"]]
#> class : SpatRaster
#> dimensions : 900, 900, 6 (nrow, ncol, nlyr)
#> resolution : 0.001111111, 0.001111111 (x, y)
#> extent : 0, 1, 0, 1 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84
#> sources : NDVI-2017-07-09.tif
#> NDVI-2017-08-09.tif
#> NDVI-2017-09-09.tif
#> ... and 3 more source(s)
#> names : NDVI-~07-09, NDVI-~08-09, NDVI-~09-09, NDVI-~10-09, NDVI-~11-09, NDVI-~12-09
#> min values : -3840, -3840, -3840, -3840, -3840, -3840
#> max values : 3881, 3881, 3881, 3881, 3881, 3881
#> time : 2017-07-09 to 2017-12-09 UTC
Personally, I would leave it this way and not create a separate stack per year because querying is quite comfortable. If you wish to split your stack per year nevertheless, you could do something like this, even if this is maybe not the most elegant way to accomplish:
# get unique years from time attribute
years <- terra::time(stack) |> format("%Y") |> unique()
# loop over years and create new sub-stacks
for (i in 1:length(years)) {
assign(paste0("NDVI_stack_", years[i]),
stack[years[i]])
}