Google Earth Engine – Finding Difference of Two ImageCollections in Google Earth Engine

google-earth-engine

I'm trying to take the difference of two separate ee.ImageCollections, each containing a timeseries of 23 images. One of the image collections is of 20-year mean NDVI, and the other is for a single year. I'm pretty new to GEE, so I'm probably making very silly mistakes.

The first bit was adapted from this great tutorial, and works as expected to create the two image collections (NDVImean and distinctDOY):

var col = ee.ImageCollection('MODIS/006/MOD13A2').select('NDVI');

// Define a mask to clip the NDVI data by.
var mask = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')
  .filter(ee.Filter.eq('country_co', 'US')); 

// Define the regional bounds of animation frames.
var region = ee.Geometry.Rectangle(-127.18, 19.39, -62.75, 51.29);

//*******Group images by composite date: *******

//grouping inter-annual image data representing the same 16-day composite window into lists
col = col.map(function(img) {
  var doy = ee.Date(img.get('system:time_start')).getRelative('day', 'year');
  return img.set('doy', doy);
});

//filter the complete collection to a single year of data
var distinctDOY = col.filterDate('2013-01-01', '2014-01-01');

// Define a filter that identifies which images from the complete collection
// match the DOY from the distinct DOY collection.
var filter = ee.Filter.equals({leftField: 'doy', rightField: 'doy'});

// Define a join.
var join = ee.Join.saveAll('doy_matches');

// Apply the join and convert the resulting FeatureCollection to an
// ImageCollection.
var joinCol = ee.ImageCollection(join.apply(distinctDOY, col, filter));

//******Reduce composite groups: ******
// The result is a new collection, with one image per distinct DOY composite
// that represents its 20-year mean NDVI per pixel.

// Apply mean reduction among matching DOY collections.
var NDVImean = joinCol.map(function(img) {
  var doyCol = ee.ImageCollection.fromImages(
    img.get('doy_matches')
  );
  return doyCol.reduce(ee.Reducer.mean());
});

I tried to use the same method to join the two new collections by date, before subtracting one from each other (so that each image is subtracted from the image of a corresponding date). Since there is no Reducer for subtraction, I tried a work around by first multiplying the distinctDOY by -1, and then using ee.Reducer.sum().

The results are clearly incorrect. I'm pretty sure it's something to do with either the filtering or sum reduction step, since everything looks as expected before that.

My code is:

//**** Find difference *****
// Join mean NDVI with individual year and convert to an image collection

// This function multiplies image by -1
var makeNeg = function(image) {
  return image.multiply(-1);
};

// Map the function over the collection 
var neg = distinctDOY.map(makeNeg);

var compyear = ee.ImageCollection(join.apply(neg, NDVImean, filter));

// Apply sum reduction among matching DOY collections.
var NDVIcomp = compyear.map(function(img) {
  var doyCol = ee.ImageCollection.fromImages(
    img.get('doy_matches')
  );
  return doyCol.reduce(ee.Reducer.sum());
});

In case it's relevant, the code I'm using to visualize the results is:

// Define RGB visualization parameters.
var visParams = {
  min: -1000.0,
  max: 1000.0,
  palette: [
    'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718', '74A901',
    '66A000', '529400', '3E8601', '207401', '056201', '004C00', '023B01',
    '012E01', '011D01', '011301'
  ],
};

// Create RGB visualization images for use as animation frames.
var rgbVis = NDVIcomp.map(function(img) {
  return img.visualize(visParams).clip(mask);
});

// Create animated gif
// Define GIF visualization parameters.
var gifParams = {
  'region': region,
  'dimensions': 600,
  'crs': 'EPSG:3857',
  'framesPerSecond': 10
};

// Print the GIF URL to the console.
print(rgbVis.getVideoThumbURL(gifParams));

Best Answer

You can map over the list of days-of-year shared by the 20-year NDVI mean collection and the single-year NDVI collection and subtract the NDVI mean from the single-year for each given doy. Below is the code to do this. Note that you need to set the doy property of the composite image for it to work (included in the code block).

//******Reduce composite groups: ******
// The result is a new collection, with one image per distinct DOY composite
// that represents its 20-year mean NDVI per pixel.

// Apply mean reduction among matching DOY collections.
var NDVImean = joinCol.map(function(img) {
  var doyCol = ee.ImageCollection.fromImages(
    img.get('doy_matches')
  );
  return doyCol.reduce(ee.Reducer.mean())
    // Set the day-of-year (doy) for the image 
    .set('doy', img.getNumber('doy'));
});

// Get day-of-year (doy) list.
var doyList = ee.List(NDVImean.aggregate_array('doy'));

// Map over the list of doys to calculate NDVI differences.
var ndviDifFromMeanList = doyList.map(function(doy){
  // Get the 20-year mean NDVI image for the given doy.
  var ndviMean = NDVImean.filter(ee.Filter.eq('doy', doy)).first();
  // Get the single-year NDVI image for the given doy.
  var ndviYear = distinctDOY.filter(ee.Filter.eq('doy', doy)).first();
  // Calculate the single-year NDVI difference from 20-year mean NDVI.
  return ndviYear.subtract(ndviMean)
    // Set the doy for the image.
    .set('doy', doy);
});

// Convert image list to image collection.
var ndviDifFromMeanCol = ee.ImageCollection.fromImages(ndviDifFromMeanList);

// Chart the difference for each doy.
var chart = ui.Chart.image.seriesByRegion({
  imageCollection: ndviDifFromMeanCol,
  regions: region,
  reducer: ee.Reducer.mean(),
  scale: 1e4,
  xProperty: 'doy'});

print(chart);

Code Editor script

Related Question