Janus / Guides / Trend test

Trends · regions and classes

Is the region really trending?

A map of the pixels with a significant trend is not evidence that a region is greening or browning. The AR(1) trend tests one pixel's series. The Trend test, by PARTS (Ives et al. 2021), tests a whole region, or each class of a land-cover map, with p-values that account for neighbouring pixels moving together. This guide explains both from the start, on 41 years of Landsat.

01The idea

A common way to describe a region's trend is to test every pixel and report the share of pixels with a significant trend, or to average the pixels' slopes and test the mean as if the pixels were independent measurements. Both mislead, for two reasons.

  • Thousands of tests. At a 5% level, about 5% of the pixels of a region with no trend at all come out significant.
  • Neighbours move together. A drought or a wet year affects a whole landscape. Ten thousand correlated pixels carry far less information than ten thousand independent ones. A test that treats them as independent is far too confident.
A simulated map with no trend: each pixel's slope in orange and teal patches; the 6.6% of pixels with p below 0.05, in clusters; a naive test of the mean giving p = 1.5 × 10^-213 and PARTS giving p = 0.26.
A map with no trend at all. 10,000 simulated pixels whose year-to-year noise is shared by neighbours. 6.6% of the pixels test significant, in patches. A t-test of their slopes as if independent says p = 1.5 × 10−213, a confident and wrong "yes". PARTS says p = 0.26: no evidence of a trend, which is right.

PARTS (Partitioned Autoregressive Time Series) answers the regional question with one test instead of thousands. It measures how strongly pixels are correlated at each distance and builds that into the test. Janus has it in two places:

WhereAnswersGives
Tools → Temporal dynamics → AR(1) trendDoes this pixel's series have a trend, allowing for the persistence of its errors from year to year?On the chart: the line and its test. On the image: maps of the slope, p, t, its standard error, ρ.
View → Analysis → Trend testDoes the mean trend of the ROI, the visible area or the image differ from 0? Of each class of a land-cover layer? Do the classes differ?A table of estimates, standard errors, t and p, and the chi-square test of the classes.
Trend
The slope of a straight line through a pixel's series, in index units per year.
AR(1) ρ
How much of one year's departure from the line carries over to the next year (0: none, close to 1: long runs above or below the line).
Range
The distance over which pixels' departures stay correlated: the correlation is 0.37 at one range and 0.05 at three.
Nugget
The share of a pixel's variation that is its own, shared with no neighbour (sensor noise, a field ploughed one year).

02The example

41 annual Landsat medoid composites of a vegetation index, 1985 to 2025, over the coast of Sergipe and Alagoas in north-eastern Brazil, around the lower São Francisco river (path/row 215/067). One GeoTIFF per year, named landsat_medoid_1985.tif and so on (Janus reads the years from the names), in EPSG:4326 at about 30 m, 7478 × 7319 pixels.

The files mark the sea and the area outside the scene with 0 but declare no nodata value. A pixel that is 0 is then a value, and the sea would enter the test. The guide opens a VRT of each year with 0 declared as no data instead (a few lines of GDAL, in tools/make_parts_guide.py). The zones used in section 08 come from the same script.

Fill values

Before testing trends, check that water, the sea and the outside of the scene are no data, not 0. A layer's Display panel shows its nodata value, and GDAL's gdalbuildvrt -srcnodata 0 or gdal_edit -a_nodata 0 sets one.

03One pixel: the AR(1) trend

Tools → Temporal dynamics → AR(1) trend fits each series on the chart (the cursor, the pins and the ROI mean) with a straight line whose errors follow an AR(1) process, by REML, as remotePARTS's fitAR. The line is drawn on the chart and its test goes into the Statistics table.

The AR(1) trend window open, a pin on a pixel near the river, its 41-year series falling from about 0.1 to 0 on the chart, and the Statistics table.
A pin near the river. The index was high in the late 1980s, then low for decades.
The AR(1) trend window: trend per year, 0 harmonics, missing dates exact, significance level 0.05, minimum 4 valid observations, run on the whole image estimated at about 12 seconds.
The window, at its defaults.
The Statistics table of the pin: OLS trend -0.003493 per year, then AR(1) trend: no trend, not significant, p-value 0.055635, slope -0.003254 per year, standard error 0.0016494, t -1.9728, AR(1) rho 0.5976.
OLS and AR(1), side by side.

