I need to calculate mean NDVI on a year-parish level in Ecuador. Parishes are the finest level of governmental structure. I have written code (heavily based on this: https://knowyourspace.dk/2020/12/20/mean_ndvi_per_polygon_gee/) that outputs a csv table with the mean NDVIs per parish for one year, but I have no idea how to loop over multiple years.

My basic intuition was to create a list of years, make the code I have written into one big function, and map that big function over my list of years. That did not work, however.

I'd love help in figuring this out. Below is the code for one year. The shapefile can be found at: https://gadm.org/download_country.html.

// Define ecuador country borders for easier computation later on
var worldcountries = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
var ecuador = worldcountries.filter(ee.Filter.eq('country_na', 'Ecuador'));

// Import parish level shape files and add to map
var parish = ee.FeatureCollection('projects/deforestation-sar/assets/gadm41_ECU_3_2');
Map.setCenter(-85.33, -2 ,6);
Map.addLayer(parish.draw({color:'7C110E', strokeWidth: 1}), {}, 'parish');

// Set the date for one year (map over years later)
var year = '2019'
var startdate = year+'-01-01';
var enddate = year+'-12-31';

// Import image collection and filter accordingly
var landsat = ee.ImageCollection("LANDSAT/LC08/C02/T1_RT_TOA")
// Add an NDVI band to the image collection and select that band
var addNDVI = function(image) {
  var ndvi = image.normalizedDifference(['B5', 'B4']).rename('NDVI');
  return image.addBands(ndvi);

var landsatNDVI = landsat.map(addNDVI).select('NDVI');

// Get the mean over each parish
var mean = landsatNDVI.mean().clip(parish);
// Map the meaned NDVI
var color = {
 min: 0.0,
 max: 1.0,
 palette: [
 'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718', '74A901',
 '66A000', '529400', '3E8601', '207401', '056201', '004C00', '023B01',
 '012E01', '011D01', '011301'

Map.addLayer(mean, color, 'mean');

// Add mean NDVI value to parishes in feature collection.
var MeanNDVIperParish = mean.reduceRegions({
  collection: parish,
  reducer: ee.Reducer.mean(),
  scale: 30,

// Remove the content of .geo column for more pleasant .csv files
var NDVIfinal = MeanNDVIperParish.map(function(feat){
  var nullfeat = ee.Feature(null)
  return nullfeat.copyProperties(feat)

// Export the table
  collection: NDVIfinal,
  description: 'ndvi_landsat_gadm3_ecuador_'+year,
  folder: 'GEE',
  fileFormat: 'CSV'

1 Answer 1


You don't specify exactly how you want your output to be formatted. Here's one option that create an image where each year is a separate band, resulting in a CSV where each row is a perish and the mean for each year are in the columns:

var startYear = 2019
var endYear = 2020

var means = ee.Image(
  ee.List.sequence(startYear, endYear)
    function(year, acc) {
      return ee.Image(acc).addBands(

var ndviPerParish = means.reduceRegions({
  collection: parish,
  reducer: ee.Reducer.mean().forEachBand(means),
  scale: 30

var columns = ['Parish'].concat(sequence(startYear, endYear))

  collection: ndviPerParish.sort('Parish'),
  description: 'ndviPerParish',
  selectors: columns

function yearlyMeans(year) {
  var startDate = ee.Date.fromYMD(year, 1, 1)
  var endDate = startDate.advance(1, 'year')

  // You probably don't want to use a real-time collection for this.
  // It would probably be better with TOA or the Level 2 collections
  // Note that Level 2 require you to rescale the numbers before you take the normalized difference
  // var landsat = ee.ImageCollection("LANDSAT/LC08/C02/T1_RT_TOA")
  var landsat = ee.ImageCollection("LANDSAT/LC08/C02/T1_TOA")
    .filterDate(startDate, endDate)

  var addNDVI = function(image) {
    return image
      .normalizedDifference(['B5', 'B4'])
      .rename(year.format('%d')) // Rename NDVI band to the current year

  return landsat.map(addNDVI).mean()

// Create client-side sequence
function sequence(from, to) {
  return Array.apply(null, {length: to - from + 1})
    .map(function (_, i) {
      return i + from


  • Thank you so much! Feb 24, 2023 at 10:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.