4.1.4 Analyse de la précision
La précision des cartes est déterminée selon la procédure de Olofsson et al. (2014). Le script utilisé est basé sur une implémentation des méthodes dans OpenForis.
Le calcul de la précision se fait sur la matrice d’erreur d’une part et les surfaces couvertes par les catégories correspondantes d’autre part. Avec les informations sur les surfaces, la matrice d’erreur est extrapolée en une matrice d’erreur proportionnelle, qui indique dans les cellules les proportions de la surface totale. Sur cette base les indices de précision sont calculés: concrètement la précision globale, le Kappa de Cohen, la précision du producteur et la précision de l’utilisateur, y compris la variance et l’intervalle de confiance à 95 %. D’autre part, la matrice d’erreur proportionnelle est extrapolée à la superficie totale pour obtenir des estimations de superficie des différentes catégories, y compris les erreurs types et les intervalles de confiance.
Example
Le nombre de pixels cartographiés et matrice d’erreur des transitions de l’occupation du sol 2003 – 2018. Dans les colonnes sont les classes attribuées par les photo-interprètes, dans les lignes les classes selon les cartes forêt/non-forêt nettoyées.
Pixels | xFxF | xFxN | xNxF | xNxN | |
---|---|---|---|---|---|
xFxF | 12 590 594 | 536 | 52 | 80 | 105 |
xFxN | 2 509 971 | 36 | 80 | 3 | 52 |
xNxF | 1 637 331 | 23 | 0 | 73 | 29 |
xNxN | 46 599 650 | 35 | 84 | 50 | 1175 |
La matrice d’erreur proportionelle aux surfaces cartographiées. La somme de tous les cellules est 1 (100%).
xFxF | xFxN | xNxF | xNxN | ||
---|---|---|---|---|---|
xFxF | 0.138 | 0.013 | 0.021 | 0.027 | |
xFxN | 0.008 | 0.019 | 0.001 | 0.012 | |
xNxF | 0.005 | 0 | 0.015 | 0.006 | |
xNxN | 0.019 | 0.046 | 0.027 | 0.643 |
Les indices de précision globales sont:
Précision globale: 81.5% (± 1.5% intervalle de confiance)
Cohen’s Kappa: 59.3%
Indices de précision et le surfaces estimées par catégories (± intervalle de confiance à 95%)
Surface cartes | Prop. cartes | Proportion ajustée | Surface ajustée | Précision utilisateur | Précision producteur | |
---|---|---|---|---|---|---|
xFxF | 1’133’153 | 0.20 | 0.17 ± 0.01 | 969’621 ± 54’104 | 0.69 ± 0.03 | 0.81 ± 0.03 |
xFxN | 225’897 | 0.04 | 0.08 ± 0.01 | 444’034 ± 60’300 | 0.47 ± 0.08 | 0.24 ± 0.04 |
xNxF | 147’360 | 0.03 | 0.06 ± 0.01 | 363’320 ± 50’777 | 0.58 ± 0.09 | 0.24 ± 0.04 |
xNxN | 4’193’969 | 0.74 | 0.69 ± 0.01 | 3’923’405 ± 81’517 | 0.87 ± 0.02 | 0.94 ± 0.01 |
MCF/04_fc-maps-accuracy.R
##########################################################################
# NERF_Togo/FCC/7_fc-maps-accuracy.R: validate clean forest cover maps
# ------------------------------------------------------------------------
# Bern University of Applied Sciences
# Oliver Gardi, <oliver.gardi@bfh.ch>
# 20 May 2019
# based on OpenForis implementation of Olofsson et al. (2014), written by
# Antonia Ortmann, 20 October, 2014
# Source: https://github.com/openforis/accuracy-assessment/blob/master/Rscripts/error_matrix_analysis.R
VALSET <- "FC30_flex"
# Function for estimating accuracies ----------------------------------
accuracy.estimate <- function(areas.map, error.matrix, filename=NULL, pixelsize=30^2/10000) {
# remove "x" from the category
colnames(error.matrix) <- rownames(error.matrix) <- gsub("x", "", colnames(error.matrix))
# match category order
maparea <- areas.map[match(rownames(error.matrix), names(areas.map))]
ma <- error.matrix
dyn <- names(maparea)
# calculate the area proportions for each map class
aoi <- sum(maparea)
propmaparea <- maparea/aoi
# convert the absolute cross tab into a probability cross tab
ni. <- rowSums(ma) # number of reference points per map class
propma <- as.matrix(ma/ni. * as.vector(propmaparea))
propma[is.nan(propma)] <- 0 # for classes with ni. = 0
# estimate the accuracies now
OA <- sum(diag(propma)) # overall accuracy (Eq. 1 in Olofsson et al. 2014)
pe <- 0 # Agreement by chance ...
for (i in 1:length(dyn)) {
pe <- pe + sum(propma[i,]) * sum(propma[,i])
}
K <- (OA - pe) / (1 - pe) # ... for Cohen's Kappa
UA <- diag(propma) / rowSums(propma) # user's accuracy (Eq. 2 in Olofsson et al. 2014)
PA <- diag(propma) / colSums(propma) # producer's accuracy (Eq. 3 in Olofsson et al. 2014)
# estimate confidence intervals for the accuracies
V_OA <- sum(as.vector(propmaparea)^2 * UA * (1 - UA) / (ni. - 1), na.rm=T) # variance of overall accuracy (Eq. 5 in Olofsson et al. 2014)
V_UA <- UA * (1 - UA) / (rowSums(ma) - 1) # variance of user's accuracy (Eq. 6 in Olofsson et al. 2014)
# variance of producer's accuracy (Eq. 7 in Olofsson et al. 2014)
N.j <- array(0, dim=length(dyn))
aftersumsign <- array(0, dim=length(dyn))
for(cj in 1:length(dyn)) {
N.j[cj] <- sum(maparea / ni. * ma[, cj], na.rm=T)
aftersumsign[cj] <- sum(maparea[-cj]^2 * ma[-cj, cj] / ni.[-cj] * ( 1 - ma[-cj, cj] / ni.[-cj]) / (ni.[-cj] - 1), na.rm = T)
}
V_PA <- 1/N.j^2 * (
maparea^2 * (1-PA)^2 * UA * (1-UA) / (ni.-1) +
PA^2 * aftersumsign
)
V_PA[is.nan(V_PA)] <- 0
# proportional area estimation
propAreaEst <- colSums(propma) # proportion of area (Eq. 8 in Olofsson et al. 2014)
AreaEst <- propAreaEst * sum(maparea) # estimated area
# standard errors of the area estimation (Eq. 10 in Olofsson et al. 2014)
V_propAreaEst <- array(0, dim=length(dyn))
for (cj in 1:length(dyn)) {
V_propAreaEst[cj] <- sum((as.vector(propmaparea) * propma[, cj] - propma[, cj] ^ 2) / ( rowSums(ma) - 1))
}
V_propAreaEst[is.na(V_propAreaEst)] <- 0
# produce result tables
res <- list()
res$PREDICTED_PX <- as.table(maparea)
res$ERROR_MATRIX <- ma
res$ERROR_MATRIX_PROP <- round(propma, 3)
res$OVERALL_ACC <- data.frame(accuracy=round(c(OA, K), 3),
CI=round(c(1.96 * sqrt(V_OA), NA), 3),
row.names=c("OA", "Kappa"))
res$CLASSES_ACC <- data.frame(maparea=round(maparea * pixelsize, 3)) # in ha
res$CLASSES_ACC$prop_maparea <- round(propmaparea, 3)
res$CLASSES_ACC$adj_proparea <- round(propAreaEst, 3)
res$CLASSES_ACC$CI_adj_proparea <- round(1.96 * sqrt(V_propAreaEst), 3)
res$CLASSES_ACC$adj_area <- round(propAreaEst * aoi * pixelsize, 3) # in ha
res$CLASSES_ACC$CI_adj_area <- round(1.96 * sqrt(V_propAreaEst) * aoi * pixelsize, 3) # in ha
res$CLASSES_ACC$UA <- round(UA, 3)
res$CLASSES_ACC$CI_UA <- round(1.96 * sqrt(V_UA), 3)
res$CLASSES_ACC$PA <- round(PA, 3)
res$CLASSES_ACC$CI_PA <- round(1.96 * sqrt(V_PA), 3)
# write results to Excel File
if(!is.null(filename)) {
write.xlsx(res,
file=filename,
col.names=TRUE, row.names=TRUE, overwrite=TRUE)
}
return(res)
}
# DO THE WORK ----------------------------------
# load the error matrices (R object ct)
load(paste0(FCC.VAL.DIR, "/ConfTab_", VALSET, ".RData"))
# load the predictions and convert "potential regeneration (2)" to "non-forest (3)"
fc.2003 <- brick(paste0(FCC.CLN.DIR, "/FC30/TGO/TGO_2003_F30cf.tif")); fc.2003[fc.2003==2] <- 3
fc.2015 <- brick(paste0(FCC.CLN.DIR, "/FC30/TGO/TGO_2015_F30cf.tif")); fc.2015[fc.2015==2] <- 3
fc.2018 <- brick(paste0(FCC.CLN.DIR, "/FC30/TGO/TGO_2018_F30cf.tif")); fc.2018[fc.2018==2] <- 3
# create 3-date transition map
fcc <- 100 * fc.2003 + 10 * fc.2015 + 1 * fc.2018
# get pixel counts (takes time) and separate for different dates / transitions
freq <- table(fcc[])
tmp <- as.numeric(freq); names(tmp) <- names(freq); freq <- tmp
freq.03.15.18 <- c(freq["111"], freq["113"], freq["131"], freq["133"], freq["311"], freq["313"], freq["331"], freq["333"])
names(freq.03.15.18) <- c("111", "113", "131", "133", "311", "313", "331", "333"); freq.03.15.18[is.na(freq.03.15.18)] <- 0
freq.03.18 <- c(sum(freq["111"],freq["131"], na.rm=T), sum(freq["113"],freq["133"], na.rm=T),
sum(freq["311"],freq["331"], na.rm=T), sum(freq["313"],freq["333"], na.rm=T))
names(freq.03.18) <- c("11", "13", "31", "33"); freq.03.18[is.na(freq.03.18)] <- 0
freq.03.15 <- c(sum(freq["111"],freq["113"], na.rm=T), sum(freq["131"],freq["133"], na.rm=T),
sum(freq["311"],freq["313"], na.rm=T), sum(freq["331"],freq["333"], na.rm=T))
names(freq.03.15) <- c("11", "13", "31", "33"); freq.03.15[is.na(freq.03.15)] <- 0
freq.15.18 <- c(sum(freq["111"],freq["311"], na.rm=T), sum(freq["113"],freq["313"], na.rm=T),
sum(freq["131"],freq["331"], na.rm=T), sum(freq["133"],freq["333"], na.rm=T))
names(freq.15.18) <- c("11", "13", "31", "33"); freq.15.18[is.na(freq.15.18)] <- 0
freq.03 <- c(sum(freq["111"],freq["131"], freq["113"],freq["133"], na.rm=T),
sum(freq["311"],freq["331"], freq["313"],freq["333"], na.rm=T))
names(freq.03) <- c("1", "3"); freq.03[is.na(freq.03)] <- 0
freq.15 <- c(sum(freq["111"],freq["311"], freq["113"],freq["313"], na.rm=T),
sum(freq["131"],freq["331"], freq["133"],freq["333"], na.rm=T))
names(freq.15) <- c("1", "3"); freq.15[is.na(freq.15)] <- 0
freq.18 <- c(sum(freq["111"],freq["311"], freq["131"],freq["331"], na.rm=T),
sum(freq["113"],freq["313"], freq["133"],freq["333"], na.rm=T))
names(freq.18) <- c("1", "3"); freq.18[is.na(freq.18)] <- 0
# create accuracy maps ------------------------------------
accuracy.estimate(freq.18, ct$MAP_VAL.18c$table , filename=paste0(FCC.VAL.DIR, "/", VALSET, "_Acc_18.xlsx"))
accuracy.estimate(freq.15, ct$MAP_VAL.15c$table , filename=paste0(FCC.VAL.DIR, "/", VALSET, "_Acc_15.xlsx"))
accuracy.estimate(freq.03, ct$MAP_VAL.03c$table , filename=paste0(FCC.VAL.DIR, "/", VALSET, "_Acc_03.xlsx"))
accuracy.estimate(freq.03.15, ct$MAP_VAL.03.15c$table , filename=paste0(FCC.VAL.DIR, "/", VALSET, "_Acc_03-15.xlsx"))
accuracy.estimate(freq.15.18, ct$MAP_VAL.15.18c$table , filename=paste0(FCC.VAL.DIR, "/", VALSET, "_Acc_15-18.xlsx"))
accuracy.estimate(freq.03.18, ct$MAP_VAL.03.18c$table , filename=paste0(FCC.VAL.DIR, "/", VALSET, "_Acc_03-18.xlsx"))
accuracy.estimate(freq.03.15.18, ct$MAP_VAL.03.15.18c$table , filename=paste0(FCC.VAL.DIR, "/", VALSET, "_Acc_03-15-18.xlsx"))
# calculate forest cover, loss and gains ---------------------
res <- data.frame(year = c(2003, 2015, 2018),
total.ha = c(freq.03["1"], freq.15["1"], freq.18["1"]) * 30^2/10000,
defor.ha = c(NA, freq.03.15["13"], freq.15.18["13"]) * 30^2/10000,
regen.ha = c(NA, freq.03.15["31"], freq.15.18["31"]) * 30^2/10000)
# Example from Olofsson et al. (2014)
# -----------------------------------
# ct <- matrix(data=c(66, 0, 5, 4,
# 0, 55, 8, 12,
# 1, 0, 153, 11,
# 2, 1, 9, 313),
# nrow=4,
# byrow=TRUE,
# dimnames = list(Prediction=c("FN", "NF", "FF", "NN"), Reference=c("FN", "NF", "FF", "NN")))
#
# px <- c(FN=200000, NF=150000, FF=3200000, NN=6450000)
#
# accuracy.estimate(px, ct)