The ordinary least-squares line (OLS trend, −0.0035 per year) and the AR(1) line (−0.0033 per year) have nearly the same slope. The residuals of this pixel, however, come in long runs of years above or below the line: ρ = 0.60. Successive years that resemble each other carry less information than independent years. The AR(1) fit accounts for that, and its standard error (0.0016) is about twice what OLS assumes. The verdict is no trend (p = 0.056). Zeit, on the nearest pixel of a UTM grid, gives p = 0.00023 by OLS and 0.05 by AR(1).

ParameterDefaultWhat it does
Trend perYearThe slope per year (decimal years of the dates), or per time step (t = 1, 2, …, as remotePARTS).
Harmonics0Pairs of seasonal harmonics fitted with the line, for series with several dates a year (2 is typical for 16-day or monthly data).
Missing datesExactExact: two observations k dates apart are correlated by ρk, so a missing year counts. Ignore: the valid observations are taken as consecutive, as remotePARTS does.
Significance level0.05The level of the test, for the verdict and the map of the significant slope.
Min valid observations4Pixels with fewer valid observations are not fitted.

04The map of trends

The same window runs on the image (Run on the raster): whole image, visible area or ROI. Over an ROI of 700 × 700 pixels (21 km across), the run took 2 seconds. The first map shows the slope where it is significant. The others are the slope, the p-value, t, the standard error, ρ, the intercept and the residual SD.

The significant slope over a 700 by 700 pixel ROI: many patches of significant greening, a few of browning; the Tasks window with the run done in 2 seconds; the ROI mean series on the chart rising slowly with large year-to-year swings.
The significant slope in the ROI. Patches of significant greening cover about a quarter of the ROI. Below, the ROI mean series swings from year to year far more than it rises.

A quarter of the ROI is significantly greening, and the usual next sentence would be "the area is greening". The ROI mean series on the chart suggests caution: it rises by 0.0013 per year (OLS trend), but its year-to-year swings are much larger. Its own AR(1) test, in the Statistics table, does not support a trend:

The Statistics table of the ROI mean: OLS trend 0.0012527 per year, then AR(1) trend: no trend, p-value 0.093763, slope 0.0012873 per year, standard error 0.00074941, rho 0.14619.
The ROI mean's AR(1) test: +0.0013 ± 0.0007 per year, p = 0.094.

Testing the mean series is a fair check, but it only answers the question for one region, and it throws away information when the pixels' correlation is short compared with the region. The Trend test does better.

05The Trend test

