Transformaciones de arrays

Earth Engine admite transformaciones de arrays, como transposición, inversa y pseudoinversa. A modo de ejemplo, considera una regresión de mínimos cuadrados ordinarios (OLS) de una serie temporal de imágenes. En el siguiente ejemplo, una imagen con bandas para predictores y una respuesta se convierte en una imagen de array y, luego, se “resuelve” para obtener estimaciones de coeficientes de mínimos cuadrados de tres maneras. Primero, reúne los datos de la imagen y conviértelos en arrays:

Editor de código (JavaScript)

// Scales and masks Landsat 8 surface reflectance images.
function prepSrL8(image) {
  // Develop masks for unwanted pixels (fill, cloud, cloud shadow).
  var qaMask = image.select('QA_PIXEL').bitwiseAnd(parseInt('11111', 2)).eq(0);
  var saturationMask = image.select('QA_RADSAT').eq(0);

  // Apply the scaling factors to the appropriate bands.
  var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
  var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);

  // Replace the original bands with the scaled ones and apply the masks.
  return image.addBands(opticalBands, null, true)
      .addBands(thermalBands, null, true)
      .updateMask(qaMask)
      .updateMask(saturationMask);
}

// Load a Landsat 8 surface reflectance image collection.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
  // Filter to get only two years of data.
  .filterDate('2019-04-01', '2021-04-01')
  // Filter to get only imagery at a point of interest.
  .filterBounds(ee.Geometry.Point(-122.08709, 36.9732))
  // Prepare images by mapping the prepSrL8 function over the collection.
  .map(prepSrL8)
  // Select NIR and red bands only.
  .select(['SR_B5', 'SR_B4'])
  // Sort the collection in chronological order.
  .sort('system:time_start', true);

// This function computes the predictors and the response from the input.
var makeVariables = function(image) {
  // Compute time of the image in fractional years relative to the Epoch.
  var year = ee.Image(image.date().difference(ee.Date('1970-01-01'), 'year'));
  // Compute the season in radians, one cycle per year.
  var season = year.multiply(2 * Math.PI);
  // Return an image of the predictors followed by the response.
  return image.select()
    .addBands(ee.Image(1))                                  // 0. constant
    .addBands(year.rename('t'))                             // 1. linear trend
    .addBands(season.sin().rename('sin'))                   // 2. seasonal
    .addBands(season.cos().rename('cos'))                   // 3. seasonal
    .addBands(image.normalizedDifference().rename('NDVI'))  // 4. response
    .toFloat();
};

// Define the axes of variation in the collection array.
var imageAxis = 0;
var bandAxis = 1;

// Convert the collection to an array.
var array = collection.map(makeVariables).toArray();

// Check the length of the image axis (number of images).
var arrayLength = array.arrayLength(imageAxis);
// Update the mask to ensure that the number of images is greater than or
// equal to the number of predictors (the linear model is solvable).
array = array.updateMask(arrayLength.gt(4));

// Get slices of the array according to positions along the band axis.
var predictors = array.arraySlice(bandAxis, 0, 4);
var response = array.arraySlice(bandAxis, 4);

Configuración de Python

Consulta la página Entorno de Python para obtener información sobre la API de Python y el uso de geemap para el desarrollo interactivo.

import ee
import geemap.core as geemap

Colab (Python)

import math


# Scales and masks Landsat 8 surface reflectance images.
def prep_sr_l8(image):
  # Develop masks for unwanted pixels (fill, cloud, cloud shadow).
  qa_mask = image.select('QA_PIXEL').bitwiseAnd(int('11111', 2)).eq(0)
  saturation_mask = image.select('QA_RADSAT').eq(0)

  # Apply the scaling factors to the appropriate bands.
  optical_bands = image.select('SR_B.').multiply(0.0000275).add(-0.2)
  thermal_bands = image.select('ST_B.*').multiply(0.00341802).add(149.0)

  # Replace the original bands with the scaled ones and apply the masks.
  return (
      image.addBands(optical_bands, None, True)
      .addBands(thermal_bands, None, True)
      .updateMask(qa_mask)
      .updateMask(saturation_mask)
  )


# Load a Landsat 8 surface reflectance image collection.
collection = (
    ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
    # Filter to get only two years of data.
    .filterDate('2019-04-01', '2021-04-01')
    # Filter to get only imagery at a point of interest.
    .filterBounds(ee.Geometry.Point(-122.08709, 36.9732))
    # Prepare images by mapping the prep_sr_l8 function over the collection.
    .map(prep_sr_l8)
    # Select NIR and red bands only.
    .select(['SR_B5', 'SR_B4'])
    # Sort the collection in chronological order.
    .sort('system:time_start', True)
)


