Google Earth Engine NDVI Calculation – Same Mean for First Two Polygons Each Month

google-earth-enginendvi

Getting some weird results for NDVI code that used to work on the EE. I'm trying to calculate mean NDVI values for a series a months within 6 polygons. When I run the code, I get the expected results, except that the first two polygons (in this case object ID 0 and 1) have the same exact means for every month… When I check other details of the output, such as the .geo field that specifies the objects geometry, they are different and as I would expect. But for some reason the NDVI calculation is always the same.

I'm not a great EE coder so I haven't been able to figure out why this would be happening with object ID 0 and 1 but not the rest of them. Anyone have any clues?

Reproduceable example —–
link to code: https://code.earthengine.google.com/a80276b72c8c9c23468840b514d36f5c
link to features: https://code.earthengine.google.com/?asset=users/marshallthewolf/efish_valleyBottoms

// Add NDVI band Function
var addNDVI = function(image) {
  var ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI');
  return image.addBands(ndvi);
};

// ================================================
// Import Data and Calc NDVI Values
// ================================================

// Define boundary for Swaner Preserve
var swaner_sites = ee.FeatureCollection("users/marshallthewolf/efish_valleyBottoms");
Map.centerObject(swaner_sites);
// print(sites);

// Define Sentinel 2 collection
var sentinel2 = ee.ImageCollection('COPERNICUS/S2'); // "S2" contains the older data "S2_SR" does NOT!

// Add NDVI band to each image in the collection (with mapping)
//var sentinel2 = sentinel2.map(addNDVI).select('NDVI');

// Filter Sentinel collection for May-September for 2017-2022 
var swaner_summer_ndvi = sentinel2
  .filter(ee.Filter.calendarRange(2017, 2022, 'year'))
  .filter(ee.Filter.calendarRange(5, 9, 'month'))
  .filterMetadata('CLOUDY_PIXEL_PERCENTAGE',"less_than", 25) 
  .filterBounds(swaner_sites)
  .sort('system:time_start', false)
  // add NDVI and copy properties of the images
            .map(function(image){
              return image.normalizedDifference(['B8', 'B4']).rename("NDVI")
                        .set(image.toDictionary(image.propertyNames()));
            });
print(swaner_summer_ndvi, 'NDVI');

// ================================================
// Build table of NDVI means and prepair to export
// ================================================

var startDate = ee.Date('2017-05-01'); // set analysis start time
var endDate = ee.Date('2022-10-01'); // set analysis end time
var bandName = 'NDVI';

// calculate the number of months to process
var nMonths = ee.Number(endDate.difference(startDate,'month')).round();

var timeSeries = ee.FeatureCollection(ee.List.sequence(0,nMonths).map(function (n){
  // calculate the offset from startDate
  var ini = startDate.advance(n,'month');
  // advance just one month
  var end = ini.advance(1,'month');
  // check if there are images in time span
  var image = ee.Image(ee.Algorithms.If({
    condition: swaner_summer_ndvi.filterDate(ini,end).size().gte(1), 
          // the valid NDVI image
    trueCase: swaner_summer_ndvi.filterDate(ini,end).mean(), 
          // make a constant non-valid NDVI value image
    falseCase: ee.Image(-999).rename(bandName) 
  }));
  
  // filter and reduce (returns featureCollection)
  var data = image.reduceRegions({
    reducer: ee.Reducer.mean(),
    collection: swaner_sites,
    scale: 1000
  })
    // add the date of the image to each feature
    .map(function(feat){
      return feat.set('system:time_start', ini.millis(),
                      'system:time_end', end.millis(),
                      'numbImages', swaner_summer_ndvi.filterDate(ini,end).size(),
                      'YYYMMdd', ini.format('YYYMMdd'),
                      "name", swaner_summer_ndvi.select("name"));
    });
    
  return data;
})).flatten();

// print to see if it is doing what we expect...
print(timeSeries.filter(ee.Filter.neq('mean',-999)))

// ================================================
// Export the table to the driver for further R coding
// ================================================

// Export the data to a table for further analysis
Export.table.toDrive({
  collection:timeSeries,
  description:"Swaner_NDVI_022823",
  fileFormat:"CSV",
  //selectors:["HRpcode","timeseries"]
})

Best Answer

Posting this for posterity and info for others.

In my reducer call I had the scale set way to high for some reason - possibly for faster computation I can't remember... My fix is below.

// filter and reduce (returns featureCollection)
  var data = image.reduceRegions({
    reducer: ee.Reducer.mean(),
    collection: swaner_sites,
    scale: 10 // Sentinel has NDVI pixel rez of 10 meters -> scale = 10
  })

Once I changed the reducer scale from 1000, to 10 I started getting the answers I expected. So lesson learned, set you reducer scale = native pixel values of your imagery unless you are getting computational errors

Related Question