View → Analysis, tab Trend test. Choose the Region (the ROI, the visible area or the whole image), leave Classes at None (the region's mean), and press Run the test. The test runs on a thread, with its progress and a Cancel button. Here it took 13 to 17 seconds on a 20-thread CPU.

The ROI over the image and, in the Analysis panel's Trend test tab, the region's result: 490000 pixels, trend 0.001538 per year, SE 0.001536, t 1.002, p 0.3165, 26% of pixels significantly up.
The Trend test on the ROI, next to the ROI mean's statistics.
The Trend test tab: region ROI, no classes, trend per year, 0 harmonics, missing dates kept apart, partitions of 1500 pixels, at most 100, nugget by maximum likelihood; the result line and the table.
The result. Spatial correlation exp(−d / 21.94 km), nugget 0.11. Regional trend +0.0015 ± 0.0015 per year, p = 0.32.

Line by line:

  • 490,000 pixels read, 490,000 with a trend, 150,000 tested in 100 partitions of 1500. Every pixel of the ROI got its AR(1) trend. 100 random groups of 1500 pixels were tested (see section 06).
  • exp(−d / 21.94 km), nugget 0.1106. Pixels' departures from their trends stay correlated over about 22 km, as wide as the ROI itself. Only 11% of a pixel's variation is its own.
  • Region: 0.001538 /yr, SE 0.001536, t 1.002, p 0.3165. The mean trend is positive, but its standard error is as large as the estimate: no evidence of a regional trend.
  • Pixels up 125,871 (26%), down 2,088 (0%). The pixels' own AR(1) tests, counted for comparison. They are not a regional test.

A quarter of the pixels significantly greening, and no regional trend, are not a contradiction. The whole ROI rises and falls together with wet and dry years, so its 490,000 pixels behave almost like a single pixel observed 41 times. Its mean rose over the period, but no more than such a strongly varying series often does by chance. The ROI mean's own test (p = 0.094) agrees.

Run again, the test gives exactly the same numbers: its random draws use a fixed seed.

06How it works

The Trend test takes four steps.

  1. Each pixel's trendThe AR(1) trend of every pixel read, as in section 03.
  2. The correlationThe residuals of 1000 sampled pixels, correlated pair by pair and fitted against distance.
  3. PartitionsThe pixels drawn into random groups; a regression of their trends that accounts for the correlation, in each.
  4. CombinedThe groups' estimates averaged; a standard error that accounts for the groups being correlated too.
  5. TestsA t-test of each mean against 0, and with classes, a chi-square test that they differ.
  6. CountsThe pixels' own significant trends, for comparison.

Step 2: correlation against distance

For each pair of the 1000 sampled pixels, Janus takes the correlation of their two residual series (observed minus the fitted line) and the distance between them. Through all those pairs (about half a million), it fits the curve exp(−d / range).

Correlation of the residual series of pairs of pixels against their distance, on a simulated map: the mean correlations fall from about 0.65 to 0 by 1.5 km, the fitted exponential close to the true curve.
A simulated map with a true correlation of 0.8 exp(−d / 450 m), that is a range of 450 m and a nugget of 0.2. The grey cloud is every pair of sampled pixels, the dots the mean at each distance. The fitted range is 366 m.

The curve does not include the nugget, which Janus estimates in step 3. As a result, a large nugget pulls the fitted range down: 366 m instead of 450 m here. Section 07 shows what that does to the p-values.

Real data rarely look this clean. In the ROI of this guide, the mean correlation drops to about 0.75 between close neighbours and then stays near 0.6 at every distance up to 28 km (Zeit's documentation plots it), because the whole area shares one interannual signal. An exponential curve fits that only roughly, which is why the fitted range (22 km) is as wide as the ROI.

Step 3: a regression in partitions

The right way to average correlated pixels is a generalised least squares regression (GLS). It gives less weight to pixels that repeat their neighbours' information. On 490,000 pixels, however, GLS would need a matrix of 490,000 × 490,000 numbers, about 2 TB. PARTS therefore draws the pixels into random partitions of 1500, fits a GLS in each (a matrix of 18 MB), and estimates each partition's nugget by maximum likelihood.

Three random partitions of 600 pixels on a simulated map of 60 by 60 pixels, in blue, orange and green dots spread over the whole map.
Three of six random partitions of a simulated map of 60 × 60 pixels. Each one is spread over the whole map.

On the ROI of this guide, run in Zeit, the 100 partitions' estimates agreed closely (standard deviation 0.00015). Treated as 100 independent measurements, they would give a standard error of about 0.00002 and a p-value close to 0. PARTS gives 0.0017, because every partition carries the same correlated signal.

Step 4: combining the partitions

The partitions are not independent, since a pixel in one partition has neighbours in the others. PARTS computes, from the fitted correlation, how correlated the estimates of 6 partitions are (15 pairs), and combines all the partitions' standard errors with that correlation. When the partitions are almost fully correlated, as here, the combined standard error stays close to a single partition's, however many partitions are added. That is why 100 partitions are enough. With classes, the partitions' F statistics are combined into a chi-square test of whether the classes differ, again allowing for the correlation (Imhof's method).

07How far to trust it

A test is calibrated if it says p < 0.05 in 5% of the cases where there is nothing to find. To check this, tools/make_parts_guide.py simulated 300 maps with no trend, then 100 maps for each of several real trends, and ran both tests on each.

Left: p-values over 300 maps with no trend, piled near 0 for the naive test and spread out for PARTS. Middle: the share of significant pixels per map, from 2% to over 20%. Right: how often each test detects a real trend as it grows.
Left: with no trend, the naive test says p < 0.05 on 94% of the maps and PARTS on 14% (6.3% when given the true range instead of fitting it, a run not drawn here). Middle: the share of significant pixels on maps with no trend: 7.5% on average, above 16% on one map in twenty. Right: PARTS detects 57% of trends of 0.0005 per year and 90% of trends of 0.0008 per year (noise SD 0.03). The naive test says "yes" whatever the trend.
  • PARTS removes most of the problem: 14% false alarms instead of 94%.
  • It is not perfect. The range fitted in step 2 tends to be too short (365 m against the true 450 m, the median over these maps), so the p-values are somewhat too small: 6.3% of the maps gave p < 0.01. Treat p between 0.01 and 0.05 as weak evidence.
  • The correlation model matters. When the correlation does not fall off like an exponential (smooth, Gaussian-like fields, or a regional signal that stays high at every distance, as in this example), the answer depends more on the settings. Try a second partition size: in Zeit, the same ROI gave p = 0.13 with partitions of 500 and 0.54 with 3000, the same conclusion either way.

08Classes

With a categorical layer open on the same grid (a land-cover map, zones), choose it under Classes. Each class gets its own mean trend, tested against 0, and a last line says whether the classes' trends differ. The layer's class names and colours come from its legend (Display → Categorical (classes)). Pixels outside every class are left out.

Here, the zones are the distance to the sea, made by tools/make_parts_guide.py data: coastal (under 40 km), transition (40 to 100 km) and interior (beyond 100 km), with water as no class. The region is the whole image.

The zones layer over the whole image, three bands of distance to the sea in shades of teal, and the Trend test with Classes set to zones.
Zones by distance to the sea, and the Trend test by class over the whole image.
The Trend test's table by zone: coastal 0.0006129 per year, p 0.2309; transition 0.0004015, p 0.4197; interior 0.0005443, p 0.2702; no evidence that the classes' trends differ, p = 0.09232.
The table by zone (taken with the map in a window of its own, where Visible area falls back to the whole image). 499,086 pixels read, one in 10.5 × 10.5; range 84.88 km, nugget 0.60.
ZonePixelsTrend (/yr)SEpPixels up / down
Coastal (< 40 km)67,0130.00061290.00051160.230920% / 8%
Transition (40–100 km)143,5420.00040150.00049760.41973% / 5%
Interior (> 100 km)248,8060.00054430.00049370.270211% / 1%

No zone's trend differs from 0, and the difference between the zones is not significant at 0.05 (p = 0.092, F = 1.52 with 2 and 1498 degrees of freedom).

The same question asked in Zeit, on the scene averaged to 300 m pixels instead of one 30 m pixel in 10.5 × 10.5, gave the same picture for each zone (p from 0.29 to 0.47) but p = 0.028 for the difference between zones. The two reads of the image differ (averaged pixels are smoother, so the nugget is lower: 0.32 against 0.60), and a p between 0.03 and 0.09 is borderline either way. Report such a result as weak evidence that the zones differ, and say how the image was read.

Differences can be easier to detect than trends

The regional signal (droughts, wet years) is shared by every class. It makes each class's mean uncertain, but it cancels when classes are compared. On real data, the chi-square test can be significant while no class differs from 0.

A class needs about 10 pixels per partition: classes with fewer than 10 × pixels / partition size pixels are left out and listed under the table. Merge small classes, or test a smaller region where they matter more.

09Reading and reporting

ResultMeansDoes not mean
Region: p < 0.05The region's mean trend differs from 0, in the direction of its sign.That most pixels trend that way.
Region: p ≥ 0.05No evidence of a regional mean trend.That nothing trends anywhere: local trends can be real and average out.
A class: p < 0.05That class's mean trend differs from 0.That it differs from the other classes.
Classes differ: p < 0.05The classes' mean trends are not all the same.Which ones differ: compare the estimates, or test two classes alone.
Pixels up 26%26% of the pixels' own tests have p < 0.05 and a positive slope.That 26% of the area is changing, or that the region is.

For a report, give the estimate with its standard error (or the interval estimate ± 1.96 SE), t and p, the region, and the settings. Copy CSV copies the table (class, pixels, trend_per_yr, se, t, p, significant_up, significant_down, and the chi-square p with classes). For example:

A sentence for the methods and results

Per-pixel trends of the annual index (1985–2025) were fitted by REML with AR(1) errors, and the regional mean trend was tested with PARTS (Ives et al. 2021) in Janus (exponential spatial correlation fitted to 1000 pixels, range 21.9 km; 100 random partitions of 1500 pixels; maximum-likelihood nugget). The mean trend of the area was +0.0015 ± 0.0015 yr−1 (t = 1.00, p = 0.32): not significantly different from 0, although 26% of the pixels had a significant positive trend individually.

Pitfalls

Fill values read as data (the sea as 0). Water, cities and bare ground mixed into a vegetation trend: mask them with the ROI, or make them classes of their own. Choosing the region, the partition size or the settings until p falls below 0.05. Reading the share of significant pixels as a regional result.

10Settings and limits

SettingDefaultWhat it does
RegionROIThe ROI (its polygon), the visible area, or the whole image. Without an ROI, the whole image.
ClassesNoneA categorical layer on the same grid, at its current date: a mean per class, and the test that they differ.
Trend peryearPer year of the dates, or per time step.
Harmonics0Seasonal harmonics fitted with each pixel's line (sub-annual series).
Missing dates kept apartonObservations k dates apart correlated by ρk; off, the valid ones are taken as consecutive (as remotePARTS).
Partitions of1500 pixelsThe partition size (remotePARTS's authors use 1500 to 3000). Time grows as its cube, memory as its square: 36 MB per thread at 1500.
at most100The number of partitions. More add little once they are strongly correlated with each other.
Nugget by maximum likelihoodonEach partition's own nugget; off, a nugget of 0 (every pixel's noise fully shared with its neighbours, which is rarely true).
  • Large regions are read at full resolution up to 500,000 pixels (and 40 million values), and beyond that one pixel in k × k (nearest), as the whole image above: one in 10.5 × 10.5.
  • Distances are in km. A projected CRS is used as it is. A geographic CRS (longitude and latitude, as here) is projected equirectangularly at the region's mean latitude. Without a georeference, distances are in pixels.
  • Fixed choices: 1000 pixels sampled to fit the correlation, an exponential correlation, 6 partitions crossed, seed 1, the pixels' own tests at 0.05, and pixels with at least the design's columns + 2 valid observations. Zeit exposes all of them in Python.
  • The active layer is tested: a series of values with 4 dates or more.

Reproduce it

python tools/make_parts_guide.py data SERIES out writes the VRTs and the zones; python tools/make_parts_guide.py figures site/assets/img/trend-test the figures of the method. The screenshots come from janus out/series --interface modern with --roi 1000,500,1700,1200 --analysis trend,run, or --layer out/zones.tif --analysis trend,run,classes (see site/README.md).

11What next

  • The method in depth, covariates, sensitivity checks and parity with remotePARTS: Regional Trends (PARTS) in Zeit's documentation, with zeit.ar_trend and zeit.parts in Python.
  • A non-parametric pixel trend: Tools → Temporal dynamics → Mann-Kendall, Sen's slope with the Hamed-Rao correction.
  • When the land changed, not only whether it trends: LandTrendr and BFAST.
  • Classes over time: View → Analysis → Classes.

12References

  • Ives, A. R., Zhu, L., Wang, F., Zhu, J., Morrow, C. J., Radeloff, V. C. (2021). Statistical inference for trends in spatiotemporal data. Remote Sensing of Environment, 266, 112678. doi:10.1016/j.rse.2021.112678
  • Morrow, C. J., Ives, A. R. remotePARTS: Spatiotemporal autoregression analyses for large data sets. R package, version 1.0.4. CRAN
  • Imhof, J. P. (1961). Computing the distribution of quadratic forms in normal variables. Biometrika, 48(3/4), 419–426.
  • Zeit: the library behind the tools of Janus, where ar_trend and parts implement these methods.