Announcement: All noncommercial projects registered to use Earth Engine before April 15, 2025 must verify noncommercial eligibility to maintain Earth Engine access.
Stay organized with collections
Save and categorize content based on your preferences.
Earth Engine supports array transformations such as transpose, inverse and pseudo-inverse.
As an example, consider an ordinary least squares (OLS) regression of a time series of
images. In the following example, an image with bands for predictors and a response is
converted to an array image, then “solved” to obtain least squares coefficients estimates
three ways. First, assemble the image data and convert to arrays:
Note that arraySlice() returns all the images in the time series for the
range of indices specified along the bandAxis (the 1-axis). At this point,
matrix algebra can be used to solve for the OLS coefficients:
Although this method works, it is inefficient and makes for difficult to read code. A
better way is to use the pseudoInverse() method
(matrixPseudoInverse() for an array image):
From a readability and computational efficiency perspective, the best way to get the OLS
coefficients is solve() (matrixSolve() for an array image). The
solve() function determines how to best solve the system from characteristics
of the inputs, using the pseudo-inverse for overdetermined systems, the inverse for square
matrices and special techniques for nearly singular matrices:
Examine the outputs of the three methods and observe that the resultant matrix of
coefficients is the same regardless of the solver. That solve() is flexible
and efficient makes it a good choice for general purpose linear modeling.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2024-04-29 UTC."],[[["\u003cp\u003eEarth Engine enables array transformations like transpose, inverse, and pseudo-inverse for advanced analysis, such as ordinary least squares (OLS) regression on image time series.\u003c/p\u003e\n"],["\u003cp\u003eUsers can convert image collections to arrays, extract predictors and responses, and apply matrix operations to derive regression coefficients.\u003c/p\u003e\n"],["\u003cp\u003eEarth Engine offers multiple methods for solving linear systems, with \u003ccode\u003esolve()\u003c/code\u003e being the most efficient and adaptable for various scenarios, including overdetermined systems and nearly singular matrices.\u003c/p\u003e\n"],["\u003cp\u003eArray images resulting from calculations can be transformed back into multi-band images for visualization and further analysis.\u003c/p\u003e\n"]]],["The content demonstrates ordinary least squares (OLS) regression on a Landsat 8 image time series using Earth Engine. Key actions include preparing images by masking and scaling, creating predictor and response variables (constant, trend, seasonal, and NDVI), and converting the collection to an array. OLS coefficients are then calculated using three methods: direct matrix operations, pseudo-inverse, and the `matrixSolve()` function. Finally, the coefficient array is projected and flattened into a multi-band image. `matrixSolve()` is highlighted as the most efficient and flexible method.\n"],null,["Earth Engine supports array transformations such as transpose, inverse and pseudo-inverse.\nAs an example, consider an ordinary least squares (OLS) regression of a time series of\nimages. In the following example, an image with bands for predictors and a response is\nconverted to an array image, then \"solved\" to obtain least squares coefficients estimates\nthree ways. First, assemble the image data and convert to arrays:\n\nCode Editor (JavaScript) \n\n```javascript\n// Scales and masks Landsat 8 surface reflectance images.\nfunction prepSrL8(image) {\n // Develop masks for unwanted pixels (fill, cloud, cloud shadow).\n var qaMask = image.select('QA_PIXEL').bitwiseAnd(parseInt('11111', 2)).eq(0);\n var saturationMask = image.select('QA_RADSAT').eq(0);\n\n // Apply the scaling factors to the appropriate bands.\n var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);\n var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);\n\n // Replace the original bands with the scaled ones and apply the masks.\n return image.addBands(opticalBands, null, true)\n .addBands(thermalBands, null, true)\n .updateMask(qaMask)\n .updateMask(saturationMask);\n}\n\n// Load a Landsat 8 surface reflectance image collection.\nvar collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')\n // Filter to get only two years of data.\n .filterDate('2019-04-01', '2021-04-01')\n // Filter to get only imagery at a point of interest.\n .filterBounds(ee.Geometry.Point(-122.08709, 36.9732))\n // Prepare images by mapping the prepSrL8 function over the collection.\n .map(prepSrL8)\n // Select NIR and red bands only.\n .select(['SR_B5', 'SR_B4'])\n // Sort the collection in chronological order.\n .sort('system:time_start', true);\n\n// This function computes the predictors and the response from the input.\nvar makeVariables = function(image) {\n // Compute time of the image in fractional years relative to the Epoch.\n var year = ee.Image(image.date().difference(ee.Date('1970-01-01'), 'year'));\n // Compute the season in radians, one cycle per year.\n var season = year.multiply(2 * Math.PI);\n // Return an image of the predictors followed by the response.\n return image.select()\n .addBands(ee.Image(1)) // 0. constant\n .addBands(year.rename('t')) // 1. linear trend\n .addBands(season.sin().rename('sin')) // 2. seasonal\n .addBands(season.cos().rename('cos')) // 3. seasonal\n .addBands(image.normalizedDifference().rename('NDVI')) // 4. response\n .toFloat();\n};\n\n// Define the axes of variation in the collection array.\nvar imageAxis = 0;\nvar bandAxis = 1;\n\n// Convert the collection to an array.\nvar array = collection.map(makeVariables).toArray();\n\n// Check the length of the image axis (number of images).\nvar arrayLength = array.arrayLength(imageAxis);\n// Update the mask to ensure that the number of images is greater than or\n// equal to the number of predictors (the linear model is solvable).\narray = array.updateMask(arrayLength.gt(4));\n\n// Get slices of the array according to positions along the band axis.\nvar predictors = array.arraySlice(bandAxis, 0, 4);\nvar response = array.arraySlice(bandAxis, 4);\n```\nPython setup\n\nSee the [Python Environment](/earth-engine/guides/python_install) page for information on the Python API and using\n`geemap` for interactive development. \n\n```python\nimport ee\nimport geemap.core as geemap\n```\n\nColab (Python) \n\n```python\nimport math\n\n\n# Scales and masks Landsat 8 surface reflectance images.\ndef prep_sr_l8(image):\n # Develop masks for unwanted pixels (fill, cloud, cloud shadow).\n qa_mask = image.select('QA_PIXEL').bitwiseAnd(int('11111', 2)).eq(0)\n saturation_mask = image.select('QA_RADSAT').eq(0)\n\n # Apply the scaling factors to the appropriate bands.\n optical_bands = image.select('SR_B.').multiply(0.0000275).add(-0.2)\n thermal_bands = image.select('ST_B.*').multiply(0.00341802).add(149.0)\n\n # Replace the original bands with the scaled ones and apply the masks.\n return (\n image.addBands(optical_bands, None, True)\n .addBands(thermal_bands, None, True)\n .updateMask(qa_mask)\n .updateMask(saturation_mask)\n )\n\n\n# Load a Landsat 8 surface reflectance image collection.\ncollection = (\n ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')\n # Filter to get only two years of data.\n .filterDate('2019-04-01', '2021-04-01')\n # Filter to get only imagery at a point of interest.\n .filterBounds(ee.Geometry.Point(-122.08709, 36.9732))\n # Prepare images by mapping the prep_sr_l8 function over the collection.\n .map(prep_sr_l8)\n # Select NIR and red bands only.\n .select(['SR_B5', 'SR_B4'])\n # Sort the collection in chronological order.\n .sort('system:time_start', True)\n)\n\n\n# This function computes the predictors and the response from the input.\ndef make_variables(image):\n # Compute time of the image in fractional years relative to the Epoch.\n year = ee.Image(image.date().difference(ee.Date('1970-01-01'), 'year'))\n # Compute the season in radians, one cycle per year.\n season = year.multiply(2 * math.pi)\n # Return an image of the predictors followed by the response.\n return (\n image.select()\n .addBands(ee.Image(1)) # 0. constant\n .addBands(year.rename('t')) # 1. linear trend\n .addBands(season.sin().rename('sin')) # 2. seasonal\n .addBands(season.cos().rename('cos')) # 3. seasonal\n .addBands(image.normalizedDifference().rename('NDVI')) # 4. response\n .toFloat()\n )\n\n\n# Define the axes of variation in the collection array.\nimage_axis = 0\nband_axis = 1\n\n# Convert the collection to an array.\narray = collection.map(make_variables).toArray()\n\n# Check the length of the image axis (number of images).\narray_length = array.arrayLength(image_axis)\n# Update the mask to ensure that the number of images is greater than or\n# equal to the number of predictors (the linear model is solvable).\narray = array.updateMask(array_length.gt(4))\n\n# Get slices of the array according to positions along the band axis.\npredictors = array.arraySlice(band_axis, 0, 4)\nresponse = array.arraySlice(band_axis, 4)\n```\n\nNote that `arraySlice()` returns all the images in the time series for the\nrange of indices specified along the `bandAxis` (the 1-axis). At this point,\nmatrix algebra can be used to solve for the OLS coefficients:\n\nCode Editor (JavaScript) \n\n```javascript\n// Compute coefficients the hard way.\nvar coefficients1 = predictors.arrayTranspose().matrixMultiply(predictors)\n .matrixInverse().matrixMultiply(predictors.arrayTranspose())\n .matrixMultiply(response);\n```\nPython setup\n\nSee the [Python Environment](/earth-engine/guides/python_install) page for information on the Python API and using\n`geemap` for interactive development. \n\n```python\nimport ee\nimport geemap.core as geemap\n```\n\nColab (Python) \n\n```python\n# Compute coefficients the hard way.\ncoefficients_1 = (\n predictors.arrayTranspose()\n .matrixMultiply(predictors)\n .matrixInverse()\n .matrixMultiply(predictors.arrayTranspose())\n .matrixMultiply(response)\n)\n```\n\nAlthough this method works, it is inefficient and makes for difficult to read code. A\nbetter way is to use the `pseudoInverse()` method\n(`matrixPseudoInverse()` for an array image):\n\nCode Editor (JavaScript) \n\n```javascript\n// Compute coefficients the easy way.\nvar coefficients2 = predictors.matrixPseudoInverse()\n .matrixMultiply(response);\n```\nPython setup\n\nSee the [Python Environment](/earth-engine/guides/python_install) page for information on the Python API and using\n`geemap` for interactive development. \n\n```python\nimport ee\nimport geemap.core as geemap\n```\n\nColab (Python) \n\n```python\n# Compute coefficients the easy way.\ncoefficients_2 = predictors.matrixPseudoInverse().matrixMultiply(response)\n```\n\nFrom a readability and computational efficiency perspective, the best way to get the OLS\ncoefficients is `solve()` (`matrixSolve()` for an array image). The\n`solve()` function determines how to best solve the system from characteristics\nof the inputs, using the pseudo-inverse for overdetermined systems, the inverse for square\nmatrices and special techniques for nearly singular matrices:\n\nCode Editor (JavaScript) \n\n```javascript\n// Compute coefficients the easiest way.\nvar coefficients3 = predictors.matrixSolve(response);\n```\nPython setup\n\nSee the [Python Environment](/earth-engine/guides/python_install) page for information on the Python API and using\n`geemap` for interactive development. \n\n```python\nimport ee\nimport geemap.core as geemap\n```\n\nColab (Python) \n\n```python\n# Compute coefficients the easiest way.\ncoefficients_3 = predictors.matrixSolve(response)\n```\n\nTo get a multi-band image, project the array image into a lower dimensional space, then\nflatten it:\n\nCode Editor (JavaScript) \n\n```javascript\n// Turn the results into a multi-band image.\nvar coefficientsImage = coefficients3\n // Get rid of the extra dimensions.\n .arrayProject([0])\n .arrayFlatten([\n ['constant', 'trend', 'sin', 'cos']\n]);\n```\nPython setup\n\nSee the [Python Environment](/earth-engine/guides/python_install) page for information on the Python API and using\n`geemap` for interactive development. \n\n```python\nimport ee\nimport geemap.core as geemap\n```\n\nColab (Python) \n\n```python\n# Turn the results into a multi-band image.\ncoefficients_image = (\n coefficients_3\n # Get rid of the extra dimensions.\n .arrayProject([0]).arrayFlatten([['constant', 'trend', 'sin', 'cos']])\n)\n```\n\nExamine the outputs of the three methods and observe that the resultant matrix of\ncoefficients is the same regardless of the solver. That `solve()` is flexible\nand efficient makes it a good choice for general purpose linear modeling."]]