The area of land cover change obtained from a map may differ greatly from the true area of change because of misclassifications in your map. In regard of this, Olofsson et al. published two papers in 2013 and 2014, introducing an error-adjusted estimator of area, that can be easily produced once an accuracy assessment has been performed and an error matrix was constructed. Furthermore, they provide a confidence interval for the area of land change to quantify the uncertainty of the estimations. The following script reproduces the findings of Olofsson et al. 2014.
What do you need?
- your classification map
- your training shapefile
- your validation shapefile
What do you get?
- error-adjusted area estimates of your classes
- confidence intervals for all accuracies
The script is commented comprehensively and the equations from the paper are marked accordingly in the comments of this script.
# area adjusted accuracy assessment # calculated as in Olofsson et al. 2014 library(raster) # import classification image and train and validation shapefiles setwd("/media/sf_exchange/landsatdata/") img.class <- raster("classification_RF.tif") shp.train <- shapefile("training_data.shp") shp.valid <- shapefile("validation_RF.shp") # create regular accuracy matrix confmat <- table(as.factor(extract(img.class, shp.valid)), as.factor(shp.valid$validclass)) # get number of pixels per class and convert in km² imgVal <- as.factor(getValues(img.class)) nclass <- length(unique(shp.train$classes)) maparea <- sapply(1:nclass, function(x) sum(imgVal == x)) maparea <- maparea * res(img.class)[1] ^ 2 / 1000000 # set confidence interval conf <- 1.96 # total map area A <- sum(maparea) # proportion of area mapped as class i W_i <- maparea / A # number of reference points per class n_i <- rowSums(confmat) # population error matrix (Eq.4) p <- W_i * confmat / n_i p[is.na(p)] <- 0 # area estimation p_area <- colSums(p) * A # area estimation confidence interval (Eq.10) p_area_CI <- conf * A * sqrt(colSums((W_i * p - p ^ 2) / (n_i - 1))) # overall accuracy (Eq.1) OA <- sum(diag(p)) # producers accuracy (Eq.2) PA <- diag(p) / colSums(p) # users accuracy (Eq.3) UA <- diag(p) / rowSums(p) # overall accuracy confidence interval (Eq.5) OA_CI <- conf * sqrt(sum(W_i ^ 2 * UA * (1 - UA) / (n_i - 1))) # user accuracy confidence interval (Eq.6) UA_CI <- conf * sqrt(UA * (1 - UA) / (n_i - 1)) # producer accuracy confidence interval (Eq.7) N_j <- sapply(1:nclass, function(x) sum(maparea / n_i * confmat[ , x]) ) tmp <- sapply(1:nclass, function(x) sum(maparea[-x] ^ 2 * confmat[-x, x] / n_i[-x] * ( 1 - confmat[-x, x] / n_i[-x]) / (n_i[-x] - 1)) ) PA_CI <- conf * sqrt(1 / N_j ^ 2 * (maparea ^ 2 * ( 1 - PA ) ^ 2 * UA * (1 - UA) / (n_i - 1) + PA ^ 2 * tmp)) # gather results result <- matrix(c(p_area, p_area_CI, PA * 100, PA_CI * 100, UA * 100, UA_CI * 100, c(OA * 100, rep(NA, nclass-1)), c(OA_CI * 100, rep(NA, nclass-1))), nrow = nclass) result <- round(result, digits = 2) rownames(result) <- levels(as.factor(shp.train$classes)) colnames(result) <- c("km²", "km²±", "PA", "PA±", "UA", "UA±", "OA", "OA±") class(result) <- "table" result
library(raster) setwd("/media/sf_exchange/landsatdata/") img.class <- raster("classification_RF.tif") shp.train <- shapefile("training_data.shp") shp.valid <- shapefile("validation_RF.shp") confmat <- table(as.factor(extract(img.class, shp.valid)), as.factor(shp.valid$validclass)) confmat ## ## 1 2 3 4 5 6 ## 1 31 0 7 2 5 0 ## 2 0 47 2 0 0 1 ## 3 0 7 39 0 4 0 ## 4 0 0 0 40 9 0 ## 5 0 0 1 2 47 0 ## 6 0 0 0 0 0 49 imgVal <- as.factor(getValues(img.class)) head(imgVal) ## [1] 5 5 5 5 5 5 ## Levels: 1 2 3 4 5 6 nclass <- length(unique(shp.train$classes)) nclass ## [1] 6 maparea <- sapply(1:nclass, function(x) sum(imgVal == x)) maparea ## [1] 26741 121740 36345 129130 185222 19942 maparea <- maparea * res(img.class)[1] ^ 2 / 1000000 maparea ## [1] 24.0669 109.5660 32.7105 116.2170 166.6998 17.9478 conf <- 1.96 A <- sum(maparea) A ## [1] 467.208 W_i <- maparea / A W_i ## [1] 0.05151217 0.23451225 0.07001271 0.24874788 0.35679997 0.03841501 n_i <- rowSums(confmat) n_i ## 1 2 3 4 5 6 ## 45 50 50 49 50 49 p <- W_i * confmat / n_i p[is.na(p)] <- 0 round(p, digits = 4) ## ## 1 2 3 4 5 6 ## 1 0.0355 0.0000 0.0080 0.0023 0.0057 0.0000 ## 2 0.0000 0.2204 0.0094 0.0000 0.0000 0.0047 ## 3 0.0000 0.0098 0.0546 0.0000 0.0056 0.0000 ## 4 0.0000 0.0000 0.0000 0.2031 0.0457 0.0000 ## 5 0.0000 0.0000 0.0071 0.0143 0.3354 0.0000 ## 6 0.0000 0.0000 0.0000 0.0000 0.0000 0.0384 p_area <- colSums(p) * A p_area ## 1 2 3 4 5 6 ## 16.57942 107.57151 36.97457 102.60865 183.33473 20.13912 p_area_CI <- conf * A * sqrt(colSums((W_i * p - p ^ 2) / (n_i - 1))) p_area_CI ## 1 2 3 4 5 6 ## 3.292170 7.948700 9.994001 15.744342 17.208163 4.294987 OA <- sum(diag(p)) OA ## [1] 0.8874041 PA <- diag(p) / colSums(p) PA ## 1 2 3 4 5 6 ## 1.0000000 0.9574286 0.6900470 0.9245908 0.8547088 0.8911909 UA <- diag(p) / rowSums(p) UA ## 1 2 3 4 5 6 ## 0.6888889 0.9400000 0.7800000 0.8163265 0.9400000 1.0000000 OA_CI <- conf * sqrt(sum(W_i ^ 2 * UA * (1 - UA) / (n_i - 1))) OA_CI ## [1] 0.04079462 UA_CI <- conf * sqrt(UA * (1 - UA) / (n_i - 1)) UA_CI ## 1 2 3 4 5 6 ## 0.13679244 0.06649632 0.11598896 0.10954451 0.06649632 0.00000000 N_j <- sapply(1:nclass, function(x) sum(maparea / n_i * confmat[ , x]) ) tmp <- sapply(1:nclass, function(x) sum(maparea[-x] ^ 2 * confmat[-x, x] / n_i[-x] * ( 1 - confmat[-x, x] / n_i[-x]) / (n_i[-x] - 1)) ) PA_CI <- conf * sqrt(1 / N_j ^ 2 * (maparea ^ 2 * ( 1 - PA ) ^ 2 * UA * (1 - UA) / (n_i - 1) + PA ^ 2 * tmp)) PA_CI ## 1 2 3 4 5 6 ## 0.00000000 0.02843232 0.17545912 0.08399238 0.06198829 0.19006061 result <- matrix(c(p_area, p_area_CI, PA * 100, PA_CI * 100, UA * 100, UA_CI * 100, c(OA * 100, rep(NA, nclass-1)), c(OA_CI * 100, rep(NA, nclass-1))), nrow = nclass) result <- round(result, digits = 2) rownames(result) <- levels(as.factor(shp.train$classes)) colnames(result) <- c("km²", "km²±", "PA", "PA±", "UA", "UA±", "OA", "OA±") class(result) <- "table" result ## km² km²± PA PA± UA UA± OA OA± ## baresoil 16.58 3.29 100.00 0.00 68.89 13.68 88.74 4.08 ## forest 107.57 7.95 95.74 2.84 94.00 6.65 ## grassland 36.97 9.99 69.00 17.55 78.00 11.60 ## urban_hd 102.61 15.74 92.46 8.40 81.63 10.95 ## urban_ld 183.33 17.21 85.47 6.20 94.00 6.65 ## water 20.14 4.29 89.12 19.01 100.00 0.00