# This function computes the predictors and the response from the input.
def make_variables(image):
  # Compute time of the image in fractional years relative to the Epoch.
  year = ee.Image(image.date().difference(ee.Date('1970-01-01'), 'year'))
  # Compute the season in radians, one cycle per year.
  season = year.multiply(2 * math.pi)
  # Return an image of the predictors followed by the response.
  return (
      image.select()
      .addBands(ee.Image(1))  # 0. constant
      .addBands(year.rename('t'))  # 1. linear trend
      .addBands(season.sin().rename('sin'))  # 2. seasonal
      .addBands(season.cos().rename('cos'))  # 3. seasonal
      .addBands(image.normalizedDifference().rename('NDVI'))  # 4. response
      .toFloat()
  )


# Define the axes of variation in the collection array.
image_axis = 0
band_axis = 1

# Convert the collection to an array.
array = collection.map(make_variables).toArray()

# Check the length of the image axis (number of images).
array_length = array.arrayLength(image_axis)
# Update the mask to ensure that the number of images is greater than or
# equal to the number of predictors (the linear model is solvable).
array = array.updateMask(array_length.gt(4))

# Get slices of the array according to positions along the band axis.
predictors = array.arraySlice(band_axis, 0, 4)
response = array.arraySlice(band_axis, 4)

Ten en cuenta que arraySlice() muestra todas las imágenes de la serie temporal para el rango de índices especificado a lo largo de bandAxis (el eje 1). En este punto, se puede usar el álgebra matricial para resolver los coeficientes de OLS:

Editor de código (JavaScript)

// Compute coefficients the hard way.
var coefficients1 = predictors.arrayTranspose().matrixMultiply(predictors)
  .matrixInverse().matrixMultiply(predictors.arrayTranspose())
    .matrixMultiply(response);

Configuración de Python

Consulta la página Entorno de Python para obtener información sobre la API de Python y el uso de geemap para el desarrollo interactivo.

import ee
import geemap.core as geemap

Colab (Python)

# Compute coefficients the hard way.
coefficients_1 = (
    predictors.arrayTranspose()
    .matrixMultiply(predictors)
    .matrixInverse()
    .matrixMultiply(predictors.arrayTranspose())
    .matrixMultiply(response)
)

Si bien este método funciona, es ineficiente y dificulta la lectura del código. Una mejor manera es usar el método pseudoInverse() (matrixPseudoInverse() para una imagen de array):

Editor de código (JavaScript)

// Compute coefficients the easy way.
var coefficients2 = predictors.matrixPseudoInverse()
  .matrixMultiply(response);

Configuración de Python

Consulta la página Entorno de Python para obtener información sobre la API de Python y el uso de geemap para el desarrollo interactivo.

import ee
import geemap.core as geemap

Colab (Python)

# Compute coefficients the easy way.
coefficients_2 = predictors.matrixPseudoInverse().matrixMultiply(response)

Desde una perspectiva de legibilidad y eficiencia computacional, la mejor manera de obtener los coeficientes de OLS es solve() (matrixSolve() para una imagen de array). La función solve() determina cómo resolver mejor el sistema a partir de las características de las entradas, con el pseudoinverso para sistemas sobredeterminados, el inverso para matrices cuadradas y técnicas especiales para matrices casi singulares:

Editor de código (JavaScript)

// Compute coefficients the easiest way.
var coefficients3 = predictors.matrixSolve(response);

Configuración de Python

Consulta la página Entorno de Python para obtener información sobre la API de Python y el uso de geemap para el desarrollo interactivo.

import ee
import geemap.core as geemap

Colab (Python)

# Compute coefficients the easiest way.
coefficients_3 = predictors.matrixSolve(response)

Para obtener una imagen multibanda, proyecta la imagen del array en un espacio de dimensiones más bajas y, luego, aplánala:

Editor de código (JavaScript)

// Turn the results into a multi-band image.
var coefficientsImage = coefficients3
  // Get rid of the extra dimensions.
  .arrayProject([0])
  .arrayFlatten([
    ['constant', 'trend', 'sin', 'cos']
]);

Configuración de Python

Consulta la página Entorno de Python para obtener información sobre la API de Python y el uso de geemap para el desarrollo interactivo.

import ee
import geemap.core as geemap

Colab (Python)

# Turn the results into a multi-band image.
coefficients_image = (
    coefficients_3
    # Get rid of the extra dimensions.
    .arrayProject([0]).arrayFlatten([['constant', 'trend', 'sin', 'cos']])
)

Examina los resultados de los tres métodos y observa que la matriz resultante de los coeficientes es la misma independientemente del solucionador. El hecho de que solve() sea flexible y eficiente lo convierte en una buena opción para el modelado lineal de uso general.