From 1c372c4cdc70fa126b1491ad02c1b936af5e0220 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:16:18 +0100 Subject: [PATCH 001/176] Add files via upload Addition of 'utils' function and a new POST_FATE function for habitat validation in RFate --- POST_FATE.validation_habitat.R | 169 +++++++++++++++++++++ UTILS.do_habitat_validation.R | 263 +++++++++++++++++++++++++++++++++ UTILS.plot_predicted_habitat.R | 137 +++++++++++++++++ UTILS.train_RF_habitat.R | 221 +++++++++++++++++++++++++++ 4 files changed, 790 insertions(+) create mode 100644 POST_FATE.validation_habitat.R create mode 100644 UTILS.do_habitat_validation.R create mode 100644 UTILS.plot_predicted_habitat.R create mode 100644 UTILS.train_RF_habitat.R diff --git a/POST_FATE.validation_habitat.R b/POST_FATE.validation_habitat.R new file mode 100644 index 0000000..75e8b5a --- /dev/null +++ b/POST_FATE.validation_habitat.R @@ -0,0 +1,169 @@ +### HEADER ##################################################################### +##' +##' @title Compute habitat performance and create a prediction plot of habitat +##' for a whole map of a \code{FATE} simulation. +##' +##' @name POST_FATE.validation.habitat +##' +##' @author Matthieu ... & Maxime Delprat +##' +##' @description This script compare habitat simulations and observations and +##' create a map to visualize this comparison with all the the \code{FATE} and +##' observed data. +##' +##' @param name.simulation simulation folder name. +##' @param sim.version name of the simulation we want to validate (it works with +##' only one sim.version). +##' @param obs.path the function needs observed data, please create a folder for them in your +##' simulation folder and then indicate in this parmeter the access path to this folder. +##' @param releves.PFG name of file which contain the observed Braund-Blanquet abundance at each site +##' and each PFG and strata (with extension). +##' @param releves.site name of the file which contain coordinates and a description of +##' the habitat associated with the dominant species of each site in the studied map (with extension). +##' @param hab.obs name of the file which contain the extended studied map in the simulation (with extension). +##' @param habitat.FATE.map name of the file which contain the restricted studied map in the simulation (with extension). +##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation. +##' +##' @details +##' +##' The observed habitat is derived from the cesbio map, the simulated habitat +##' is derived from FATE simulated relative abundance, based on a random forest +##' algorithm trained on CBNA data. To compare observations and simulations, the function +##' compute confusion matrix between observation and prediction and then compute the TSS +##' for each habitat h (number of prediction of habitat h/number of observation +##' of habitat h + number of non-prediction of habitat h/number of non-observation +##' of habitat h). The final metrics this script use is the mean of TSS per habitat over all +##' habitats, weighted by the share of each habitat in the observed habitat distribution. +##' +##' @return +##' +##' Two folders are created in name.simulation folder : +##' \describe{ +##' \item{\file{VALIDATION/HABITAT/sim.version}}{containing the prepared CBNA data, +##' RF model, the performance analyzes (confusion matrix and TSS) for the training and +##' testing parts of the RF model, the habitat performance file, the habitat prediction file with +##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} +##' \item{\file{DATA_OBS}}{maps of observed habitat and csv files of PFG and sites releves.} +##' } +##' +### END OF HEADER ############################################################## + + +POST_FATE.validation_habitat = function(name.simulation + , sim.version + , obs.path + , releves.PFG + , releves.sites + , hab.obs + , habitat.FATE.map + , validation.mask) +{ + + ## LIBRARIES + require(data.table) + require(raster) + require(RFate) + require(reshape2) + require(stringr) + require(foreign) + require(stringr) + require(dplyr) + require(sp) + options("rgdal_show_exportToProj4_warnings"="none") + require(rgdal) + require(randomForest) + require(ggplot2) + require(ggradar) + require(tidyverse) + require(ggpubr) + require(gridExtra) + require(vegan) + require(parallel) + require(scales) + require(class) + require(caret) + require(sampling) + require(tidyselect) + require(grid) + require(gtable) + require(scales) + require(cowplot) + require(sf) + require(visNetwork) + require(foreach) + require(doParallel) + require(prettyR) + require(vcd) + + ## GLOBAL PARAMETERS + + # Create directories + dir.create(paste0(name.simulation, "/VALIDATION"), recursive = TRUE) + dir.create(paste0(name.simulation, "/VALIDATION/HABITAT"), recursive = TRUE) + dir.create(paste0(name.simulation, "/VALIDATION/HABITAT/", sim.version), recursive = TRUE) + + # General + output.path = paste0(name.simulation, "/VALIDATION") + + # Useful elements to extract from the simulation + simulation.map=raster(paste0(name.simulation,"/DATA/MASK/MASK_Champsaur.tif")) + + # For habitat validation + # CBNA releves data habitat map + releves.PFG<-read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) + releves.sites<-st_read(paste0(obs.path, releves.sites)) + hab.obs<-raster(paste0(obs.path, hab.obs)) + # Habitat mask at FATE simu resolution + # hab.obs.modif<-projectRaster(from = hab.obs, to = simulation.map, res = res(hab.obs)[1], crs = crs(projection(simulation.mask))) + # habitat.FATE.map<-crop(hab.obs.modif, simulation.map) + habitat.FATE.map<-raster(paste0(obs.path, habitat.FATE.map)) + validation.mask<-raster(paste0(obs.path, validation.mask)) + + # Provide a color df + col.df<-data.frame( + habitat=c("agricultural.grassland","coniferous.forest","deciduous.forest","natural.grassland","woody.heatland"), + failure=c("yellow","blueviolet","aquamarine","chartreuse1","lightsalmon"), + success=c("darkorange1","blue4","aquamarine3","chartreuse3","firebrick4")) + + # Other + studied.habitat=c("coniferous.forest","deciduous.forest","natural.grassland","woody.heatland","agricultural.grassland") + RF.param = list( + share.training=0.7, + ntree=500) + predict.all.map<-T + + ## TRAIN A RF ON CBNA DATA + + RF.model <- train.RF.habitat(releves.PFG = releves.PFG + , releves.sites = releves.sites + , hab.obs = hab.obs + , external.training.mask = NULL + , studied.habitat = studied.habitat + , RF.param = RF.param + , output.path = output.path + , perStrata = F + , sim.version = sim.version) + + ## USE THE RF MODEL TO VALIDATE OUTPUT + + habitats.results <- do.habitat.validation(output.path = output.path + , RF.model = RF.model + , habitat.FATE.map = habitat.FATE.map + , validation.mask = validation.mask + , simulation.map = simulation.map + , predict.all.map = predict.all.map + , sim.version = sim.version + , name.simulation = name.simulation + , perStrata = F) + + ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT + + prediction.map <- plot.predicted.habitat(predicted.habitat = habitats.results + , col.df = col.df + , simulation.map = simulation.map + , output.path = output.path + , sim.version = sim.version) + return(prediction.map) + +} + diff --git a/UTILS.do_habitat_validation.R b/UTILS.do_habitat_validation.R new file mode 100644 index 0000000..6a6c07f --- /dev/null +++ b/UTILS.do_habitat_validation.R @@ -0,0 +1,263 @@ +### HEADER ##################################################################### +##' +##' @title Compare observed and simulated habitat of a \code{FATE} simulation +##' at the last simulation year. +##' +##' @name do.habitat.validation +##' +##' @author Matthieu ... & Maxime Delprat +##' +##' @description To compare observations and simulations, this function compute +##' confusion matrix between observation and prediction and then compute the TSS +##' for each habitat. +##' +##' @param output.path access path to the for the folder where output files +##' will be created. +##' @param RF.model random forest model trained on CBNA data (train.RF.habitat +##' function) +##' @param habitat.FATE.map a raster map of the observed habitat in the +##' studied area. +##' @param validation.mask a raster mask that specified which pixels need validation. +##' @param simulation.map a raster map of the whole studied area use to check +##' the consistency between simulation map and the observed habitat map. +##' @param predict.all.map a TRUE/FALSE vector. If TRUE, the script will predict +##' habitat for the whole map. +##' @param sim.version name of the simulation we want to validate. +##' @param name.simulation simulation folder name. +##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +##' by strata in each pixel. If FALSE, PFG abundance is defined for all strata. +##' +##' @details +##' +##' After several preliminary checks, the function is going to prepare the observations +##' database by extracting the observed habitat from a raster map. Then, for each +##' simulations (sim.version), the script take the evolution abundance for each PFG +##' and all strata file and predict the habitat for the whole map (if option selected) +##' thanks to the RF model.Finally, the function compute habitat performance based on +##' TSS for each habitat. +##' +##' @return +##' +##' Habitat performance file +##' If option selected, the function returns an habitat prediction file with +##' observed and simulated habitat for each pixel of the whole map. +##' +### END OF HEADER ############################################################## + + +do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata) { + + #notes + # we prepare the relevé data in this function, but in fact we could provide them directly if we adjust the code + + ########################### + #I. Preliminary checks + ########################### + + #check if strata definition used in the RF model is the same as the one used to analyze FATE output + if(perStrata==F){ + list.strata<-"all" + }else{ + stop("check 'perStrata' parameter and/or the names of strata in param$list.strata.releves & param$list.strata.simul") + } + + #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) + if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map)==res(validation.mask))){ + stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") + } + + #consistency between habitat.FATE.map and simulation.map + if(!compareCRS(simulation.map,habitat.FATE.map)){ + print("reprojecting habitat.FATE.map to match simulation.map crs") + habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) + } + if(!all(res(habitat.FATE.map)==res(simulation.map))){ + stop("provide habitat.FATE.map with same resolution as simulation.map") + } + if(extent(simulation.map)!=extent(habitat.FATE.map)){ + print("cropping habitat.FATE.map to match simulation.map") + habitat.FATE.map<-crop(x=habitat.FATE.map,y=simulation.map) + } + if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ + print("setting origin habitat.FATE.map to match simulation.map") + origin(habitat.FATE.map)<-origin(simulation.map) + } + if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates + stop("habitat.FATE.map could not be coerced to match simulation.map") + }else{ + print("simulation.map & habitat.FATE.map are (now) consistent") + } + + #adjust validation.mask accordingly + if(!all(res(habitat.FATE.map)==res(validation.mask))){ + validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") + } + if(extent(validation.mask)!=extent(habitat.FATE.map)){ + validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) + } + if(!compareRaster(validation.mask,habitat.FATE.map)){ + stop("error in correcting validation.mask to match habitat.FATE.map") + }else{ + print("validation.mask is (now) consistent with (modified) habitat.FATE.map") + } + + #check consistency for PFG & strata classes between FATE output vs the RF model + + RF.predictors<-rownames(RF.model$importance) + RF.PFG<-unique(str_sub(RF.predictors,1,2)) + + FATE.PFG<-str_sub(list.files(paste0(name.simulation,"/DATA/PFGS/SUCC")),6,7) + + if(length(setdiff(FATE.PFG,RF.PFG))>0|length(setdiff(RF.PFG,FATE.PFG))>0){ + stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") + } + + + ######################################################################################### + #II. Prepare database for FATE habitat + ######################################################################################### + + #index of the pixels in the simulation area + in.region.pixels<-which(getValues(simulation.map)==1) + + #habitat df for the whole simulation area + habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) + habitat.whole.area.df<-habitat.whole.area.df[in.region.pixels,] + habitat.whole.area.df<-merge(habitat.whole.area.df,dplyr::select(levels(habitat.FATE.map)[[1]],c(ID,habitat)),by.x="code.habitat",by.y="ID") + habitat.whole.area.df<-filter(habitat.whole.area.df,is.element(habitat,RF.model$classes)) + + print(cat("Habitat considered in the prediction exercise: ",c(unique(habitat.whole.area.df$habitat)),"\n",sep="\t")) + + print("Habitat in the simulation area:") + table(habitat.whole.area.df$habitat,useNA="always") + + print("Habitat in the subpart of the simulation area used for validation:") + table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation==1],useNA="always") + + ############################## + # III. Loop on simulations + ######################### + + print("processing simulations") + + registerDoParallel(detectCores()-2) + results.simul <- foreach(i=1:length(sim.version),.packages = c("dplyr","forcats","reshape2","randomForest","vcd","caret")) %dopar%{ + + ########################" + # III.1. Data preparation + ######################### + + #get simulated abundance per pixel*strata*PFG for pixels in the simulation area + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,-c(3:44)] + colnames(simu_PFG) = c("PFG", "pixel", "abs") + + #aggregate per strata group with the correspondance provided in input + simu_PFG$new.strata<-NA + + #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) + if(perStrata==F){ + simu_PFG$new.strata<-"A" + } + + simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") + + #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) + simu_PFG<-aggregate(abs~pixel+strata+PFG,data=simu_PFG,FUN="sum") + + #transform absolute abundance into relative abundance (no pb if all combination PFG*strata are not present, since then the value is 0!) + simu_PFG<-simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance= round(prop.table(abs),digits=2)) #those are proportions, not percentages + simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) + simu_PFG<-as.data.frame(simu_PFG) + + #drop the absolute abundance + simu_PFG$abs<-NULL + + #set a factor structure + simu_PFG$PFG<-as.factor(simu_PFG$PFG) + simu_PFG$strata<-as.factor(simu_PFG$strata) + + #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) + simu_PFG$PFG<-fct_expand(simu_PFG$PFG,RF.PFG) + simu_PFG$strata<-fct_expand(simu_PFG$strata,list.strata) + + #cast + simu_PFG<-dcast(simu_PFG,pixel~PFG*strata,value.var=c("relative.abundance"),fill=0,drop=F) + + #merge PFG info and habitat + transform habitat into factor + + #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable + data.FATE.PFG.habitat<-merge(simu_PFG,habitat.whole.area.df,by="pixel") #at this stage we have all the pixels in the simulation area + data.FATE.PFG.habitat$habitat<-factor(data.FATE.PFG.habitat$habitat,levels=RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs + + ############################ + # III.2. Prediction of habitat with the RF algorithm + ################################# + + data.validation<-filter(data.FATE.PFG.habitat,for.validation==1) + x.validation<-select(data.validation,all_of(RF.predictors)) + y.validation<-data.validation$habitat + + y.validation.predicted<-predict(object=RF.model,newdata=x.validation,type="response",norm.votes=T) + + ############################## + # III.3. Analysis of the results + ################################ + + confusion.validation<-confusionMatrix(data=y.validation.predicted,reference=fct_expand(y.validation,levels(y.validation.predicted))) + + synthesis.validation<-data.frame(habitat=colnames(confusion.validation$table),sensitivity=confusion.validation$byClass[,1],specificity=confusion.validation$byClass[,2],weight=colSums(confusion.validation$table)/sum(colSums(confusion.validation$table))) + synthesis.validation<-synthesis.validation%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) + + aggregate.TSS.validation<-round(sum(synthesis.validation$weight*synthesis.validation$TSS,na.rm=T),digits=2) + + ######################## + # III.4. Predict habitat for the whole map if option selected (do it only for a small number of simulations) + ############################################ + + if(predict.all.map==T){ + + y.all.map.predicted = predict(object=RF.model,newdata=select(data.FATE.PFG.habitat,all_of(RF.predictors)),type="response",norm.votes=T) + y.all.map.predicted = as.data.frame(y.all.map.predicted) + y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel + colnames(y.all.map.predicted) = c(sim.version, "pixel") + + }else{ + y.all.map.predicted<-NULL + } + + #prepare outputs + + output.validation<-c(synthesis.validation$TSS,aggregate.TSS.validation) + names(output.validation)<-c(synthesis.validation$habitat,"aggregated") + + output<-list(output.validation,y.all.map.predicted) + names(output)<-c("output.validation","y.all.map.predicted") + + return(output) + } + #end of the loop on simulations + + #deal with the results regarding model performance + habitat.performance<-as.data.frame(matrix(unlist(lapply(results.simul,"[[",1)),ncol=length(RF.model$classes)+1,byrow=T)) + names(habitat.performance)<-c(RF.model$classes,"weighted") + habitat.performance$simulation<-sim.version + + #save + write.csv(habitat.performance,paste0(output.path,"/HABITAT/", sim.version, "/performance.habitat.csv"),row.names=F) + + print("habitat performance saved") + + #deal with the results regarding habitat prediction over the whole map + all.map.prediction = results.simul[[1]]$y.all.map.predicted + all.map.prediction = merge(all.map.prediction, select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") + all.map.prediction = rename(all.map.prediction,"true.habitat"="habitat") + + #save + write.csv(all.map.prediction,paste0(output.path,"/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names=F) + + #return results + return(all.map.prediction) + +} + diff --git a/UTILS.plot_predicted_habitat.R b/UTILS.plot_predicted_habitat.R new file mode 100644 index 0000000..a415c26 --- /dev/null +++ b/UTILS.plot_predicted_habitat.R @@ -0,0 +1,137 @@ +### HEADER ##################################################################### +##' +##' @title Create a raster map of habitat prediction for a specific \code{FATE} +##' simulation at the last simulation year. +##' +##' @name plot.predicted.habitat +##' +##' @author Matthieu ... & Maxime Delprat +##' +##' @description This script is designed to create a raster map of habitat prediction +##' based on a habitat prediction file. For each pixel, the habitat failure or success value +##' is associated to a color and then, the map is built. +##' +##' @param predicted habitat a csv file created by the do.habitat.validation function +##' which contain, for each pixel of the studied map, the simulated and observed habitat. +##' @param col.df a data frame with all the colors associated with the failure or +##' success of each studied habitat prediction. +##' @param simulation.map a raster map of the whole studied area. +##' @param output.path access path to the for the folder where output files +##' will be created. +##' @param sim.version name of the simulation we want to validate. +##' +##' @details +##' +##' The function determine true/false prediction ('failure' if false, 'success' if true) +##' and prepare a dataframe containing color and habitat code. Then, the script merge +##' the prediction dataframe with the color and code habitat dataframe. Finally, +##' the function draw a raster map and a plot of prediction habitat over it thanks +##' to the data prepared before. +##' +##' @return +##' +##' a synthetic.prediction.png file which contain the final prediction plot. +### END OF HEADER ############################################################## + + +plot.predicted.habitat<-function(predicted.habitat + , col.df + , simulation.map + , output.path + , sim.version) +{ + + #auxiliary function to compute the proportion of simulations lead to the modal prediction + count.habitat<-function(df){ + index<-which(names(df)=="modal.predicted.habitat") + prop.simu<-sum(df[-index]==as.character(df[index]))/(length(names(df))-1) + return(prop.simu) + } + + #compute modal predicted habitat and the proportion of simulations predicting this habitat (for each pixel) + predicted.habitat$modal.predicted.habitat<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version))),1,Mode) + predicted.habitat$modal.predicted.habitat[predicted.habitat$modal.predicted.habitat==">1 mode"]<-"ambiguous" + predicted.habitat$confidence<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version),modal.predicted.habitat)),1,FUN=function(x) count.habitat(x)) + + + #true/false prediction + predicted.habitat$prediction.code<-"failure" + predicted.habitat$prediction.code[predicted.habitat$modal.predicted.habitat==predicted.habitat$true.habitat]<-"success" + + #prepare a df containing color & habitat code (to facilitate conversion into raster) + col.df.long<-data.table::melt(data=setDT(col.df),id.vars="habitat",variable.name="prediction.code",value.name="color") + + habitat.code.df<-unique(dplyr::select(predicted.habitat,c(modal.predicted.habitat,prediction.code))) + habitat.code.df$habitat.code<-seq(from=1,to=dim(habitat.code.df)[1],by=1) + habitat.code.df<-rename(habitat.code.df,"habitat"="modal.predicted.habitat") + + habitat.code.df<-merge(habitat.code.df,col.df.long,by=c("habitat","prediction.code")) + habitat.code.df$label<-paste0(habitat.code.df$habitat," (",habitat.code.df$prediction.code,")") + + #deal with out of scope habitat + out.of.scope<-data.frame(habitat="out.of.scope",prediction.code="",habitat.code=0,color="white",label="out of scope") + habitat.code.df<-rbind(habitat.code.df,out.of.scope) + + habitat.code.df$label<-as.factor(habitat.code.df$label) + + #order the df + habitat.code.df<-habitat.code.df[order(habitat.code.df$label),] #to be sure it's in te right order (useful to ensure the correspondance between label and color in the ggplot function) + + + #merge the prediction df with the df containing color and habitat code + predicted.habitat<-merge(predicted.habitat,habitat.code.df,by.x=c("modal.predicted.habitat","prediction.code"),by.y=c("habitat","prediction.code")) + + + #plot + + #prepare raster + prediction.map<-raster(nrows=nrow(simulation.map),ncols=ncol(simulation.map),crs=crs(simulation.map),ext=extent(simulation.map), resolution=res(simulation.map)) + + prediction.map[]<-0 #initialization of the raster, corresponding to "out of scope habitats" + prediction.map[predicted.habitat$pixel]<-predicted.habitat$habitat.code + + #ratify + prediction.map<-ratify(prediction.map) + prediction.map.rat<-levels(prediction.map)[[1]] + prediction.map.rat<-merge(prediction.map.rat,habitat.code.df,by.x="ID",by.y="habitat.code") + levels(prediction.map)<-prediction.map.rat + + #save the raster + writeRaster(prediction.map,filename = paste0(output.path,"/HABITAT/", sim.version, "/synthetic.prediction.grd"),overwrite=T) + + + #plot on R + #convert into xy + xy.prediction<-as.data.frame(prediction.map,xy=T) + names(xy.prediction)<-c("x","y","habitat","prediction.code","color","label") + xy.prediction<-xy.prediction[complete.cases(xy.prediction),] + + #plot + prediction.plot<- + ggplot(xy.prediction, aes(x=x, y=y, fill=factor(label)))+ + geom_raster(show.legend = T) + + coord_equal()+ + scale_fill_manual(values = as.character(habitat.code.df$color))+ #ok only if habitat.code.df has been ordered according to "label" + ggtitle(paste0("Modal prediction over ",length(sim.version)," simulations"))+ + guides(fill=guide_legend(nrow=4,byrow=F))+ + theme( + plot.title = element_text(size = 8), + legend.text = element_text(size = 8, colour ="black"), + legend.title = element_blank(), + legend.position = "bottom", + axis.title.x=element_blank(), + axis.text.x=element_blank(), + axis.ticks.x=element_blank(), + axis.title.y=element_blank(), + axis.text.y=element_blank(), + axis.ticks.y=element_blank() + ) + + #save the map + ggsave(filename="synthetic.prediction.png",plot = prediction.plot,path = paste0(output.path, "/HABITAT/", sim.version),scale = 1,dpi = 300,limitsize = F,width = 15,height = 15,units ="cm") + + #return the map + return(prediction.plot) + +} + diff --git a/UTILS.train_RF_habitat.R b/UTILS.train_RF_habitat.R new file mode 100644 index 0000000..8174f9a --- /dev/null +++ b/UTILS.train_RF_habitat.R @@ -0,0 +1,221 @@ +### HEADER ##################################################################### +##' +##' @title Create a random forest algorithm trained on CBNA data, in order to +##' obtain the simulated habitat, derived from a \code{FATE} simulation. +##' +##' @name train.RF.habitat +##' +##' @author Matthieu & Maxime Delprat +##' +##' @description This script is designed to produce a random forest model +##' trained on observed PFG abundance, sites releves and a map of observed +##' habitat. +##' +##' @param releves.PFG a data frame with Braund-Blanquet abundance at each site +##' and each PFG and strata. +##' @param releves.sites a data frame with coordinates and a description of +##' the habitat associated with the dominant species of each site in the +##' studied map. +##' @param hab.obs a raster map of the observed habitat in the +##' extended studied area. +##' @param external.training.mask default \code{NULL}. (optional) Keep only +##' releves data in a specific area. +##' @param studied.habitat a vector that specifies habitats that we take +##' into account for the validation. +##' @param RF.param a list of 2 parameters for random forest model : +##' share.training defines the size of the trainig part of the data base. +##' ntree is the number of trees build by the algorithm, it allows to reduce +##' the prediction error. +##' @param output.path access path to the for the folder where output files +##' will be created. +##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +##' by strata in each site. If FALSE, PFG abundance is defined for all strata. +##' @param sim.version name of the simulation we want to validate. +##' +##' @details +##' +##' This function transform PFG Braund-Blanquet abundance in relative abundance, +##' get habitat information from the releves map, keep only relees on interesting +##' habitat and then builds de random forest model. Finally, the function analyzes +##' the model performance with computation of confusion matrix and TSS for +##' the traning and testing sample. +##' +##' @return +##' +##' 2 prepared CBNA releves files are created before the building of the random +##' forest model in a habitat validation folder. +##' 5 more files are created at the end of the script to save the RF model and +##' the performance analyzes (confusion matrix and TSS) for the training and +##' testing parts. +##' +### END OF HEADER ############################################################## + + +train.RF.habitat<-function(releves.PFG + , releves.sites + , hab.obs + , external.training.mask=NULL + , studied.habitat + , RF.param + , output.path + , perStrata + , sim.version) +{ + + #1. Compute relative abundance metric + ######################################### + + #identify sites with wrong BB values (ie values that cannot be converted by the PRE_FATE.abundBraunBlanquet function) + releves.PFG<-filter(releves.PFG,is.element(BB,c(NA, "NA", 0, "+", "r", 1:5))) + + #transformation into coverage percentage + releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage + + if(perStrata==T){ + aggregated.releves.PFG<-aggregate(coverage~site+PFG+strata,data=releves.PFG,FUN="sum") + }else if(perStrata==F){ + aggregated.releves.PFG<-aggregate(coverage~site+PFG,data=releves.PFG,FUN="sum") + aggregated.releves.PFG$strata<-"A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) + } + + #transformation into a relative metric (here relative.metric is relative coverage) + aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG %>% group_by(site,strata) %>% mutate(relative.metric= round(prop.table(coverage),digits = 2))) #rel is proportion of total pct_cov, not percentage + aggregated.releves.PFG$relative.metric[is.na(aggregated.releves.PFG$relative.metric)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) + aggregated.releves.PFG$coverage<-NULL + + print("releve data have been transformed into a relative metric") + + #2. Cast the df + ####################### + + #transfo into factor to be sure to create all the combination when doing "dcast" + aggregated.releves.PFG$PFG<-as.factor(aggregated.releves.PFG$PFG) + aggregated.releves.PFG$strata<-as.factor(aggregated.releves.PFG$strata) + + aggregated.releves.PFG<-dcast(setDT(aggregated.releves.PFG),site~PFG+strata,value.var=c("relative.metric"),fill=0,drop=F) + + #3. Get habitat information + ################################### + + #get sites coordinates + aggregated.releves.PFG<-merge(dplyr::select(releves.sites,c(site)),aggregated.releves.PFG,by="site") + + #get habitat code and name + if(compareCRS(aggregated.releves.PFG,hab.obs)){ + aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) + }else{ + aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(hab.obs)) + aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) + } + + #correspondance habitat code/habitat name + table.habitat.releve<-levels(hab.obs)[[1]] + + aggregated.releves.PFG<-merge(aggregated.releves.PFG,dplyr::select(table.habitat.releve,c(ID,habitat)),by.x="code.habitat",by.y="ID") + + #(optional) keep only releves data in a specific area + if(!is.null(external.training.mask)){ + + if(compareCRS(aggregated.releves.PFG,external.training.mask)==F){ #as this stage it is not a problem to transform crs(aggregated.releves.PFG) since we have no more merge to do (we have already extracted habitat info from the map) + aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(external.training.mask)) + } + + aggregated.releves.PFG<-st_crop(x=aggregated.releves.PFG,y=external.training.mask) + print("'releve' map has been cropped to match 'external.training.mask'.") + } + + + # 4. Keep only releve on interesting habitat + ###################################################" + + aggregated.releves.PFG<-filter(aggregated.releves.PFG,is.element(habitat,studied.habitat)) #filter non interesting habitat + NA + + print(cat("habitat classes used in the RF algo: ",unique(aggregated.releves.PFG$habitat),"\n",sep="\t")) + + # 5. Save data + ##################### + + st_write(aggregated.releves.PFG,paste0(output.path,"/HABITAT/", sim.version, "/releve.PFG.habitat.shp"),overwrite=T,append=F) + write.csv(aggregated.releves.PFG,paste0(output.path,"/HABITAT/", sim.version, "/CBNA.releves.prepared.csv"),row.names = F) + + # 6. Small adjustment in data structure + ########################################## + + aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG) #get rid of the spatial structure before entering the RF process + aggregated.releves.PFG$habitat<-as.factor(aggregated.releves.PFG$habitat) + + # 7.Random forest + ###################################### + + #separate the database into a training and a test part + set.seed(123) + + training.site<-sample(aggregated.releves.PFG$site,size=RF.param$share.training*length(aggregated.releves.PFG$site),replace = F) + releves.training<-filter(aggregated.releves.PFG,is.element(site,training.site)) + releves.testing<-filter(aggregated.releves.PFG,!is.element(site,training.site)) + + #train the model (with correction for imbalances in sampling) + + #run optimization algo (careful : optimization over OOB...) + mtry.perf<-as.data.frame( + tuneRF( + x=select(releves.training,-c(code.habitat,site,habitat,geometry)), + y=releves.training$habitat, + strata=releves.training$habitat, + sampsize=min(table(releves.training$habitat)), + ntreeTry=RF.param$ntree, + stepFactor=2, improve=0.05,doBest=FALSE,plot=F,trace=F + ) + ) + + #select mtry + mtry<-mtry.perf$mtry[mtry.perf$OOBError==min(mtry.perf$OOBError)][1] #the lowest n achieving minimum OOB + + #run real model + model<- randomForest( + x=select(releves.training,-c(code.habitat,site,habitat,geometry)), + y=releves.training$habitat, + xtest=select(releves.testing,-c(code.habitat,site,habitat,geometry)), + ytest=releves.testing$habitat, + strata=releves.training$habitat, + min(table(releves.training$habitat)), + ntree=RF.param$ntree, + mtry=mtry, + norm.votes=TRUE, + keep.forest=TRUE + ) + + #analyse model performance + + # Analysis on the training sample + + confusion.training<-confusionMatrix(data=model$predicted,reference=releves.training$habitat) + + synthesis.training<-data.frame(habitat=colnames(confusion.training$table),sensitivity=confusion.training$byClass[,1],specificity=confusion.training$byClass[,2],weight=colSums(confusion.training$table)/sum(colSums(confusion.training$table))) #warning: prevalence is the weight of predicted habitat, not of observed habitat + synthesis.training<-synthesis.training%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) + + aggregate.TSS.training<-round(sum(synthesis.training$weight*synthesis.training$TSS),digits=2) + + # Analysis on the testing sample + + confusion.testing<-confusionMatrix(data=model$test$predicted,reference=releves.testing$habitat) + + synthesis.testing<-data.frame(habitat=colnames(confusion.testing$table),sensitivity=confusion.testing$byClass[,1],specificity=confusion.testing$byClass[,2],weight=colSums(confusion.testing$table)/sum(colSums(confusion.testing$table)))#warning: prevalence is the weight of predicted habitat, not of observed habitat + synthesis.testing<-synthesis.testing%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) + + aggregate.TSS.testing<-round(sum(synthesis.testing$weight*synthesis.testing$TSS),digits=2) + + + # 8. Save and return output + #######################################" + + write_rds(model,paste0(output.path,"/HABITAT/", sim.version, "/RF.model.rds"),compress="none") + write.csv(synthesis.training,paste0(output.path,"/HABITAT/", sim.version, "/RF_perf.per.hab_training.csv"),row.names=F) + write.csv(aggregate.TSS.training,paste0(output.path,"/HABITAT/", sim.version, "/RF_aggregate.TSS_training.csv"),row.names=F) + write.csv(synthesis.testing,paste0(output.path,"/HABITAT/", sim.version, "/RF_perf.per.hab_testing.csv"),row.names=F) + write.csv(aggregate.TSS.testing,paste0(output.path,"/HABITAT/", sim.version, "/RF_aggregate.TSS_testing.csv"),row.names=F) + + return(model) + +} + From c56c83923592d68a6ab585fcaffd5a58c098b5cb Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:22:47 +0100 Subject: [PATCH 002/176] Delete POST_FATE.validation_habitat.R --- POST_FATE.validation_habitat.R | 169 --------------------------------- 1 file changed, 169 deletions(-) delete mode 100644 POST_FATE.validation_habitat.R diff --git a/POST_FATE.validation_habitat.R b/POST_FATE.validation_habitat.R deleted file mode 100644 index 75e8b5a..0000000 --- a/POST_FATE.validation_habitat.R +++ /dev/null @@ -1,169 +0,0 @@ -### HEADER ##################################################################### -##' -##' @title Compute habitat performance and create a prediction plot of habitat -##' for a whole map of a \code{FATE} simulation. -##' -##' @name POST_FATE.validation.habitat -##' -##' @author Matthieu ... & Maxime Delprat -##' -##' @description This script compare habitat simulations and observations and -##' create a map to visualize this comparison with all the the \code{FATE} and -##' observed data. -##' -##' @param name.simulation simulation folder name. -##' @param sim.version name of the simulation we want to validate (it works with -##' only one sim.version). -##' @param obs.path the function needs observed data, please create a folder for them in your -##' simulation folder and then indicate in this parmeter the access path to this folder. -##' @param releves.PFG name of file which contain the observed Braund-Blanquet abundance at each site -##' and each PFG and strata (with extension). -##' @param releves.site name of the file which contain coordinates and a description of -##' the habitat associated with the dominant species of each site in the studied map (with extension). -##' @param hab.obs name of the file which contain the extended studied map in the simulation (with extension). -##' @param habitat.FATE.map name of the file which contain the restricted studied map in the simulation (with extension). -##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation. -##' -##' @details -##' -##' The observed habitat is derived from the cesbio map, the simulated habitat -##' is derived from FATE simulated relative abundance, based on a random forest -##' algorithm trained on CBNA data. To compare observations and simulations, the function -##' compute confusion matrix between observation and prediction and then compute the TSS -##' for each habitat h (number of prediction of habitat h/number of observation -##' of habitat h + number of non-prediction of habitat h/number of non-observation -##' of habitat h). The final metrics this script use is the mean of TSS per habitat over all -##' habitats, weighted by the share of each habitat in the observed habitat distribution. -##' -##' @return -##' -##' Two folders are created in name.simulation folder : -##' \describe{ -##' \item{\file{VALIDATION/HABITAT/sim.version}}{containing the prepared CBNA data, -##' RF model, the performance analyzes (confusion matrix and TSS) for the training and -##' testing parts of the RF model, the habitat performance file, the habitat prediction file with -##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} -##' \item{\file{DATA_OBS}}{maps of observed habitat and csv files of PFG and sites releves.} -##' } -##' -### END OF HEADER ############################################################## - - -POST_FATE.validation_habitat = function(name.simulation - , sim.version - , obs.path - , releves.PFG - , releves.sites - , hab.obs - , habitat.FATE.map - , validation.mask) -{ - - ## LIBRARIES - require(data.table) - require(raster) - require(RFate) - require(reshape2) - require(stringr) - require(foreign) - require(stringr) - require(dplyr) - require(sp) - options("rgdal_show_exportToProj4_warnings"="none") - require(rgdal) - require(randomForest) - require(ggplot2) - require(ggradar) - require(tidyverse) - require(ggpubr) - require(gridExtra) - require(vegan) - require(parallel) - require(scales) - require(class) - require(caret) - require(sampling) - require(tidyselect) - require(grid) - require(gtable) - require(scales) - require(cowplot) - require(sf) - require(visNetwork) - require(foreach) - require(doParallel) - require(prettyR) - require(vcd) - - ## GLOBAL PARAMETERS - - # Create directories - dir.create(paste0(name.simulation, "/VALIDATION"), recursive = TRUE) - dir.create(paste0(name.simulation, "/VALIDATION/HABITAT"), recursive = TRUE) - dir.create(paste0(name.simulation, "/VALIDATION/HABITAT/", sim.version), recursive = TRUE) - - # General - output.path = paste0(name.simulation, "/VALIDATION") - - # Useful elements to extract from the simulation - simulation.map=raster(paste0(name.simulation,"/DATA/MASK/MASK_Champsaur.tif")) - - # For habitat validation - # CBNA releves data habitat map - releves.PFG<-read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) - releves.sites<-st_read(paste0(obs.path, releves.sites)) - hab.obs<-raster(paste0(obs.path, hab.obs)) - # Habitat mask at FATE simu resolution - # hab.obs.modif<-projectRaster(from = hab.obs, to = simulation.map, res = res(hab.obs)[1], crs = crs(projection(simulation.mask))) - # habitat.FATE.map<-crop(hab.obs.modif, simulation.map) - habitat.FATE.map<-raster(paste0(obs.path, habitat.FATE.map)) - validation.mask<-raster(paste0(obs.path, validation.mask)) - - # Provide a color df - col.df<-data.frame( - habitat=c("agricultural.grassland","coniferous.forest","deciduous.forest","natural.grassland","woody.heatland"), - failure=c("yellow","blueviolet","aquamarine","chartreuse1","lightsalmon"), - success=c("darkorange1","blue4","aquamarine3","chartreuse3","firebrick4")) - - # Other - studied.habitat=c("coniferous.forest","deciduous.forest","natural.grassland","woody.heatland","agricultural.grassland") - RF.param = list( - share.training=0.7, - ntree=500) - predict.all.map<-T - - ## TRAIN A RF ON CBNA DATA - - RF.model <- train.RF.habitat(releves.PFG = releves.PFG - , releves.sites = releves.sites - , hab.obs = hab.obs - , external.training.mask = NULL - , studied.habitat = studied.habitat - , RF.param = RF.param - , output.path = output.path - , perStrata = F - , sim.version = sim.version) - - ## USE THE RF MODEL TO VALIDATE OUTPUT - - habitats.results <- do.habitat.validation(output.path = output.path - , RF.model = RF.model - , habitat.FATE.map = habitat.FATE.map - , validation.mask = validation.mask - , simulation.map = simulation.map - , predict.all.map = predict.all.map - , sim.version = sim.version - , name.simulation = name.simulation - , perStrata = F) - - ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT - - prediction.map <- plot.predicted.habitat(predicted.habitat = habitats.results - , col.df = col.df - , simulation.map = simulation.map - , output.path = output.path - , sim.version = sim.version) - return(prediction.map) - -} - From 8d05b710c54ac7d176b2032c24a9840e252c3841 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:22:56 +0100 Subject: [PATCH 003/176] Delete UTILS.do_habitat_validation.R --- UTILS.do_habitat_validation.R | 263 ---------------------------------- 1 file changed, 263 deletions(-) delete mode 100644 UTILS.do_habitat_validation.R diff --git a/UTILS.do_habitat_validation.R b/UTILS.do_habitat_validation.R deleted file mode 100644 index 6a6c07f..0000000 --- a/UTILS.do_habitat_validation.R +++ /dev/null @@ -1,263 +0,0 @@ -### HEADER ##################################################################### -##' -##' @title Compare observed and simulated habitat of a \code{FATE} simulation -##' at the last simulation year. -##' -##' @name do.habitat.validation -##' -##' @author Matthieu ... & Maxime Delprat -##' -##' @description To compare observations and simulations, this function compute -##' confusion matrix between observation and prediction and then compute the TSS -##' for each habitat. -##' -##' @param output.path access path to the for the folder where output files -##' will be created. -##' @param RF.model random forest model trained on CBNA data (train.RF.habitat -##' function) -##' @param habitat.FATE.map a raster map of the observed habitat in the -##' studied area. -##' @param validation.mask a raster mask that specified which pixels need validation. -##' @param simulation.map a raster map of the whole studied area use to check -##' the consistency between simulation map and the observed habitat map. -##' @param predict.all.map a TRUE/FALSE vector. If TRUE, the script will predict -##' habitat for the whole map. -##' @param sim.version name of the simulation we want to validate. -##' @param name.simulation simulation folder name. -##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined -##' by strata in each pixel. If FALSE, PFG abundance is defined for all strata. -##' -##' @details -##' -##' After several preliminary checks, the function is going to prepare the observations -##' database by extracting the observed habitat from a raster map. Then, for each -##' simulations (sim.version), the script take the evolution abundance for each PFG -##' and all strata file and predict the habitat for the whole map (if option selected) -##' thanks to the RF model.Finally, the function compute habitat performance based on -##' TSS for each habitat. -##' -##' @return -##' -##' Habitat performance file -##' If option selected, the function returns an habitat prediction file with -##' observed and simulated habitat for each pixel of the whole map. -##' -### END OF HEADER ############################################################## - - -do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata) { - - #notes - # we prepare the relevé data in this function, but in fact we could provide them directly if we adjust the code - - ########################### - #I. Preliminary checks - ########################### - - #check if strata definition used in the RF model is the same as the one used to analyze FATE output - if(perStrata==F){ - list.strata<-"all" - }else{ - stop("check 'perStrata' parameter and/or the names of strata in param$list.strata.releves & param$list.strata.simul") - } - - #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) - if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map)==res(validation.mask))){ - stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") - } - - #consistency between habitat.FATE.map and simulation.map - if(!compareCRS(simulation.map,habitat.FATE.map)){ - print("reprojecting habitat.FATE.map to match simulation.map crs") - habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) - } - if(!all(res(habitat.FATE.map)==res(simulation.map))){ - stop("provide habitat.FATE.map with same resolution as simulation.map") - } - if(extent(simulation.map)!=extent(habitat.FATE.map)){ - print("cropping habitat.FATE.map to match simulation.map") - habitat.FATE.map<-crop(x=habitat.FATE.map,y=simulation.map) - } - if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ - print("setting origin habitat.FATE.map to match simulation.map") - origin(habitat.FATE.map)<-origin(simulation.map) - } - if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates - stop("habitat.FATE.map could not be coerced to match simulation.map") - }else{ - print("simulation.map & habitat.FATE.map are (now) consistent") - } - - #adjust validation.mask accordingly - if(!all(res(habitat.FATE.map)==res(validation.mask))){ - validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") - } - if(extent(validation.mask)!=extent(habitat.FATE.map)){ - validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) - } - if(!compareRaster(validation.mask,habitat.FATE.map)){ - stop("error in correcting validation.mask to match habitat.FATE.map") - }else{ - print("validation.mask is (now) consistent with (modified) habitat.FATE.map") - } - - #check consistency for PFG & strata classes between FATE output vs the RF model - - RF.predictors<-rownames(RF.model$importance) - RF.PFG<-unique(str_sub(RF.predictors,1,2)) - - FATE.PFG<-str_sub(list.files(paste0(name.simulation,"/DATA/PFGS/SUCC")),6,7) - - if(length(setdiff(FATE.PFG,RF.PFG))>0|length(setdiff(RF.PFG,FATE.PFG))>0){ - stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") - } - - - ######################################################################################### - #II. Prepare database for FATE habitat - ######################################################################################### - - #index of the pixels in the simulation area - in.region.pixels<-which(getValues(simulation.map)==1) - - #habitat df for the whole simulation area - habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) - habitat.whole.area.df<-habitat.whole.area.df[in.region.pixels,] - habitat.whole.area.df<-merge(habitat.whole.area.df,dplyr::select(levels(habitat.FATE.map)[[1]],c(ID,habitat)),by.x="code.habitat",by.y="ID") - habitat.whole.area.df<-filter(habitat.whole.area.df,is.element(habitat,RF.model$classes)) - - print(cat("Habitat considered in the prediction exercise: ",c(unique(habitat.whole.area.df$habitat)),"\n",sep="\t")) - - print("Habitat in the simulation area:") - table(habitat.whole.area.df$habitat,useNA="always") - - print("Habitat in the subpart of the simulation area used for validation:") - table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation==1],useNA="always") - - ############################## - # III. Loop on simulations - ######################### - - print("processing simulations") - - registerDoParallel(detectCores()-2) - results.simul <- foreach(i=1:length(sim.version),.packages = c("dplyr","forcats","reshape2","randomForest","vcd","caret")) %dopar%{ - - ########################" - # III.1. Data preparation - ######################### - - #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,-c(3:44)] - colnames(simu_PFG) = c("PFG", "pixel", "abs") - - #aggregate per strata group with the correspondance provided in input - simu_PFG$new.strata<-NA - - #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) - if(perStrata==F){ - simu_PFG$new.strata<-"A" - } - - simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") - - #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) - simu_PFG<-aggregate(abs~pixel+strata+PFG,data=simu_PFG,FUN="sum") - - #transform absolute abundance into relative abundance (no pb if all combination PFG*strata are not present, since then the value is 0!) - simu_PFG<-simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance= round(prop.table(abs),digits=2)) #those are proportions, not percentages - simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) - simu_PFG<-as.data.frame(simu_PFG) - - #drop the absolute abundance - simu_PFG$abs<-NULL - - #set a factor structure - simu_PFG$PFG<-as.factor(simu_PFG$PFG) - simu_PFG$strata<-as.factor(simu_PFG$strata) - - #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) - simu_PFG$PFG<-fct_expand(simu_PFG$PFG,RF.PFG) - simu_PFG$strata<-fct_expand(simu_PFG$strata,list.strata) - - #cast - simu_PFG<-dcast(simu_PFG,pixel~PFG*strata,value.var=c("relative.abundance"),fill=0,drop=F) - - #merge PFG info and habitat + transform habitat into factor - - #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable - data.FATE.PFG.habitat<-merge(simu_PFG,habitat.whole.area.df,by="pixel") #at this stage we have all the pixels in the simulation area - data.FATE.PFG.habitat$habitat<-factor(data.FATE.PFG.habitat$habitat,levels=RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs - - ############################ - # III.2. Prediction of habitat with the RF algorithm - ################################# - - data.validation<-filter(data.FATE.PFG.habitat,for.validation==1) - x.validation<-select(data.validation,all_of(RF.predictors)) - y.validation<-data.validation$habitat - - y.validation.predicted<-predict(object=RF.model,newdata=x.validation,type="response",norm.votes=T) - - ############################## - # III.3. Analysis of the results - ################################ - - confusion.validation<-confusionMatrix(data=y.validation.predicted,reference=fct_expand(y.validation,levels(y.validation.predicted))) - - synthesis.validation<-data.frame(habitat=colnames(confusion.validation$table),sensitivity=confusion.validation$byClass[,1],specificity=confusion.validation$byClass[,2],weight=colSums(confusion.validation$table)/sum(colSums(confusion.validation$table))) - synthesis.validation<-synthesis.validation%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) - - aggregate.TSS.validation<-round(sum(synthesis.validation$weight*synthesis.validation$TSS,na.rm=T),digits=2) - - ######################## - # III.4. Predict habitat for the whole map if option selected (do it only for a small number of simulations) - ############################################ - - if(predict.all.map==T){ - - y.all.map.predicted = predict(object=RF.model,newdata=select(data.FATE.PFG.habitat,all_of(RF.predictors)),type="response",norm.votes=T) - y.all.map.predicted = as.data.frame(y.all.map.predicted) - y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel - colnames(y.all.map.predicted) = c(sim.version, "pixel") - - }else{ - y.all.map.predicted<-NULL - } - - #prepare outputs - - output.validation<-c(synthesis.validation$TSS,aggregate.TSS.validation) - names(output.validation)<-c(synthesis.validation$habitat,"aggregated") - - output<-list(output.validation,y.all.map.predicted) - names(output)<-c("output.validation","y.all.map.predicted") - - return(output) - } - #end of the loop on simulations - - #deal with the results regarding model performance - habitat.performance<-as.data.frame(matrix(unlist(lapply(results.simul,"[[",1)),ncol=length(RF.model$classes)+1,byrow=T)) - names(habitat.performance)<-c(RF.model$classes,"weighted") - habitat.performance$simulation<-sim.version - - #save - write.csv(habitat.performance,paste0(output.path,"/HABITAT/", sim.version, "/performance.habitat.csv"),row.names=F) - - print("habitat performance saved") - - #deal with the results regarding habitat prediction over the whole map - all.map.prediction = results.simul[[1]]$y.all.map.predicted - all.map.prediction = merge(all.map.prediction, select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") - all.map.prediction = rename(all.map.prediction,"true.habitat"="habitat") - - #save - write.csv(all.map.prediction,paste0(output.path,"/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names=F) - - #return results - return(all.map.prediction) - -} - From 74647c2188b28dba03fd968752c6f2859c280893 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:23:06 +0100 Subject: [PATCH 004/176] Delete UTILS.plot_predicted_habitat.R --- UTILS.plot_predicted_habitat.R | 137 --------------------------------- 1 file changed, 137 deletions(-) delete mode 100644 UTILS.plot_predicted_habitat.R diff --git a/UTILS.plot_predicted_habitat.R b/UTILS.plot_predicted_habitat.R deleted file mode 100644 index a415c26..0000000 --- a/UTILS.plot_predicted_habitat.R +++ /dev/null @@ -1,137 +0,0 @@ -### HEADER ##################################################################### -##' -##' @title Create a raster map of habitat prediction for a specific \code{FATE} -##' simulation at the last simulation year. -##' -##' @name plot.predicted.habitat -##' -##' @author Matthieu ... & Maxime Delprat -##' -##' @description This script is designed to create a raster map of habitat prediction -##' based on a habitat prediction file. For each pixel, the habitat failure or success value -##' is associated to a color and then, the map is built. -##' -##' @param predicted habitat a csv file created by the do.habitat.validation function -##' which contain, for each pixel of the studied map, the simulated and observed habitat. -##' @param col.df a data frame with all the colors associated with the failure or -##' success of each studied habitat prediction. -##' @param simulation.map a raster map of the whole studied area. -##' @param output.path access path to the for the folder where output files -##' will be created. -##' @param sim.version name of the simulation we want to validate. -##' -##' @details -##' -##' The function determine true/false prediction ('failure' if false, 'success' if true) -##' and prepare a dataframe containing color and habitat code. Then, the script merge -##' the prediction dataframe with the color and code habitat dataframe. Finally, -##' the function draw a raster map and a plot of prediction habitat over it thanks -##' to the data prepared before. -##' -##' @return -##' -##' a synthetic.prediction.png file which contain the final prediction plot. -### END OF HEADER ############################################################## - - -plot.predicted.habitat<-function(predicted.habitat - , col.df - , simulation.map - , output.path - , sim.version) -{ - - #auxiliary function to compute the proportion of simulations lead to the modal prediction - count.habitat<-function(df){ - index<-which(names(df)=="modal.predicted.habitat") - prop.simu<-sum(df[-index]==as.character(df[index]))/(length(names(df))-1) - return(prop.simu) - } - - #compute modal predicted habitat and the proportion of simulations predicting this habitat (for each pixel) - predicted.habitat$modal.predicted.habitat<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version))),1,Mode) - predicted.habitat$modal.predicted.habitat[predicted.habitat$modal.predicted.habitat==">1 mode"]<-"ambiguous" - predicted.habitat$confidence<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version),modal.predicted.habitat)),1,FUN=function(x) count.habitat(x)) - - - #true/false prediction - predicted.habitat$prediction.code<-"failure" - predicted.habitat$prediction.code[predicted.habitat$modal.predicted.habitat==predicted.habitat$true.habitat]<-"success" - - #prepare a df containing color & habitat code (to facilitate conversion into raster) - col.df.long<-data.table::melt(data=setDT(col.df),id.vars="habitat",variable.name="prediction.code",value.name="color") - - habitat.code.df<-unique(dplyr::select(predicted.habitat,c(modal.predicted.habitat,prediction.code))) - habitat.code.df$habitat.code<-seq(from=1,to=dim(habitat.code.df)[1],by=1) - habitat.code.df<-rename(habitat.code.df,"habitat"="modal.predicted.habitat") - - habitat.code.df<-merge(habitat.code.df,col.df.long,by=c("habitat","prediction.code")) - habitat.code.df$label<-paste0(habitat.code.df$habitat," (",habitat.code.df$prediction.code,")") - - #deal with out of scope habitat - out.of.scope<-data.frame(habitat="out.of.scope",prediction.code="",habitat.code=0,color="white",label="out of scope") - habitat.code.df<-rbind(habitat.code.df,out.of.scope) - - habitat.code.df$label<-as.factor(habitat.code.df$label) - - #order the df - habitat.code.df<-habitat.code.df[order(habitat.code.df$label),] #to be sure it's in te right order (useful to ensure the correspondance between label and color in the ggplot function) - - - #merge the prediction df with the df containing color and habitat code - predicted.habitat<-merge(predicted.habitat,habitat.code.df,by.x=c("modal.predicted.habitat","prediction.code"),by.y=c("habitat","prediction.code")) - - - #plot - - #prepare raster - prediction.map<-raster(nrows=nrow(simulation.map),ncols=ncol(simulation.map),crs=crs(simulation.map),ext=extent(simulation.map), resolution=res(simulation.map)) - - prediction.map[]<-0 #initialization of the raster, corresponding to "out of scope habitats" - prediction.map[predicted.habitat$pixel]<-predicted.habitat$habitat.code - - #ratify - prediction.map<-ratify(prediction.map) - prediction.map.rat<-levels(prediction.map)[[1]] - prediction.map.rat<-merge(prediction.map.rat,habitat.code.df,by.x="ID",by.y="habitat.code") - levels(prediction.map)<-prediction.map.rat - - #save the raster - writeRaster(prediction.map,filename = paste0(output.path,"/HABITAT/", sim.version, "/synthetic.prediction.grd"),overwrite=T) - - - #plot on R - #convert into xy - xy.prediction<-as.data.frame(prediction.map,xy=T) - names(xy.prediction)<-c("x","y","habitat","prediction.code","color","label") - xy.prediction<-xy.prediction[complete.cases(xy.prediction),] - - #plot - prediction.plot<- - ggplot(xy.prediction, aes(x=x, y=y, fill=factor(label)))+ - geom_raster(show.legend = T) + - coord_equal()+ - scale_fill_manual(values = as.character(habitat.code.df$color))+ #ok only if habitat.code.df has been ordered according to "label" - ggtitle(paste0("Modal prediction over ",length(sim.version)," simulations"))+ - guides(fill=guide_legend(nrow=4,byrow=F))+ - theme( - plot.title = element_text(size = 8), - legend.text = element_text(size = 8, colour ="black"), - legend.title = element_blank(), - legend.position = "bottom", - axis.title.x=element_blank(), - axis.text.x=element_blank(), - axis.ticks.x=element_blank(), - axis.title.y=element_blank(), - axis.text.y=element_blank(), - axis.ticks.y=element_blank() - ) - - #save the map - ggsave(filename="synthetic.prediction.png",plot = prediction.plot,path = paste0(output.path, "/HABITAT/", sim.version),scale = 1,dpi = 300,limitsize = F,width = 15,height = 15,units ="cm") - - #return the map - return(prediction.plot) - -} - From 848cab7d4595a0ead65ae998457541f36e6e0490 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:23:15 +0100 Subject: [PATCH 005/176] Delete UTILS.train_RF_habitat.R --- UTILS.train_RF_habitat.R | 221 --------------------------------------- 1 file changed, 221 deletions(-) delete mode 100644 UTILS.train_RF_habitat.R diff --git a/UTILS.train_RF_habitat.R b/UTILS.train_RF_habitat.R deleted file mode 100644 index 8174f9a..0000000 --- a/UTILS.train_RF_habitat.R +++ /dev/null @@ -1,221 +0,0 @@ -### HEADER ##################################################################### -##' -##' @title Create a random forest algorithm trained on CBNA data, in order to -##' obtain the simulated habitat, derived from a \code{FATE} simulation. -##' -##' @name train.RF.habitat -##' -##' @author Matthieu & Maxime Delprat -##' -##' @description This script is designed to produce a random forest model -##' trained on observed PFG abundance, sites releves and a map of observed -##' habitat. -##' -##' @param releves.PFG a data frame with Braund-Blanquet abundance at each site -##' and each PFG and strata. -##' @param releves.sites a data frame with coordinates and a description of -##' the habitat associated with the dominant species of each site in the -##' studied map. -##' @param hab.obs a raster map of the observed habitat in the -##' extended studied area. -##' @param external.training.mask default \code{NULL}. (optional) Keep only -##' releves data in a specific area. -##' @param studied.habitat a vector that specifies habitats that we take -##' into account for the validation. -##' @param RF.param a list of 2 parameters for random forest model : -##' share.training defines the size of the trainig part of the data base. -##' ntree is the number of trees build by the algorithm, it allows to reduce -##' the prediction error. -##' @param output.path access path to the for the folder where output files -##' will be created. -##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined -##' by strata in each site. If FALSE, PFG abundance is defined for all strata. -##' @param sim.version name of the simulation we want to validate. -##' -##' @details -##' -##' This function transform PFG Braund-Blanquet abundance in relative abundance, -##' get habitat information from the releves map, keep only relees on interesting -##' habitat and then builds de random forest model. Finally, the function analyzes -##' the model performance with computation of confusion matrix and TSS for -##' the traning and testing sample. -##' -##' @return -##' -##' 2 prepared CBNA releves files are created before the building of the random -##' forest model in a habitat validation folder. -##' 5 more files are created at the end of the script to save the RF model and -##' the performance analyzes (confusion matrix and TSS) for the training and -##' testing parts. -##' -### END OF HEADER ############################################################## - - -train.RF.habitat<-function(releves.PFG - , releves.sites - , hab.obs - , external.training.mask=NULL - , studied.habitat - , RF.param - , output.path - , perStrata - , sim.version) -{ - - #1. Compute relative abundance metric - ######################################### - - #identify sites with wrong BB values (ie values that cannot be converted by the PRE_FATE.abundBraunBlanquet function) - releves.PFG<-filter(releves.PFG,is.element(BB,c(NA, "NA", 0, "+", "r", 1:5))) - - #transformation into coverage percentage - releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage - - if(perStrata==T){ - aggregated.releves.PFG<-aggregate(coverage~site+PFG+strata,data=releves.PFG,FUN="sum") - }else if(perStrata==F){ - aggregated.releves.PFG<-aggregate(coverage~site+PFG,data=releves.PFG,FUN="sum") - aggregated.releves.PFG$strata<-"A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) - } - - #transformation into a relative metric (here relative.metric is relative coverage) - aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG %>% group_by(site,strata) %>% mutate(relative.metric= round(prop.table(coverage),digits = 2))) #rel is proportion of total pct_cov, not percentage - aggregated.releves.PFG$relative.metric[is.na(aggregated.releves.PFG$relative.metric)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) - aggregated.releves.PFG$coverage<-NULL - - print("releve data have been transformed into a relative metric") - - #2. Cast the df - ####################### - - #transfo into factor to be sure to create all the combination when doing "dcast" - aggregated.releves.PFG$PFG<-as.factor(aggregated.releves.PFG$PFG) - aggregated.releves.PFG$strata<-as.factor(aggregated.releves.PFG$strata) - - aggregated.releves.PFG<-dcast(setDT(aggregated.releves.PFG),site~PFG+strata,value.var=c("relative.metric"),fill=0,drop=F) - - #3. Get habitat information - ################################### - - #get sites coordinates - aggregated.releves.PFG<-merge(dplyr::select(releves.sites,c(site)),aggregated.releves.PFG,by="site") - - #get habitat code and name - if(compareCRS(aggregated.releves.PFG,hab.obs)){ - aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) - }else{ - aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(hab.obs)) - aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) - } - - #correspondance habitat code/habitat name - table.habitat.releve<-levels(hab.obs)[[1]] - - aggregated.releves.PFG<-merge(aggregated.releves.PFG,dplyr::select(table.habitat.releve,c(ID,habitat)),by.x="code.habitat",by.y="ID") - - #(optional) keep only releves data in a specific area - if(!is.null(external.training.mask)){ - - if(compareCRS(aggregated.releves.PFG,external.training.mask)==F){ #as this stage it is not a problem to transform crs(aggregated.releves.PFG) since we have no more merge to do (we have already extracted habitat info from the map) - aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(external.training.mask)) - } - - aggregated.releves.PFG<-st_crop(x=aggregated.releves.PFG,y=external.training.mask) - print("'releve' map has been cropped to match 'external.training.mask'.") - } - - - # 4. Keep only releve on interesting habitat - ###################################################" - - aggregated.releves.PFG<-filter(aggregated.releves.PFG,is.element(habitat,studied.habitat)) #filter non interesting habitat + NA - - print(cat("habitat classes used in the RF algo: ",unique(aggregated.releves.PFG$habitat),"\n",sep="\t")) - - # 5. Save data - ##################### - - st_write(aggregated.releves.PFG,paste0(output.path,"/HABITAT/", sim.version, "/releve.PFG.habitat.shp"),overwrite=T,append=F) - write.csv(aggregated.releves.PFG,paste0(output.path,"/HABITAT/", sim.version, "/CBNA.releves.prepared.csv"),row.names = F) - - # 6. Small adjustment in data structure - ########################################## - - aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG) #get rid of the spatial structure before entering the RF process - aggregated.releves.PFG$habitat<-as.factor(aggregated.releves.PFG$habitat) - - # 7.Random forest - ###################################### - - #separate the database into a training and a test part - set.seed(123) - - training.site<-sample(aggregated.releves.PFG$site,size=RF.param$share.training*length(aggregated.releves.PFG$site),replace = F) - releves.training<-filter(aggregated.releves.PFG,is.element(site,training.site)) - releves.testing<-filter(aggregated.releves.PFG,!is.element(site,training.site)) - - #train the model (with correction for imbalances in sampling) - - #run optimization algo (careful : optimization over OOB...) - mtry.perf<-as.data.frame( - tuneRF( - x=select(releves.training,-c(code.habitat,site,habitat,geometry)), - y=releves.training$habitat, - strata=releves.training$habitat, - sampsize=min(table(releves.training$habitat)), - ntreeTry=RF.param$ntree, - stepFactor=2, improve=0.05,doBest=FALSE,plot=F,trace=F - ) - ) - - #select mtry - mtry<-mtry.perf$mtry[mtry.perf$OOBError==min(mtry.perf$OOBError)][1] #the lowest n achieving minimum OOB - - #run real model - model<- randomForest( - x=select(releves.training,-c(code.habitat,site,habitat,geometry)), - y=releves.training$habitat, - xtest=select(releves.testing,-c(code.habitat,site,habitat,geometry)), - ytest=releves.testing$habitat, - strata=releves.training$habitat, - min(table(releves.training$habitat)), - ntree=RF.param$ntree, - mtry=mtry, - norm.votes=TRUE, - keep.forest=TRUE - ) - - #analyse model performance - - # Analysis on the training sample - - confusion.training<-confusionMatrix(data=model$predicted,reference=releves.training$habitat) - - synthesis.training<-data.frame(habitat=colnames(confusion.training$table),sensitivity=confusion.training$byClass[,1],specificity=confusion.training$byClass[,2],weight=colSums(confusion.training$table)/sum(colSums(confusion.training$table))) #warning: prevalence is the weight of predicted habitat, not of observed habitat - synthesis.training<-synthesis.training%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) - - aggregate.TSS.training<-round(sum(synthesis.training$weight*synthesis.training$TSS),digits=2) - - # Analysis on the testing sample - - confusion.testing<-confusionMatrix(data=model$test$predicted,reference=releves.testing$habitat) - - synthesis.testing<-data.frame(habitat=colnames(confusion.testing$table),sensitivity=confusion.testing$byClass[,1],specificity=confusion.testing$byClass[,2],weight=colSums(confusion.testing$table)/sum(colSums(confusion.testing$table)))#warning: prevalence is the weight of predicted habitat, not of observed habitat - synthesis.testing<-synthesis.testing%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) - - aggregate.TSS.testing<-round(sum(synthesis.testing$weight*synthesis.testing$TSS),digits=2) - - - # 8. Save and return output - #######################################" - - write_rds(model,paste0(output.path,"/HABITAT/", sim.version, "/RF.model.rds"),compress="none") - write.csv(synthesis.training,paste0(output.path,"/HABITAT/", sim.version, "/RF_perf.per.hab_training.csv"),row.names=F) - write.csv(aggregate.TSS.training,paste0(output.path,"/HABITAT/", sim.version, "/RF_aggregate.TSS_training.csv"),row.names=F) - write.csv(synthesis.testing,paste0(output.path,"/HABITAT/", sim.version, "/RF_perf.per.hab_testing.csv"),row.names=F) - write.csv(aggregate.TSS.testing,paste0(output.path,"/HABITAT/", sim.version, "/RF_aggregate.TSS_testing.csv"),row.names=F) - - return(model) - -} - From d3d0ad60ac182870fe4584f36de2e2433b258b03 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:35:20 +0100 Subject: [PATCH 006/176] Add files via upload Addition of new "Utils" function and a new POST_FATE function for habitat validation of a FATE simulation --- R/POST_FATE.validation_habitat.R | 169 ++++++++++++++++++++ R/UTILS.do_habitat_validation.R | 263 +++++++++++++++++++++++++++++++ R/UTILS.plot_predicted_habitat.R | 137 ++++++++++++++++ R/UTILS.train_RF_habitat.R | 221 ++++++++++++++++++++++++++ 4 files changed, 790 insertions(+) create mode 100644 R/POST_FATE.validation_habitat.R create mode 100644 R/UTILS.do_habitat_validation.R create mode 100644 R/UTILS.plot_predicted_habitat.R create mode 100644 R/UTILS.train_RF_habitat.R diff --git a/R/POST_FATE.validation_habitat.R b/R/POST_FATE.validation_habitat.R new file mode 100644 index 0000000..59ba9a2 --- /dev/null +++ b/R/POST_FATE.validation_habitat.R @@ -0,0 +1,169 @@ +### HEADER ##################################################################### +##' +##' @title Compute habitat performance and create a prediction plot of habitat +##' for a whole map of a \code{FATE} simulation. +##' +##' @name POST_FATE.validation.habitat +##' +##' @author Matthieu .. & Maxime Delprat +##' +##' @description This script compare habitat simulations and observations and +##' create a map to visualize this comparison with all the the \code{FATE} and +##' observed data. +##' +##' @param name.simulation simulation folder name. +##' @param sim.version name of the simulation we want to validate (it works with +##' only one sim.version). +##' @param obs.path the function needs observed data, please create a folder for them in your +##' simulation folder and then indicate in this parmeter the access path to this folder. +##' @param releves.PFG name of file which contain the observed Braund-Blanquet abundance at each site +##' and each PFG and strata (with extension). +##' @param releves.site name of the file which contain coordinates and a description of +##' the habitat associated with the dominant species of each site in the studied map (with extension). +##' @param hab.obs name of the file which contain the extended studied map in the simulation (with extension). +##' @param habitat.FATE.map name of the file which contain the restricted studied map in the simulation (with extension). +##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation. +##' +##' @details +##' +##' The observed habitat is derived from the cesbio map, the simulated habitat +##' is derived from FATE simulated relative abundance, based on a random forest +##' algorithm trained on CBNA data. To compare observations and simulations, the function +##' compute confusion matrix between observation and prediction and then compute the TSS +##' for each habitat h (number of prediction of habitat h/number of observation +##' of habitat h + number of non-prediction of habitat h/number of non-observation +##' of habitat h). The final metrics this script use is the mean of TSS per habitat over all +##' habitats, weighted by the share of each habitat in the observed habitat distribution. +##' +##' @return +##' +##' Two folders are created in name.simulation folder : +##' \describe{ +##' \item{\file{VALIDATION/HABITAT/sim.version}}{containing the prepared CBNA data, +##' RF model, the performance analyzes (confusion matrix and TSS) for the training and +##' testing parts of the RF model, the habitat performance file, the habitat prediction file with +##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} +##' \item{\file{DATA_OBS}}{maps of observed habitat and csv files of PFG and sites releves.} +##' } +##' +### END OF HEADER ############################################################## + + +POST_FATE.validation_habitat = function(name.simulation + , sim.version + , obs.path + , releves.PFG + , releves.sites + , hab.obs + , habitat.FATE.map + , validation.mask) +{ + + ## LIBRARIES + require(data.table) + require(raster) + require(RFate) + require(reshape2) + require(stringr) + require(foreign) + require(stringr) + require(dplyr) + require(sp) + options("rgdal_show_exportToProj4_warnings"="none") + require(rgdal) + require(randomForest) + require(ggplot2) + require(ggradar) + require(tidyverse) + require(ggpubr) + require(gridExtra) + require(vegan) + require(parallel) + require(scales) + require(class) + require(caret) + require(sampling) + require(tidyselect) + require(grid) + require(gtable) + require(scales) + require(cowplot) + require(sf) + require(visNetwork) + require(foreach) + require(doParallel) + require(prettyR) + require(vcd) + + ## GLOBAL PARAMETERS + + # Create directories + dir.create(paste0(name.simulation, "/VALIDATION"), recursive = TRUE) + dir.create(paste0(name.simulation, "/VALIDATION/HABITAT"), recursive = TRUE) + dir.create(paste0(name.simulation, "/VALIDATION/HABITAT/", sim.version), recursive = TRUE) + + # General + output.path = paste0(name.simulation, "/VALIDATION") + + # Useful elements to extract from the simulation + simulation.map=raster(paste0(name.simulation,"/DATA/MASK/MASK_Champsaur.tif")) + + # For habitat validation + # CBNA releves data habitat map + releves.PFG<-read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) + releves.sites<-st_read(paste0(obs.path, releves.sites)) + hab.obs<-raster(paste0(obs.path, hab.obs)) + # Habitat mask at FATE simu resolution + # hab.obs.modif<-projectRaster(from = hab.obs, to = simulation.map, res = res(hab.obs)[1], crs = crs(projection(simulation.mask))) + # habitat.FATE.map<-crop(hab.obs.modif, simulation.map) + habitat.FATE.map<-raster(paste0(obs.path, habitat.FATE.map)) + validation.mask<-raster(paste0(obs.path, validation.mask)) + + # Provide a color df + col.df<-data.frame( + habitat=c("agricultural.grassland","coniferous.forest","deciduous.forest","natural.grassland","woody.heatland"), + failure=c("yellow","blueviolet","aquamarine","chartreuse1","lightsalmon"), + success=c("darkorange1","blue4","aquamarine3","chartreuse3","firebrick4")) + + # Other + studied.habitat=c("coniferous.forest","deciduous.forest","natural.grassland","woody.heatland","agricultural.grassland") + RF.param = list( + share.training=0.7, + ntree=500) + predict.all.map<-T + + ## TRAIN A RF ON CBNA DATA + + RF.model <- train.RF.habitat(releves.PFG = releves.PFG + , releves.sites = releves.sites + , hab.obs = hab.obs + , external.training.mask = NULL + , studied.habitat = studied.habitat + , RF.param = RF.param + , output.path = output.path + , perStrata = F + , sim.version = sim.version) + + ## USE THE RF MODEL TO VALIDATE OUTPUT + + habitats.results <- do.habitat.validation(output.path = output.path + , RF.model = RF.model + , habitat.FATE.map = habitat.FATE.map + , validation.mask = validation.mask + , simulation.map = simulation.map + , predict.all.map = predict.all.map + , sim.version = sim.version + , name.simulation = name.simulation + , perStrata = F) + + ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT + + prediction.map <- plot.predicted.habitat(predicted.habitat = habitats.results + , col.df = col.df + , simulation.map = simulation.map + , output.path = output.path + , sim.version = sim.version) + return(prediction.map) + +} + diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R new file mode 100644 index 0000000..4624baa --- /dev/null +++ b/R/UTILS.do_habitat_validation.R @@ -0,0 +1,263 @@ +### HEADER ##################################################################### +##' +##' @title Compare observed and simulated habitat of a \code{FATE} simulation +##' at the last simulation year. +##' +##' @name do.habitat.validation +##' +##' @author Matthieu .. & Maxime Delprat +##' +##' @description To compare observations and simulations, this function compute +##' confusion matrix between observation and prediction and then compute the TSS +##' for each habitat. +##' +##' @param output.path access path to the for the folder where output files +##' will be created. +##' @param RF.model random forest model trained on CBNA data (train.RF.habitat +##' function) +##' @param habitat.FATE.map a raster map of the observed habitat in the +##' studied area. +##' @param validation.mask a raster mask that specified which pixels need validation. +##' @param simulation.map a raster map of the whole studied area use to check +##' the consistency between simulation map and the observed habitat map. +##' @param predict.all.map a TRUE/FALSE vector. If TRUE, the script will predict +##' habitat for the whole map. +##' @param sim.version name of the simulation we want to validate. +##' @param name.simulation simulation folder name. +##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +##' by strata in each pixel. If FALSE, PFG abundance is defined for all strata. +##' +##' @details +##' +##' After several preliminary checks, the function is going to prepare the observations +##' database by extracting the observed habitat from a raster map. Then, for each +##' simulations (sim.version), the script take the evolution abundance for each PFG +##' and all strata file and predict the habitat for the whole map (if option selected) +##' thanks to the RF model.Finally, the function compute habitat performance based on +##' TSS for each habitat. +##' +##' @return +##' +##' Habitat performance file +##' If option selected, the function returns an habitat prediction file with +##' observed and simulated habitat for each pixel of the whole map. +##' +### END OF HEADER ############################################################## + + +do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata) { + + #notes + # we prepare the relevé data in this function, but in fact we could provide them directly if we adjust the code + + ########################### + #I. Preliminary checks + ########################### + + #check if strata definition used in the RF model is the same as the one used to analyze FATE output + if(perStrata==F){ + list.strata<-"all" + }else{ + stop("check 'perStrata' parameter and/or the names of strata in param$list.strata.releves & param$list.strata.simul") + } + + #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) + if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map)==res(validation.mask))){ + stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") + } + + #consistency between habitat.FATE.map and simulation.map + if(!compareCRS(simulation.map,habitat.FATE.map)){ + print("reprojecting habitat.FATE.map to match simulation.map crs") + habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) + } + if(!all(res(habitat.FATE.map)==res(simulation.map))){ + stop("provide habitat.FATE.map with same resolution as simulation.map") + } + if(extent(simulation.map)!=extent(habitat.FATE.map)){ + print("cropping habitat.FATE.map to match simulation.map") + habitat.FATE.map<-crop(x=habitat.FATE.map,y=simulation.map) + } + if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ + print("setting origin habitat.FATE.map to match simulation.map") + origin(habitat.FATE.map)<-origin(simulation.map) + } + if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates + stop("habitat.FATE.map could not be coerced to match simulation.map") + }else{ + print("simulation.map & habitat.FATE.map are (now) consistent") + } + + #adjust validation.mask accordingly + if(!all(res(habitat.FATE.map)==res(validation.mask))){ + validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") + } + if(extent(validation.mask)!=extent(habitat.FATE.map)){ + validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) + } + if(!compareRaster(validation.mask,habitat.FATE.map)){ + stop("error in correcting validation.mask to match habitat.FATE.map") + }else{ + print("validation.mask is (now) consistent with (modified) habitat.FATE.map") + } + + #check consistency for PFG & strata classes between FATE output vs the RF model + + RF.predictors<-rownames(RF.model$importance) + RF.PFG<-unique(str_sub(RF.predictors,1,2)) + + FATE.PFG<-str_sub(list.files(paste0(name.simulation,"/DATA/PFGS/SUCC")),6,7) + + if(length(setdiff(FATE.PFG,RF.PFG))>0|length(setdiff(RF.PFG,FATE.PFG))>0){ + stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") + } + + + ######################################################################################### + #II. Prepare database for FATE habitat + ######################################################################################### + + #index of the pixels in the simulation area + in.region.pixels<-which(getValues(simulation.map)==1) + + #habitat df for the whole simulation area + habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) + habitat.whole.area.df<-habitat.whole.area.df[in.region.pixels,] + habitat.whole.area.df<-merge(habitat.whole.area.df,dplyr::select(levels(habitat.FATE.map)[[1]],c(ID,habitat)),by.x="code.habitat",by.y="ID") + habitat.whole.area.df<-filter(habitat.whole.area.df,is.element(habitat,RF.model$classes)) + + print(cat("Habitat considered in the prediction exercise: ",c(unique(habitat.whole.area.df$habitat)),"\n",sep="\t")) + + print("Habitat in the simulation area:") + table(habitat.whole.area.df$habitat,useNA="always") + + print("Habitat in the subpart of the simulation area used for validation:") + table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation==1],useNA="always") + + ############################## + # III. Loop on simulations + ######################### + + print("processing simulations") + + registerDoParallel(detectCores()-2) + results.simul <- foreach(i=1:length(sim.version),.packages = c("dplyr","forcats","reshape2","randomForest","vcd","caret")) %dopar%{ + + ########################" + # III.1. Data preparation + ######################### + + #get simulated abundance per pixel*strata*PFG for pixels in the simulation area + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,-c(3:44)] + colnames(simu_PFG) = c("PFG", "pixel", "abs") + + #aggregate per strata group with the correspondance provided in input + simu_PFG$new.strata<-NA + + #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) + if(perStrata==F){ + simu_PFG$new.strata<-"A" + } + + simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") + + #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) + simu_PFG<-aggregate(abs~pixel+strata+PFG,data=simu_PFG,FUN="sum") + + #transform absolute abundance into relative abundance (no pb if all combination PFG*strata are not present, since then the value is 0!) + simu_PFG<-simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance= round(prop.table(abs),digits=2)) #those are proportions, not percentages + simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) + simu_PFG<-as.data.frame(simu_PFG) + + #drop the absolute abundance + simu_PFG$abs<-NULL + + #set a factor structure + simu_PFG$PFG<-as.factor(simu_PFG$PFG) + simu_PFG$strata<-as.factor(simu_PFG$strata) + + #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) + simu_PFG$PFG<-fct_expand(simu_PFG$PFG,RF.PFG) + simu_PFG$strata<-fct_expand(simu_PFG$strata,list.strata) + + #cast + simu_PFG<-dcast(simu_PFG,pixel~PFG*strata,value.var=c("relative.abundance"),fill=0,drop=F) + + #merge PFG info and habitat + transform habitat into factor + + #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable + data.FATE.PFG.habitat<-merge(simu_PFG,habitat.whole.area.df,by="pixel") #at this stage we have all the pixels in the simulation area + data.FATE.PFG.habitat$habitat<-factor(data.FATE.PFG.habitat$habitat,levels=RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs + + ############################ + # III.2. Prediction of habitat with the RF algorithm + ################################# + + data.validation<-filter(data.FATE.PFG.habitat,for.validation==1) + x.validation<-select(data.validation,all_of(RF.predictors)) + y.validation<-data.validation$habitat + + y.validation.predicted<-predict(object=RF.model,newdata=x.validation,type="response",norm.votes=T) + + ############################## + # III.3. Analysis of the results + ################################ + + confusion.validation<-confusionMatrix(data=y.validation.predicted,reference=fct_expand(y.validation,levels(y.validation.predicted))) + + synthesis.validation<-data.frame(habitat=colnames(confusion.validation$table),sensitivity=confusion.validation$byClass[,1],specificity=confusion.validation$byClass[,2],weight=colSums(confusion.validation$table)/sum(colSums(confusion.validation$table))) + synthesis.validation<-synthesis.validation%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) + + aggregate.TSS.validation<-round(sum(synthesis.validation$weight*synthesis.validation$TSS,na.rm=T),digits=2) + + ######################## + # III.4. Predict habitat for the whole map if option selected (do it only for a small number of simulations) + ############################################ + + if(predict.all.map==T){ + + y.all.map.predicted = predict(object=RF.model,newdata=select(data.FATE.PFG.habitat,all_of(RF.predictors)),type="response",norm.votes=T) + y.all.map.predicted = as.data.frame(y.all.map.predicted) + y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel + colnames(y.all.map.predicted) = c(sim.version, "pixel") + + }else{ + y.all.map.predicted<-NULL + } + + #prepare outputs + + output.validation<-c(synthesis.validation$TSS,aggregate.TSS.validation) + names(output.validation)<-c(synthesis.validation$habitat,"aggregated") + + output<-list(output.validation,y.all.map.predicted) + names(output)<-c("output.validation","y.all.map.predicted") + + return(output) + } + #end of the loop on simulations + + #deal with the results regarding model performance + habitat.performance<-as.data.frame(matrix(unlist(lapply(results.simul,"[[",1)),ncol=length(RF.model$classes)+1,byrow=T)) + names(habitat.performance)<-c(RF.model$classes,"weighted") + habitat.performance$simulation<-sim.version + + #save + write.csv(habitat.performance,paste0(output.path,"/HABITAT/", sim.version, "/performance.habitat.csv"),row.names=F) + + print("habitat performance saved") + + #deal with the results regarding habitat prediction over the whole map + all.map.prediction = results.simul[[1]]$y.all.map.predicted + all.map.prediction = merge(all.map.prediction, select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") + all.map.prediction = rename(all.map.prediction,"true.habitat"="habitat") + + #save + write.csv(all.map.prediction,paste0(output.path,"/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names=F) + + #return results + return(all.map.prediction) + +} + diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R new file mode 100644 index 0000000..4f40061 --- /dev/null +++ b/R/UTILS.plot_predicted_habitat.R @@ -0,0 +1,137 @@ +### HEADER ##################################################################### +##' +##' @title Create a raster map of habitat prediction for a specific \code{FATE} +##' simulation at the last simulation year. +##' +##' @name plot.predicted.habitat +##' +##' @author Matthieu .. & Maxime Delprat +##' +##' @description This script is designed to create a raster map of habitat prediction +##' based on a habitat prediction file. For each pixel, the habitat failure or success value +##' is associated to a color and then, the map is built. +##' +##' @param predicted habitat a csv file created by the do.habitat.validation function +##' which contain, for each pixel of the studied map, the simulated and observed habitat. +##' @param col.df a data frame with all the colors associated with the failure or +##' success of each studied habitat prediction. +##' @param simulation.map a raster map of the whole studied area. +##' @param output.path access path to the for the folder where output files +##' will be created. +##' @param sim.version name of the simulation we want to validate. +##' +##' @details +##' +##' The function determine true/false prediction ('failure' if false, 'success' if true) +##' and prepare a dataframe containing color and habitat code. Then, the script merge +##' the prediction dataframe with the color and code habitat dataframe. Finally, +##' the function draw a raster map and a plot of prediction habitat over it thanks +##' to the data prepared before. +##' +##' @return +##' +##' a synthetic.prediction.png file which contain the final prediction plot. +### END OF HEADER ############################################################## + + +plot.predicted.habitat<-function(predicted.habitat + , col.df + , simulation.map + , output.path + , sim.version) +{ + + #auxiliary function to compute the proportion of simulations lead to the modal prediction + count.habitat<-function(df){ + index<-which(names(df)=="modal.predicted.habitat") + prop.simu<-sum(df[-index]==as.character(df[index]))/(length(names(df))-1) + return(prop.simu) + } + + #compute modal predicted habitat and the proportion of simulations predicting this habitat (for each pixel) + predicted.habitat$modal.predicted.habitat<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version))),1,Mode) + predicted.habitat$modal.predicted.habitat[predicted.habitat$modal.predicted.habitat==">1 mode"]<-"ambiguous" + predicted.habitat$confidence<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version),modal.predicted.habitat)),1,FUN=function(x) count.habitat(x)) + + + #true/false prediction + predicted.habitat$prediction.code<-"failure" + predicted.habitat$prediction.code[predicted.habitat$modal.predicted.habitat==predicted.habitat$true.habitat]<-"success" + + #prepare a df containing color & habitat code (to facilitate conversion into raster) + col.df.long<-data.table::melt(data=setDT(col.df),id.vars="habitat",variable.name="prediction.code",value.name="color") + + habitat.code.df<-unique(dplyr::select(predicted.habitat,c(modal.predicted.habitat,prediction.code))) + habitat.code.df$habitat.code<-seq(from=1,to=dim(habitat.code.df)[1],by=1) + habitat.code.df<-rename(habitat.code.df,"habitat"="modal.predicted.habitat") + + habitat.code.df<-merge(habitat.code.df,col.df.long,by=c("habitat","prediction.code")) + habitat.code.df$label<-paste0(habitat.code.df$habitat," (",habitat.code.df$prediction.code,")") + + #deal with out of scope habitat + out.of.scope<-data.frame(habitat="out.of.scope",prediction.code="",habitat.code=0,color="white",label="out of scope") + habitat.code.df<-rbind(habitat.code.df,out.of.scope) + + habitat.code.df$label<-as.factor(habitat.code.df$label) + + #order the df + habitat.code.df<-habitat.code.df[order(habitat.code.df$label),] #to be sure it's in te right order (useful to ensure the correspondance between label and color in the ggplot function) + + + #merge the prediction df with the df containing color and habitat code + predicted.habitat<-merge(predicted.habitat,habitat.code.df,by.x=c("modal.predicted.habitat","prediction.code"),by.y=c("habitat","prediction.code")) + + + #plot + + #prepare raster + prediction.map<-raster(nrows=nrow(simulation.map),ncols=ncol(simulation.map),crs=crs(simulation.map),ext=extent(simulation.map), resolution=res(simulation.map)) + + prediction.map[]<-0 #initialization of the raster, corresponding to "out of scope habitats" + prediction.map[predicted.habitat$pixel]<-predicted.habitat$habitat.code + + #ratify + prediction.map<-ratify(prediction.map) + prediction.map.rat<-levels(prediction.map)[[1]] + prediction.map.rat<-merge(prediction.map.rat,habitat.code.df,by.x="ID",by.y="habitat.code") + levels(prediction.map)<-prediction.map.rat + + #save the raster + writeRaster(prediction.map,filename = paste0(output.path,"/HABITAT/", sim.version, "/synthetic.prediction.grd"),overwrite=T) + + + #plot on R + #convert into xy + xy.prediction<-as.data.frame(prediction.map,xy=T) + names(xy.prediction)<-c("x","y","habitat","prediction.code","color","label") + xy.prediction<-xy.prediction[complete.cases(xy.prediction),] + + #plot + prediction.plot<- + ggplot(xy.prediction, aes(x=x, y=y, fill=factor(label)))+ + geom_raster(show.legend = T) + + coord_equal()+ + scale_fill_manual(values = as.character(habitat.code.df$color))+ #ok only if habitat.code.df has been ordered according to "label" + ggtitle(paste0("Modal prediction over ",length(sim.version)," simulations"))+ + guides(fill=guide_legend(nrow=4,byrow=F))+ + theme( + plot.title = element_text(size = 8), + legend.text = element_text(size = 8, colour ="black"), + legend.title = element_blank(), + legend.position = "bottom", + axis.title.x=element_blank(), + axis.text.x=element_blank(), + axis.ticks.x=element_blank(), + axis.title.y=element_blank(), + axis.text.y=element_blank(), + axis.ticks.y=element_blank() + ) + + #save the map + ggsave(filename="synthetic.prediction.png",plot = prediction.plot,path = paste0(output.path, "/HABITAT/", sim.version),scale = 1,dpi = 300,limitsize = F,width = 15,height = 15,units ="cm") + + #return the map + return(prediction.plot) + +} + diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R new file mode 100644 index 0000000..d9b720b --- /dev/null +++ b/R/UTILS.train_RF_habitat.R @@ -0,0 +1,221 @@ +### HEADER ##################################################################### +##' +##' @title Create a random forest algorithm trained on CBNA data, in order to +##' obtain the simulated habitat, derived from a \code{FATE} simulation. +##' +##' @name train.RF.habitat +##' +##' @author Matthieu .. & Maxime Delprat +##' +##' @description This script is designed to produce a random forest model +##' trained on observed PFG abundance, sites releves and a map of observed +##' habitat. +##' +##' @param releves.PFG a data frame with Braund-Blanquet abundance at each site +##' and each PFG and strata. +##' @param releves.sites a data frame with coordinates and a description of +##' the habitat associated with the dominant species of each site in the +##' studied map. +##' @param hab.obs a raster map of the observed habitat in the +##' extended studied area. +##' @param external.training.mask default \code{NULL}. (optional) Keep only +##' releves data in a specific area. +##' @param studied.habitat a vector that specifies habitats that we take +##' into account for the validation. +##' @param RF.param a list of 2 parameters for random forest model : +##' share.training defines the size of the trainig part of the data base. +##' ntree is the number of trees build by the algorithm, it allows to reduce +##' the prediction error. +##' @param output.path access path to the for the folder where output files +##' will be created. +##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +##' by strata in each site. If FALSE, PFG abundance is defined for all strata. +##' @param sim.version name of the simulation we want to validate. +##' +##' @details +##' +##' This function transform PFG Braund-Blanquet abundance in relative abundance, +##' get habitat information from the releves map, keep only relees on interesting +##' habitat and then builds de random forest model. Finally, the function analyzes +##' the model performance with computation of confusion matrix and TSS for +##' the traning and testing sample. +##' +##' @return +##' +##' 2 prepared CBNA releves files are created before the building of the random +##' forest model in a habitat validation folder. +##' 5 more files are created at the end of the script to save the RF model and +##' the performance analyzes (confusion matrix and TSS) for the training and +##' testing parts. +##' +### END OF HEADER ############################################################## + + +train.RF.habitat<-function(releves.PFG + , releves.sites + , hab.obs + , external.training.mask=NULL + , studied.habitat + , RF.param + , output.path + , perStrata + , sim.version) +{ + + #1. Compute relative abundance metric + ######################################### + + #identify sites with wrong BB values (ie values that cannot be converted by the PRE_FATE.abundBraunBlanquet function) + releves.PFG<-filter(releves.PFG,is.element(BB,c(NA, "NA", 0, "+", "r", 1:5))) + + #transformation into coverage percentage + releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage + + if(perStrata==T){ + aggregated.releves.PFG<-aggregate(coverage~site+PFG+strata,data=releves.PFG,FUN="sum") + }else if(perStrata==F){ + aggregated.releves.PFG<-aggregate(coverage~site+PFG,data=releves.PFG,FUN="sum") + aggregated.releves.PFG$strata<-"A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) + } + + #transformation into a relative metric (here relative.metric is relative coverage) + aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG %>% group_by(site,strata) %>% mutate(relative.metric= round(prop.table(coverage),digits = 2))) #rel is proportion of total pct_cov, not percentage + aggregated.releves.PFG$relative.metric[is.na(aggregated.releves.PFG$relative.metric)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) + aggregated.releves.PFG$coverage<-NULL + + print("releve data have been transformed into a relative metric") + + #2. Cast the df + ####################### + + #transfo into factor to be sure to create all the combination when doing "dcast" + aggregated.releves.PFG$PFG<-as.factor(aggregated.releves.PFG$PFG) + aggregated.releves.PFG$strata<-as.factor(aggregated.releves.PFG$strata) + + aggregated.releves.PFG<-dcast(setDT(aggregated.releves.PFG),site~PFG+strata,value.var=c("relative.metric"),fill=0,drop=F) + + #3. Get habitat information + ################################### + + #get sites coordinates + aggregated.releves.PFG<-merge(dplyr::select(releves.sites,c(site)),aggregated.releves.PFG,by="site") + + #get habitat code and name + if(compareCRS(aggregated.releves.PFG,hab.obs)){ + aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) + }else{ + aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(hab.obs)) + aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) + } + + #correspondance habitat code/habitat name + table.habitat.releve<-levels(hab.obs)[[1]] + + aggregated.releves.PFG<-merge(aggregated.releves.PFG,dplyr::select(table.habitat.releve,c(ID,habitat)),by.x="code.habitat",by.y="ID") + + #(optional) keep only releves data in a specific area + if(!is.null(external.training.mask)){ + + if(compareCRS(aggregated.releves.PFG,external.training.mask)==F){ #as this stage it is not a problem to transform crs(aggregated.releves.PFG) since we have no more merge to do (we have already extracted habitat info from the map) + aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(external.training.mask)) + } + + aggregated.releves.PFG<-st_crop(x=aggregated.releves.PFG,y=external.training.mask) + print("'releve' map has been cropped to match 'external.training.mask'.") + } + + + # 4. Keep only releve on interesting habitat + ###################################################" + + aggregated.releves.PFG<-filter(aggregated.releves.PFG,is.element(habitat,studied.habitat)) #filter non interesting habitat + NA + + print(cat("habitat classes used in the RF algo: ",unique(aggregated.releves.PFG$habitat),"\n",sep="\t")) + + # 5. Save data + ##################### + + st_write(aggregated.releves.PFG,paste0(output.path,"/HABITAT/", sim.version, "/releve.PFG.habitat.shp"),overwrite=T,append=F) + write.csv(aggregated.releves.PFG,paste0(output.path,"/HABITAT/", sim.version, "/CBNA.releves.prepared.csv"),row.names = F) + + # 6. Small adjustment in data structure + ########################################## + + aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG) #get rid of the spatial structure before entering the RF process + aggregated.releves.PFG$habitat<-as.factor(aggregated.releves.PFG$habitat) + + # 7.Random forest + ###################################### + + #separate the database into a training and a test part + set.seed(123) + + training.site<-sample(aggregated.releves.PFG$site,size=RF.param$share.training*length(aggregated.releves.PFG$site),replace = F) + releves.training<-filter(aggregated.releves.PFG,is.element(site,training.site)) + releves.testing<-filter(aggregated.releves.PFG,!is.element(site,training.site)) + + #train the model (with correction for imbalances in sampling) + + #run optimization algo (careful : optimization over OOB...) + mtry.perf<-as.data.frame( + tuneRF( + x=select(releves.training,-c(code.habitat,site,habitat,geometry)), + y=releves.training$habitat, + strata=releves.training$habitat, + sampsize=min(table(releves.training$habitat)), + ntreeTry=RF.param$ntree, + stepFactor=2, improve=0.05,doBest=FALSE,plot=F,trace=F + ) + ) + + #select mtry + mtry<-mtry.perf$mtry[mtry.perf$OOBError==min(mtry.perf$OOBError)][1] #the lowest n achieving minimum OOB + + #run real model + model<- randomForest( + x=select(releves.training,-c(code.habitat,site,habitat,geometry)), + y=releves.training$habitat, + xtest=select(releves.testing,-c(code.habitat,site,habitat,geometry)), + ytest=releves.testing$habitat, + strata=releves.training$habitat, + min(table(releves.training$habitat)), + ntree=RF.param$ntree, + mtry=mtry, + norm.votes=TRUE, + keep.forest=TRUE + ) + + #analyse model performance + + # Analysis on the training sample + + confusion.training<-confusionMatrix(data=model$predicted,reference=releves.training$habitat) + + synthesis.training<-data.frame(habitat=colnames(confusion.training$table),sensitivity=confusion.training$byClass[,1],specificity=confusion.training$byClass[,2],weight=colSums(confusion.training$table)/sum(colSums(confusion.training$table))) #warning: prevalence is the weight of predicted habitat, not of observed habitat + synthesis.training<-synthesis.training%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) + + aggregate.TSS.training<-round(sum(synthesis.training$weight*synthesis.training$TSS),digits=2) + + # Analysis on the testing sample + + confusion.testing<-confusionMatrix(data=model$test$predicted,reference=releves.testing$habitat) + + synthesis.testing<-data.frame(habitat=colnames(confusion.testing$table),sensitivity=confusion.testing$byClass[,1],specificity=confusion.testing$byClass[,2],weight=colSums(confusion.testing$table)/sum(colSums(confusion.testing$table)))#warning: prevalence is the weight of predicted habitat, not of observed habitat + synthesis.testing<-synthesis.testing%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) + + aggregate.TSS.testing<-round(sum(synthesis.testing$weight*synthesis.testing$TSS),digits=2) + + + # 8. Save and return output + #######################################" + + write_rds(model,paste0(output.path,"/HABITAT/", sim.version, "/RF.model.rds"),compress="none") + write.csv(synthesis.training,paste0(output.path,"/HABITAT/", sim.version, "/RF_perf.per.hab_training.csv"),row.names=F) + write.csv(aggregate.TSS.training,paste0(output.path,"/HABITAT/", sim.version, "/RF_aggregate.TSS_training.csv"),row.names=F) + write.csv(synthesis.testing,paste0(output.path,"/HABITAT/", sim.version, "/RF_perf.per.hab_testing.csv"),row.names=F) + write.csv(aggregate.TSS.testing,paste0(output.path,"/HABITAT/", sim.version, "/RF_aggregate.TSS_testing.csv"),row.names=F) + + return(model) + +} + From 580822b400bf55333c41da0deec3232361ac867d Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:36:51 +0100 Subject: [PATCH 007/176] Update UTILS.plot_predicted_habitat.R --- R/UTILS.plot_predicted_habitat.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 4f40061..09a2936 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -80,7 +80,8 @@ plot.predicted.habitat<-function(predicted.habitat #merge the prediction df with the df containing color and habitat code predicted.habitat<-merge(predicted.habitat,habitat.code.df,by.x=c("modal.predicted.habitat","prediction.code"),by.y=c("habitat","prediction.code")) - + write.csv(x = predicted.habitat, file = paste0(output.path, "/HABITAT/", sim.version, "/hab.pred.csv")) + #plot From d54d0d87835e6371ccc5396b52dc1ef10d893d17 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:22:31 +0100 Subject: [PATCH 008/176] Update POST_FATE.validation_habitat.R --- R/POST_FATE.validation_habitat.R | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/R/POST_FATE.validation_habitat.R b/R/POST_FATE.validation_habitat.R index 59ba9a2..9334f27 100644 --- a/R/POST_FATE.validation_habitat.R +++ b/R/POST_FATE.validation_habitat.R @@ -5,7 +5,7 @@ ##' ##' @name POST_FATE.validation.habitat ##' -##' @author Matthieu .. & Maxime Delprat +##' @author Matthieu Combaud, Maxime Delprat ##' ##' @description This script compare habitat simulations and observations and ##' create a map to visualize this comparison with all the the \code{FATE} and @@ -163,6 +163,15 @@ POST_FATE.validation_habitat = function(name.simulation , simulation.map = simulation.map , output.path = output.path , sim.version = sim.version) + + ## COMPARISON FAILURE/SUCCESS + + hab.pred = read.csv(paste0(output.path, "/HABITAT/", sim.version, "/hab.pred.csv")) + failure = as.numeric((table(hab.pred$prediction.code)[1]/sum(table(hab.pred$prediction.code)))*100) + success = as.numeric((table(hab.pred$prediction.code)[2]/sum(table(hab.pred$prediction.code)))*100) + cat("\n ---------- END OF THE SIMULATION \n") + cat(paste0("\n ---------- ", round(failure, digits = 2), "% of habitats are not correctly predicted by ", sim.version, " \n")) + cat(paste0("\n ---------- ", round(success, digits = 2), "% of habitats are correctly predicted by ", sim.version, " \n")) return(prediction.map) } From 71a2c933650180af3ee8e675854ed296da618139 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:23:38 +0100 Subject: [PATCH 009/176] Update UTILS.do_habitat_validation.R --- R/UTILS.do_habitat_validation.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 4624baa..d77882d 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -5,7 +5,7 @@ ##' ##' @name do.habitat.validation ##' -##' @author Matthieu .. & Maxime Delprat +##' @author Matthieu Combaud, Maxime Delprat ##' ##' @description To compare observations and simulations, this function compute ##' confusion matrix between observation and prediction and then compute the TSS From 7068728c29c7ed5d16d60f3180ef7763a067c6d5 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:24:26 +0100 Subject: [PATCH 010/176] Update UTILS.plot_predicted_habitat.R --- R/UTILS.plot_predicted_habitat.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 09a2936..24666b6 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -5,7 +5,7 @@ ##' ##' @name plot.predicted.habitat ##' -##' @author Matthieu .. & Maxime Delprat +##' @author Matthieu Combaud, Maxime Delprat ##' ##' @description This script is designed to create a raster map of habitat prediction ##' based on a habitat prediction file. For each pixel, the habitat failure or success value From 02b95af62ba131032d03e12d066e8bead77d30a8 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 14:25:06 +0100 Subject: [PATCH 011/176] Update UTILS.train_RF_habitat.R --- R/UTILS.train_RF_habitat.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index d9b720b..a277f05 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -5,7 +5,7 @@ ##' ##' @name train.RF.habitat ##' -##' @author Matthieu .. & Maxime Delprat +##' @author Matthieu Combaud, Maxime Delprat ##' ##' @description This script is designed to produce a random forest model ##' trained on observed PFG abundance, sites releves and a map of observed From 12d50321ec90bd75abbe600846b05583b37b9963 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 15:52:37 +0100 Subject: [PATCH 012/176] Update POST_FATE.validation_habitat.R With this update the function take only the extended map of the studied area as argument. --- R/POST_FATE.validation_habitat.R | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/R/POST_FATE.validation_habitat.R b/R/POST_FATE.validation_habitat.R index 9334f27..68b87bb 100644 --- a/R/POST_FATE.validation_habitat.R +++ b/R/POST_FATE.validation_habitat.R @@ -55,7 +55,6 @@ POST_FATE.validation_habitat = function(name.simulation , releves.PFG , releves.sites , hab.obs - , habitat.FATE.map , validation.mask) { @@ -114,9 +113,8 @@ POST_FATE.validation_habitat = function(name.simulation releves.sites<-st_read(paste0(obs.path, releves.sites)) hab.obs<-raster(paste0(obs.path, hab.obs)) # Habitat mask at FATE simu resolution - # hab.obs.modif<-projectRaster(from = hab.obs, to = simulation.map, res = res(hab.obs)[1], crs = crs(projection(simulation.mask))) - # habitat.FATE.map<-crop(hab.obs.modif, simulation.map) - habitat.FATE.map<-raster(paste0(obs.path, habitat.FATE.map)) + hab.obs.modif<-projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") + habitat.FATE.map<-crop(hab.obs.modif, simulation.map) validation.mask<-raster(paste0(obs.path, validation.mask)) # Provide a color df From ec413be2a84f6a8e350006975338da927d13533b Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 15:57:13 +0100 Subject: [PATCH 013/176] Update UTILS.do_habitat_validation.R With this update the function uses the new restricted map of the area and extracts data from it. --- R/UTILS.do_habitat_validation.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index d77882d..e4355b3 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -123,7 +123,8 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #habitat df for the whole simulation area habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) habitat.whole.area.df<-habitat.whole.area.df[in.region.pixels,] - habitat.whole.area.df<-merge(habitat.whole.area.df,dplyr::select(levels(habitat.FATE.map)[[1]],c(ID,habitat)),by.x="code.habitat",by.y="ID") + habitat.whole.area.df<-subset(habitat.whole.area.df, for.validation!="NA") + habitat.whole.area.df<-merge(habitat.whole.area.df,dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)),by.x="code.habitat",by.y="ID") habitat.whole.area.df<-filter(habitat.whole.area.df,is.element(habitat,RF.model$classes)) print(cat("Habitat considered in the prediction exercise: ",c(unique(habitat.whole.area.df$habitat)),"\n",sep="\t")) From a61ec30958ebaccc4eaa50c2cb3c72a46cad3ea7 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:14:20 +0100 Subject: [PATCH 014/176] Update UTILS.do_habitat_validation.R --- R/UTILS.do_habitat_validation.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index e4355b3..83d2880 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -45,7 +45,7 @@ ### END OF HEADER ############################################################## -do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata) { +do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs) { #notes # we prepare the relevé data in this function, but in fact we could provide them directly if we adjust the code From 409fe3c10a61246b89da73ce34540a1d26d06707 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:16:30 +0100 Subject: [PATCH 015/176] Update POST_FATE.validation_habitat.R --- R/POST_FATE.validation_habitat.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/POST_FATE.validation_habitat.R b/R/POST_FATE.validation_habitat.R index 68b87bb..4e99d75 100644 --- a/R/POST_FATE.validation_habitat.R +++ b/R/POST_FATE.validation_habitat.R @@ -152,7 +152,8 @@ POST_FATE.validation_habitat = function(name.simulation , predict.all.map = predict.all.map , sim.version = sim.version , name.simulation = name.simulation - , perStrata = F) + , perStrata = F + , hab.obs = hab.obs) ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT From 625e9d498eff28fa64be575021539b71fa9ac111 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:18:08 +0100 Subject: [PATCH 016/176] Update UTILS.do_habitat_validation.R Modification of the header with a new param --- R/UTILS.do_habitat_validation.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 83d2880..c0dae0e 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -26,6 +26,8 @@ ##' @param name.simulation simulation folder name. ##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined ##' by strata in each pixel. If FALSE, PFG abundance is defined for all strata. +##' @param hab.obs a raster map of the observed habitat in the +##' extended studied area. ##' ##' @details ##' From 5c9149e05c954262dcce7e6273bcf3df26f2bd96 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:35:45 +0100 Subject: [PATCH 017/176] Update UTILS.plot_predicted_habitat.R Correction of a small mistake in the header --- R/UTILS.plot_predicted_habitat.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 24666b6..80e9be2 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -11,7 +11,7 @@ ##' based on a habitat prediction file. For each pixel, the habitat failure or success value ##' is associated to a color and then, the map is built. ##' -##' @param predicted habitat a csv file created by the do.habitat.validation function +##' @param predicted.habitat a csv file created by the do.habitat.validation function ##' which contain, for each pixel of the studied map, the simulated and observed habitat. ##' @param col.df a data frame with all the colors associated with the failure or ##' success of each studied habitat prediction. From 1b6896cfa68366954f2a7954d965dd1c12239959 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:36:25 +0100 Subject: [PATCH 018/176] Update UTILS.train_RF_habitat.R Modification of the title --- R/UTILS.train_RF_habitat.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index a277f05..334e079 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -1,7 +1,6 @@ ### HEADER ##################################################################### ##' -##' @title Create a random forest algorithm trained on CBNA data, in order to -##' obtain the simulated habitat, derived from a \code{FATE} simulation. +##' @title Create a random forest algorithm trained on CBNA data. ##' ##' @name train.RF.habitat ##' From 5df7fc7cc2a6014e58ed20e9065dfc04b3233c29 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:48:49 +0100 Subject: [PATCH 019/176] Add files via upload Addition of the new markdown files of the new habitat validation functions --- man/POST_FATE.validation.habitat.Rd | 67 +++++++++++++++++++++++++++ man/do.habitat.validation.Rd | 69 ++++++++++++++++++++++++++++ man/plot.predicted.habitat.Rd | 41 +++++++++++++++++ man/train.RF.habitat.Rd | 70 +++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 man/POST_FATE.validation.habitat.Rd create mode 100644 man/do.habitat.validation.Rd create mode 100644 man/plot.predicted.habitat.Rd create mode 100644 man/train.RF.habitat.Rd diff --git a/man/POST_FATE.validation.habitat.Rd b/man/POST_FATE.validation.habitat.Rd new file mode 100644 index 0000000..572efbf --- /dev/null +++ b/man/POST_FATE.validation.habitat.Rd @@ -0,0 +1,67 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/POST_FATE.validation_habitat.R +\name{POST_FATE.validation.habitat} +\alias{POST_FATE.validation.habitat} +\alias{POST_FATE.validation_habitat} +\title{Compute habitat performance and create a prediction plot of habitat +for a whole map of a \code{FATE} simulation.} +\usage{ +POST_FATE.validation_habitat( + name.simulation, + sim.version, + obs.path, + releves.PFG, + releves.sites, + hab.obs, + validation.mask +) +} +\arguments{ +\item{name.simulation}{simulation folder name.} + +\item{sim.version}{name of the simulation we want to validate (it works with +only one sim.version).} + +\item{obs.path}{the function needs observed data, please create a folder for them in your +simulation folder and then indicate in this parmeter the access path to this folder.} + +\item{releves.PFG}{name of file which contain the observed Braund-Blanquet abundance at each site +and each PFG and strata (with extension).} + +\item{hab.obs}{name of the file which contain the extended studied map in the simulation (with extension).} + +\item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation.} + +\item{releves.site}{name of the file which contain coordinates and a description of +the habitat associated with the dominant species of each site in the studied map (with extension).} + +\item{habitat.FATE.map}{name of the file which contain the restricted studied map in the simulation (with extension).} +} +\value{ +Two folders are created in name.simulation folder : +\describe{ + \item{\file{VALIDATION/HABITAT/sim.version}}{containing the prepared CBNA data, + RF model, the performance analyzes (confusion matrix and TSS) for the training and +testing parts of the RF model, the habitat performance file, the habitat prediction file with +observed and simulated habitat for each pixel of the whole map and the final prediction plot.} + \item{\file{DATA_OBS}}{maps of observed habitat and csv files of PFG and sites releves.} +} +} +\description{ +This script compare habitat simulations and observations and +create a map to visualize this comparison with all the the \code{FATE} and +observed data. +} +\details{ +The observed habitat is derived from the cesbio map, the simulated habitat +is derived from FATE simulated relative abundance, based on a random forest +algorithm trained on CBNA data. To compare observations and simulations, the function +compute confusion matrix between observation and prediction and then compute the TSS +for each habitat h (number of prediction of habitat h/number of observation +of habitat h + number of non-prediction of habitat h/number of non-observation +of habitat h). The final metrics this script use is the mean of TSS per habitat over all +habitats, weighted by the share of each habitat in the observed habitat distribution. +} +\author{ +Matthieu Combaud, Maxime Delprat +} diff --git a/man/do.habitat.validation.Rd b/man/do.habitat.validation.Rd new file mode 100644 index 0000000..5163b04 --- /dev/null +++ b/man/do.habitat.validation.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/UTILS.do_habitat_validation.R +\name{do.habitat.validation} +\alias{do.habitat.validation} +\title{Compare observed and simulated habitat of a \code{FATE} simulation +at the last simulation year.} +\usage{ +do.habitat.validation( + output.path, + RF.model, + habitat.FATE.map, + validation.mask, + simulation.map, + predict.all.map, + sim.version, + name.simulation, + perStrata, + hab.obs +) +} +\arguments{ +\item{output.path}{access path to the for the folder where output files +will be created.} + +\item{RF.model}{random forest model trained on CBNA data (train.RF.habitat +function)} + +\item{habitat.FATE.map}{a raster map of the observed habitat in the +studied area.} + +\item{validation.mask}{a raster mask that specified which pixels need validation.} + +\item{simulation.map}{a raster map of the whole studied area use to check +the consistency between simulation map and the observed habitat map.} + +\item{predict.all.map}{a TRUE/FALSE vector. If TRUE, the script will predict +habitat for the whole map.} + +\item{sim.version}{name of the simulation we want to validate.} + +\item{name.simulation}{simulation folder name.} + +\item{perStrata}{a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +by strata in each pixel. If FALSE, PFG abundance is defined for all strata.} + +\item{hab.obs}{a raster map of the observed habitat in the +extended studied area.} +} +\value{ +Habitat performance file +If option selected, the function returns an habitat prediction file with +observed and simulated habitat for each pixel of the whole map. +} +\description{ +To compare observations and simulations, this function compute +confusion matrix between observation and prediction and then compute the TSS +for each habitat. +} +\details{ +After several preliminary checks, the function is going to prepare the observations +database by extracting the observed habitat from a raster map. Then, for each +simulations (sim.version), the script take the evolution abundance for each PFG +and all strata file and predict the habitat for the whole map (if option selected) +thanks to the RF model.Finally, the function compute habitat performance based on +TSS for each habitat. +} +\author{ +Matthieu Combaud & Maxime Delprat +} diff --git a/man/plot.predicted.habitat.Rd b/man/plot.predicted.habitat.Rd new file mode 100644 index 0000000..381bac5 --- /dev/null +++ b/man/plot.predicted.habitat.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/UTILS.plot_predicted_habitat.R +\name{plot.predicted.habitat} +\alias{plot.predicted.habitat} +\title{Create a raster map of habitat prediction for a specific \code{FATE} +simulation at the last simulation year.} +\usage{ +\method{plot}{predicted.habitat}(predicted.habitat, col.df, simulation.map, output.path, sim.version) +} +\arguments{ +\item{predicted.habitat}{a csv file created by the do.habitat.validation function +which contain, for each pixel of the studied map, the simulated and observed habitat.} + +\item{col.df}{a data frame with all the colors associated with the failure or +success of each studied habitat prediction.} + +\item{simulation.map}{a raster map of the whole studied area.} + +\item{output.path}{access path to the for the folder where output files +will be created.} + +\item{sim.version}{name of the simulation we want to validate.} +} +\value{ +a synthetic.prediction.png file which contain the final prediction plot. +} +\description{ +This script is designed to create a raster map of habitat prediction +based on a habitat prediction file. For each pixel, the habitat failure or success value +is associated to a color and then, the map is built. +} +\details{ +The function determine true/false prediction ('failure' if false, 'success' if true) +and prepare a dataframe containing color and habitat code. Then, the script merge +the prediction dataframe with the color and code habitat dataframe. Finally, +the function draw a raster map and a plot of prediction habitat over it thanks +to the data prepared before. +} +\author{ +Matthieu Combaud, Maxime Delprat +} diff --git a/man/train.RF.habitat.Rd b/man/train.RF.habitat.Rd new file mode 100644 index 0000000..272141c --- /dev/null +++ b/man/train.RF.habitat.Rd @@ -0,0 +1,70 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/UTILS.train_RF_habitat.R +\name{train.RF.habitat} +\alias{train.RF.habitat} +\title{Create a random forest algorithm trained on CBNA data.} +\usage{ +train.RF.habitat( + releves.PFG, + releves.sites, + hab.obs, + external.training.mask = NULL, + studied.habitat, + RF.param, + output.path, + perStrata, + sim.version +) +} +\arguments{ +\item{releves.PFG}{a data frame with Braund-Blanquet abundance at each site +and each PFG and strata.} + +\item{releves.sites}{a data frame with coordinates and a description of +the habitat associated with the dominant species of each site in the +studied map.} + +\item{hab.obs}{a raster map of the observed habitat in the +extended studied area.} + +\item{external.training.mask}{default \code{NULL}. (optional) Keep only +releves data in a specific area.} + +\item{studied.habitat}{a vector that specifies habitats that we take +into account for the validation.} + +\item{RF.param}{a list of 2 parameters for random forest model : +share.training defines the size of the trainig part of the data base. +ntree is the number of trees build by the algorithm, it allows to reduce +the prediction error.} + +\item{output.path}{access path to the for the folder where output files +will be created.} + +\item{perStrata}{a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +by strata in each site. If FALSE, PFG abundance is defined for all strata.} + +\item{sim.version}{name of the simulation we want to validate.} +} +\value{ +2 prepared CBNA releves files are created before the building of the random +forest model in a habitat validation folder. +5 more files are created at the end of the script to save the RF model and +the performance analyzes (confusion matrix and TSS) for the training and +testing parts. +} +\description{ +This script is designed to produce a random forest model +trained on observed PFG abundance, sites releves and a map of observed +habitat. +} +\details{ +This function transform PFG Braund-Blanquet abundance in relative abundance, +get habitat information from the releves map, keep only relees on interesting +habitat and then builds de random forest model. Finally, the function analyzes +the model performance with computation of confusion matrix and TSS for +the traning and testing sample. +} +\author{ +Matthieu Combaud, Maxime Delprat +} From 05d90b1bad1f25a05c71f14c05ffc9828fc7a654 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:54:29 +0100 Subject: [PATCH 020/176] Add files via upload Addition of the documentation (.html files) of the new habitat validation functions --- .../POST_FATE.validation.habitat.html | 235 +++++++++++++++++ docs/reference/do.habitat.validation.html | 236 ++++++++++++++++++ docs/reference/plot.predicted.habitat.html | 209 ++++++++++++++++ docs/reference/train.RF.habitat.html | 235 +++++++++++++++++ 4 files changed, 915 insertions(+) create mode 100644 docs/reference/POST_FATE.validation.habitat.html create mode 100644 docs/reference/do.habitat.validation.html create mode 100644 docs/reference/plot.predicted.habitat.html create mode 100644 docs/reference/train.RF.habitat.html diff --git a/docs/reference/POST_FATE.validation.habitat.html b/docs/reference/POST_FATE.validation.habitat.html new file mode 100644 index 0000000..7be1b99 --- /dev/null +++ b/docs/reference/POST_FATE.validation.habitat.html @@ -0,0 +1,235 @@ + +Compute habitat performance and create a prediction plot of habitat +for a whole map of a FATE simulation. — POST_FATE.validation.habitat • RFate + + +
+
+ + + +
+
+ + +
+

This script compare habitat simulations and observations and +create a map to visualize this comparison with all the the FATE and +observed data.

+
+ +
+
POST_FATE.validation_habitat(
+  name.simulation,
+  sim.version,
+  obs.path,
+  releves.PFG,
+  releves.sites,
+  hab.obs,
+  validation.mask
+)
+
+ +
+

Arguments

+
name.simulation
+

simulation folder name.

+
sim.version
+

name of the simulation we want to validate (it works with +only one sim.version).

+
obs.path
+

the function needs observed data, please create a folder for them in your +simulation folder and then indicate in this parmeter the access path to this folder.

+
releves.PFG
+

name of file which contain the observed Braund-Blanquet abundance at each site +and each PFG and strata (with extension).

+
hab.obs
+

name of the file which contain the extended studied map in the simulation (with extension).

+
validation.mask
+

name of the file which contain a raster mask that specified which pixels need validation.

+
releves.site
+

name of the file which contain coordinates and a description of +the habitat associated with the dominant species of each site in the studied map (with extension).

+
habitat.FATE.map
+

name of the file which contain the restricted studied map in the simulation (with extension).

+
+
+

Value

+

Two folders are created in name.simulation folder :

VALIDATION/HABITAT/sim.version
+

containing the prepared CBNA data, + RF model, the performance analyzes (confusion matrix and TSS) for the training and +testing parts of the RF model, the habitat performance file, the habitat prediction file with +observed and simulated habitat for each pixel of the whole map and the final prediction plot.

+ +
DATA_OBS
+

maps of observed habitat and csv files of PFG and sites releves.

+ + +
+
+

Details

+

The observed habitat is derived from the cesbio map, the simulated habitat +is derived from FATE simulated relative abundance, based on a random forest +algorithm trained on CBNA data. To compare observations and simulations, the function +compute confusion matrix between observation and prediction and then compute the TSS +for each habitat h (number of prediction of habitat h/number of observation +of habitat h + number of non-prediction of habitat h/number of non-observation +of habitat h). The final metrics this script use is the mean of TSS per habitat over all +habitats, weighted by the share of each habitat in the observed habitat distribution.

+
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/docs/reference/do.habitat.validation.html b/docs/reference/do.habitat.validation.html new file mode 100644 index 0000000..9dd1092 --- /dev/null +++ b/docs/reference/do.habitat.validation.html @@ -0,0 +1,236 @@ + +Compare observed and simulated habitat of a FATE simulation +at the last simulation year. — do.habitat.validation • RFate + + +
+
+ + + +
+
+ + +
+

To compare observations and simulations, this function compute +confusion matrix between observation and prediction and then compute the TSS +for each habitat.

+
+ +
+
do.habitat.validation(
+  output.path,
+  RF.model,
+  habitat.FATE.map,
+  validation.mask,
+  simulation.map,
+  predict.all.map,
+  sim.version,
+  name.simulation,
+  perStrata,
+  hab.obs
+)
+
+ +
+

Arguments

+
output.path
+

access path to the for the folder where output files +will be created.

+
RF.model
+

random forest model trained on CBNA data (train.RF.habitat +function)

+
habitat.FATE.map
+

a raster map of the observed habitat in the +studied area.

+
validation.mask
+

a raster mask that specified which pixels need validation.

+
simulation.map
+

a raster map of the whole studied area use to check +the consistency between simulation map and the observed habitat map.

+
predict.all.map
+

a TRUE/FALSE vector. If TRUE, the script will predict +habitat for the whole map.

+
sim.version
+

name of the simulation we want to validate.

+
name.simulation
+

simulation folder name.

+
perStrata
+

a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +by strata in each pixel. If FALSE, PFG abundance is defined for all strata.

+
hab.obs
+

a raster map of the observed habitat in the +extended studied area.

+
+
+

Value

+

Habitat performance file +If option selected, the function returns an habitat prediction file with +observed and simulated habitat for each pixel of the whole map.

+
+
+

Details

+

After several preliminary checks, the function is going to prepare the observations +database by extracting the observed habitat from a raster map. Then, for each +simulations (sim.version), the script take the evolution abundance for each PFG +and all strata file and predict the habitat for the whole map (if option selected) +thanks to the RF model.Finally, the function compute habitat performance based on +TSS for each habitat.

+
+
+

Author

+

Matthieu Combaud & Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/docs/reference/plot.predicted.habitat.html b/docs/reference/plot.predicted.habitat.html new file mode 100644 index 0000000..678075a --- /dev/null +++ b/docs/reference/plot.predicted.habitat.html @@ -0,0 +1,209 @@ + +Create a raster map of habitat prediction for a specific FATE +simulation at the last simulation year. — plot.predicted.habitat • RFate + + +
+
+ + + +
+
+ + +
+

This script is designed to create a raster map of habitat prediction +based on a habitat prediction file. For each pixel, the habitat failure or success value +is associated to a color and then, the map is built.

+
+ +
+
# S3 method for predicted.habitat
+plot(predicted.habitat, col.df, simulation.map, output.path, sim.version)
+
+ +
+

Arguments

+
predicted.habitat
+

a csv file created by the do.habitat.validation function +which contain, for each pixel of the studied map, the simulated and observed habitat.

+
col.df
+

a data frame with all the colors associated with the failure or +success of each studied habitat prediction.

+
simulation.map
+

a raster map of the whole studied area.

+
output.path
+

access path to the for the folder where output files +will be created.

+
sim.version
+

name of the simulation we want to validate.

+
+
+

Value

+

a synthetic.prediction.png file which contain the final prediction plot.

+
+
+

Details

+

The function determine true/false prediction ('failure' if false, 'success' if true) +and prepare a dataframe containing color and habitat code. Then, the script merge +the prediction dataframe with the color and code habitat dataframe. Finally, +the function draw a raster map and a plot of prediction habitat over it thanks +to the data prepared before.

+
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/docs/reference/train.RF.habitat.html b/docs/reference/train.RF.habitat.html new file mode 100644 index 0000000..9c99be9 --- /dev/null +++ b/docs/reference/train.RF.habitat.html @@ -0,0 +1,235 @@ + +Create a random forest algorithm trained on CBNA data. — train.RF.habitat • RFate + + +
+
+ + + +
+
+ + +
+

This script is designed to produce a random forest model +trained on observed PFG abundance, sites releves and a map of observed +habitat.

+
+ +
+
train.RF.habitat(
+  releves.PFG,
+  releves.sites,
+  hab.obs,
+  external.training.mask = NULL,
+  studied.habitat,
+  RF.param,
+  output.path,
+  perStrata,
+  sim.version
+)
+
+ +
+

Arguments

+
releves.PFG
+

a data frame with Braund-Blanquet abundance at each site +and each PFG and strata.

+
releves.sites
+

a data frame with coordinates and a description of +the habitat associated with the dominant species of each site in the +studied map.

+
hab.obs
+

a raster map of the observed habitat in the +extended studied area.

+
external.training.mask
+

default NULL. (optional) Keep only +releves data in a specific area.

+
studied.habitat
+

a vector that specifies habitats that we take +into account for the validation.

+
RF.param
+

a list of 2 parameters for random forest model : +share.training defines the size of the trainig part of the data base. +ntree is the number of trees build by the algorithm, it allows to reduce +the prediction error.

+
output.path
+

access path to the for the folder where output files +will be created.

+
perStrata
+

a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +by strata in each site. If FALSE, PFG abundance is defined for all strata.

+
sim.version
+

name of the simulation we want to validate.

+
+
+

Value

+

2 prepared CBNA releves files are created before the building of the random +forest model in a habitat validation folder. +5 more files are created at the end of the script to save the RF model and +the performance analyzes (confusion matrix and TSS) for the training and +testing parts.

+
+
+

Details

+

This function transform PFG Braund-Blanquet abundance in relative abundance, +get habitat information from the releves map, keep only relees on interesting +habitat and then builds de random forest model. Finally, the function analyzes +the model performance with computation of confusion matrix and TSS for +the traning and testing sample.

+
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + From d3d4a8b44ce21cea4d2a41e8c2ccd125f54d77b1 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 22 Feb 2022 16:58:58 +0100 Subject: [PATCH 021/176] Update _pkgdown.yml Addition of the new habitat validation functions in the index of the website --- _pkgdown.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index b104486..c9d2eda 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -129,6 +129,7 @@ reference: - "POST_FATE.binaryMaps" - "POST_FATE.graphic_mapPFGvsHS" - "POST_FATE.graphic_mapPFG" + - "POST_FATE.validation_habitat" - title: Save FATE simulation contents: - "SAVE_FATE.step1_PFG" @@ -144,3 +145,6 @@ reference: - ".scaleMaps" - ".getCutoff" - ".unzip_ALL" + - "do_habitat_validation" + - "plot_predicted_habitat" + - "train_RF_habitat" From 910c3458a6e8fbe8572d0c0b5fca5801309e568e Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:16:31 +0100 Subject: [PATCH 022/176] Add files via upload Correction of mistakes in header --- docs/reference/POST_FATE.validation.habitat.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/reference/POST_FATE.validation.habitat.html b/docs/reference/POST_FATE.validation.habitat.html index 7be1b99..5b56626 100644 --- a/docs/reference/POST_FATE.validation.habitat.html +++ b/docs/reference/POST_FATE.validation.habitat.html @@ -166,19 +166,17 @@

Arguments

only one sim.version).

obs.path

the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parmeter the access path to this folder.

+simulation folder and then indicate in this parameter the access path to this new folder.

releves.PFG

name of file which contain the observed Braund-Blanquet abundance at each site and each PFG and strata (with extension).

hab.obs

name of the file which contain the extended studied map in the simulation (with extension).

validation.mask
-

name of the file which contain a raster mask that specified which pixels need validation.

+

name of the file which contain a raster mask that specified which pixels need validation (with extension).

releves.site

name of the file which contain coordinates and a description of the habitat associated with the dominant species of each site in the studied map (with extension).

-
habitat.FATE.map
-

name of the file which contain the restricted studied map in the simulation (with extension).

Value

From 6cd3ef7f18c6666dbbb86f3b89ccca36be6f79b5 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:17:55 +0100 Subject: [PATCH 023/176] Update POST_FATE.validation.habitat.html --- docs/reference/POST_FATE.validation.habitat.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/POST_FATE.validation.habitat.html b/docs/reference/POST_FATE.validation.habitat.html index 5b56626..3cbbcab 100644 --- a/docs/reference/POST_FATE.validation.habitat.html +++ b/docs/reference/POST_FATE.validation.habitat.html @@ -174,7 +174,7 @@

Arguments

name of the file which contain the extended studied map in the simulation (with extension).

validation.mask

name of the file which contain a raster mask that specified which pixels need validation (with extension).

-
releves.site
+
releves.sites

name of the file which contain coordinates and a description of the habitat associated with the dominant species of each site in the studied map (with extension).

From 4375b5934a66c659feaffd4238c705381ece7d87 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:19:01 +0100 Subject: [PATCH 024/176] Add files via upload Correction of mistakes --- man/POST_FATE.validation.habitat.Rd | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/man/POST_FATE.validation.habitat.Rd b/man/POST_FATE.validation.habitat.Rd index 572efbf..995d80d 100644 --- a/man/POST_FATE.validation.habitat.Rd +++ b/man/POST_FATE.validation.habitat.Rd @@ -23,19 +23,17 @@ POST_FATE.validation_habitat( only one sim.version).} \item{obs.path}{the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parmeter the access path to this folder.} +simulation folder and then indicate in this parameter the access path to this new folder.} \item{releves.PFG}{name of file which contain the observed Braund-Blanquet abundance at each site and each PFG and strata (with extension).} \item{hab.obs}{name of the file which contain the extended studied map in the simulation (with extension).} -\item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation.} +\item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation (with extension).} \item{releves.site}{name of the file which contain coordinates and a description of the habitat associated with the dominant species of each site in the studied map (with extension).} - -\item{habitat.FATE.map}{name of the file which contain the restricted studied map in the simulation (with extension).} } \value{ Two folders are created in name.simulation folder : From f5607bf8242163425dc5af568c1e8876bc75c6d0 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:20:02 +0100 Subject: [PATCH 025/176] Update POST_FATE.validation.habitat.Rd --- man/POST_FATE.validation.habitat.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/POST_FATE.validation.habitat.Rd b/man/POST_FATE.validation.habitat.Rd index 995d80d..6c82615 100644 --- a/man/POST_FATE.validation.habitat.Rd +++ b/man/POST_FATE.validation.habitat.Rd @@ -32,7 +32,7 @@ and each PFG and strata (with extension).} \item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation (with extension).} -\item{releves.site}{name of the file which contain coordinates and a description of +\item{releves.sites}{name of the file which contain coordinates and a description of the habitat associated with the dominant species of each site in the studied map (with extension).} } \value{ From 4d75823065ab2858b095b1630e16a1c941c3d2aa Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 10:14:22 +0100 Subject: [PATCH 026/176] Add files via upload Update of the function : addition of an habitat considered by the random forest model to generalized the function to any kind of environment --- R/POST_FATE.validation_habitat.R | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/R/POST_FATE.validation_habitat.R b/R/POST_FATE.validation_habitat.R index 4e99d75..b0524e1 100644 --- a/R/POST_FATE.validation_habitat.R +++ b/R/POST_FATE.validation_habitat.R @@ -15,14 +15,13 @@ ##' @param sim.version name of the simulation we want to validate (it works with ##' only one sim.version). ##' @param obs.path the function needs observed data, please create a folder for them in your -##' simulation folder and then indicate in this parmeter the access path to this folder. +##' simulation folder and then indicate in this parameter the access path to this new folder. ##' @param releves.PFG name of file which contain the observed Braund-Blanquet abundance at each site ##' and each PFG and strata (with extension). ##' @param releves.site name of the file which contain coordinates and a description of ##' the habitat associated with the dominant species of each site in the studied map (with extension). ##' @param hab.obs name of the file which contain the extended studied map in the simulation (with extension). -##' @param habitat.FATE.map name of the file which contain the restricted studied map in the simulation (with extension). -##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation. +##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation (with extension). ##' ##' @details ##' @@ -113,18 +112,18 @@ POST_FATE.validation_habitat = function(name.simulation releves.sites<-st_read(paste0(obs.path, releves.sites)) hab.obs<-raster(paste0(obs.path, hab.obs)) # Habitat mask at FATE simu resolution - hab.obs.modif<-projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") - habitat.FATE.map<-crop(hab.obs.modif, simulation.map) + hab.obs.modif <- projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") + habitat.FATE.map <- crop(hab.obs.modif, simulation.map) validation.mask<-raster(paste0(obs.path, validation.mask)) # Provide a color df col.df<-data.frame( - habitat=c("agricultural.grassland","coniferous.forest","deciduous.forest","natural.grassland","woody.heatland"), - failure=c("yellow","blueviolet","aquamarine","chartreuse1","lightsalmon"), - success=c("darkorange1","blue4","aquamarine3","chartreuse3","firebrick4")) + habitat=c("agricultural.grassland","coniferous.forest","deciduous.forest","natural.grassland","woody.heatland","crops"), + failure=c("yellow","blueviolet","aquamarine","chartreuse1","lightsalmon","slategray3"), + success=c("darkorange1","blue4","aquamarine3","chartreuse3","firebrick4","slategrey")) # Other - studied.habitat=c("coniferous.forest","deciduous.forest","natural.grassland","woody.heatland","agricultural.grassland") + studied.habitat=c("coniferous.forest","deciduous.forest","natural.grassland","woody.heatland","agricultural.grassland","crops") RF.param = list( share.training=0.7, ntree=500) From 95b185a562f42e3c29f83aef848db041373d2b1d Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:58:30 +0100 Subject: [PATCH 027/176] Add files via upload Small adjustment of the header --- R/POST_FATE.validation_habitat.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/POST_FATE.validation_habitat.R b/R/POST_FATE.validation_habitat.R index b0524e1..263f5be 100644 --- a/R/POST_FATE.validation_habitat.R +++ b/R/POST_FATE.validation_habitat.R @@ -42,7 +42,6 @@ ##' RF model, the performance analyzes (confusion matrix and TSS) for the training and ##' testing parts of the RF model, the habitat performance file, the habitat prediction file with ##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} -##' \item{\file{DATA_OBS}}{maps of observed habitat and csv files of PFG and sites releves.} ##' } ##' ### END OF HEADER ############################################################## From a68ff792e9876df7e681935e37d11726dbf06375 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:59:10 +0100 Subject: [PATCH 028/176] Add files via upload Small correction --- man/POST_FATE.validation.habitat.Rd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/man/POST_FATE.validation.habitat.Rd b/man/POST_FATE.validation.habitat.Rd index 6c82615..6f4905d 100644 --- a/man/POST_FATE.validation.habitat.Rd +++ b/man/POST_FATE.validation.habitat.Rd @@ -32,7 +32,7 @@ and each PFG and strata (with extension).} \item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation (with extension).} -\item{releves.sites}{name of the file which contain coordinates and a description of +\item{releves.site}{name of the file which contain coordinates and a description of the habitat associated with the dominant species of each site in the studied map (with extension).} } \value{ @@ -42,7 +42,6 @@ Two folders are created in name.simulation folder : RF model, the performance analyzes (confusion matrix and TSS) for the training and testing parts of the RF model, the habitat performance file, the habitat prediction file with observed and simulated habitat for each pixel of the whole map and the final prediction plot.} - \item{\file{DATA_OBS}}{maps of observed habitat and csv files of PFG and sites releves.} } } \description{ From de6cb9ff714ad6b8e8099e9b38808dda4af74d59 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:59:58 +0100 Subject: [PATCH 029/176] Add files via upload Small correction --- docs/reference/POST_FATE.validation.habitat.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/reference/POST_FATE.validation.habitat.html b/docs/reference/POST_FATE.validation.habitat.html index 3cbbcab..c6cd0f1 100644 --- a/docs/reference/POST_FATE.validation.habitat.html +++ b/docs/reference/POST_FATE.validation.habitat.html @@ -174,7 +174,7 @@

Arguments

name of the file which contain the extended studied map in the simulation (with extension).

validation.mask

name of the file which contain a raster mask that specified which pixels need validation (with extension).

-
releves.sites
+
releves.site

name of the file which contain coordinates and a description of the habitat associated with the dominant species of each site in the studied map (with extension).

@@ -186,9 +186,6 @@

Value

testing parts of the RF model, the habitat performance file, the habitat prediction file with observed and simulated habitat for each pixel of the whole map and the final prediction plot.

-
DATA_OBS
-

maps of observed habitat and csv files of PFG and sites releves.

-
From 1dba32b031a22212783eece77f9ab3500e3e66d5 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 23 Feb 2022 12:13:06 +0100 Subject: [PATCH 030/176] Update POST_FATE.validation.habitat.html --- docs/reference/POST_FATE.validation.habitat.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/POST_FATE.validation.habitat.html b/docs/reference/POST_FATE.validation.habitat.html index c6cd0f1..1a0d761 100644 --- a/docs/reference/POST_FATE.validation.habitat.html +++ b/docs/reference/POST_FATE.validation.habitat.html @@ -141,7 +141,7 @@

Compute habitat performance and create a prediction plot of habitat

This script compare habitat simulations and observations and -create a map to visualize this comparison with all the the FATE and +create a map to visualize this comparison with all the FATE and observed data.

@@ -174,13 +174,13 @@

Arguments

name of the file which contain the extended studied map in the simulation (with extension).

validation.mask

name of the file which contain a raster mask that specified which pixels need validation (with extension).

-
releves.site
+
releves.sites

name of the file which contain coordinates and a description of the habitat associated with the dominant species of each site in the studied map (with extension).

Value

-

Two folders are created in name.simulation folder :

VALIDATION/HABITAT/sim.version
+

One folder is created in name.simulation folder :

VALIDATION/HABITAT/sim.version

containing the prepared CBNA data, RF model, the performance analyzes (confusion matrix and TSS) for the training and testing parts of the RF model, the habitat performance file, the habitat prediction file with From a54bf0a5084f4aaf51ce5db89abdc0dcb85b8143 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:39:52 +0100 Subject: [PATCH 031/176] Update UTILS.train_RF_habitat.R Correction of mistakes --- R/UTILS.train_RF_habitat.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 334e079..d7f2393 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -34,8 +34,8 @@ ##' @details ##' ##' This function transform PFG Braund-Blanquet abundance in relative abundance, -##' get habitat information from the releves map, keep only relees on interesting -##' habitat and then builds de random forest model. Finally, the function analyzes +##' get habitat information from the relevés map, keep only relevés on interesting +##' habitat and then builds the random forest model. Finally, the function analyzes ##' the model performance with computation of confusion matrix and TSS for ##' the traning and testing sample. ##' From 2f1681c9d40cc76e147fc113035c9f6d6902e8f8 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:40:57 +0100 Subject: [PATCH 032/176] Update train.RF.habitat.Rd correction of mistakes --- man/train.RF.habitat.Rd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/train.RF.habitat.Rd b/man/train.RF.habitat.Rd index 272141c..21560e5 100644 --- a/man/train.RF.habitat.Rd +++ b/man/train.RF.habitat.Rd @@ -60,8 +60,8 @@ habitat. } \details{ This function transform PFG Braund-Blanquet abundance in relative abundance, -get habitat information from the releves map, keep only relees on interesting -habitat and then builds de random forest model. Finally, the function analyzes +get habitat information from the relevés map, keep only relevés on interesting +habitat and then builds the random forest model. Finally, the function analyzes the model performance with computation of confusion matrix and TSS for the traning and testing sample. } From 2604496280b78728957771a11d7ee7c2b4fd2b8e Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:42:01 +0100 Subject: [PATCH 033/176] Update train.RF.habitat.html correction of mistakes --- docs/reference/train.RF.habitat.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/train.RF.habitat.html b/docs/reference/train.RF.habitat.html index 9c99be9..009f4ec 100644 --- a/docs/reference/train.RF.habitat.html +++ b/docs/reference/train.RF.habitat.html @@ -199,8 +199,8 @@

Value

Details

This function transform PFG Braund-Blanquet abundance in relative abundance, -get habitat information from the releves map, keep only relees on interesting -habitat and then builds de random forest model. Finally, the function analyzes +get habitat information from the relevés map, keep only relevés on interesting +habitat and then builds the random forest model. Finally, the function analyzes the model performance with computation of confusion matrix and TSS for the traning and testing sample.

From 463a0372058ce6b05bdec827269fbef310c155e7 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 1 Mar 2022 16:24:45 +0100 Subject: [PATCH 034/176] Add files via upload Files corrected --- R/POST_FATE.validation_habitat.R | 87 +++++++++++++------------------- R/PRE_FATE.skeletonDirectory.R | 10 ++++ R/UTILS.do_habitat_validation.R | 25 +++++++-- R/UTILS.plot_predicted_habitat.R | 13 ++++- R/UTILS.train_RF_habitat.R | 35 +++++++++---- 5 files changed, 103 insertions(+), 67 deletions(-) diff --git a/R/POST_FATE.validation_habitat.R b/R/POST_FATE.validation_habitat.R index 263f5be..b13062f 100644 --- a/R/POST_FATE.validation_habitat.R +++ b/R/POST_FATE.validation_habitat.R @@ -8,7 +8,7 @@ ##' @author Matthieu Combaud, Maxime Delprat ##' ##' @description This script compare habitat simulations and observations and -##' create a map to visualize this comparison with all the the \code{FATE} and +##' create a map to visualize this comparison with all the \code{FATE} and ##' observed data. ##' ##' @param name.simulation simulation folder name. @@ -22,6 +22,10 @@ ##' the habitat associated with the dominant species of each site in the studied map (with extension). ##' @param hab.obs name of the file which contain the extended studied map in the simulation (with extension). ##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation (with extension). +##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will +##' take into account of all habitats in the hab.obs map. Otherwise, please specify +##' in a vector the habitats that we take into account for the validation. +##' @param year year of simulation for validation. ##' ##' @details ##' @@ -44,6 +48,12 @@ ##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} ##' } ##' +##' @export +##' +##' @importFrom raster raster projectRaster res crs +##' @importFrom sf st_read +##' @importFrom utils read.csv +##' ### END OF HEADER ############################################################## @@ -53,57 +63,25 @@ POST_FATE.validation_habitat = function(name.simulation , releves.PFG , releves.sites , hab.obs - , validation.mask) + , validation.mask + , studied.habitat = NULL + , year) { - ## LIBRARIES - require(data.table) - require(raster) - require(RFate) - require(reshape2) - require(stringr) - require(foreign) - require(stringr) - require(dplyr) - require(sp) - options("rgdal_show_exportToProj4_warnings"="none") - require(rgdal) - require(randomForest) - require(ggplot2) - require(ggradar) - require(tidyverse) - require(ggpubr) - require(gridExtra) - require(vegan) - require(parallel) - require(scales) - require(class) - require(caret) - require(sampling) - require(tidyselect) - require(grid) - require(gtable) - require(scales) - require(cowplot) - require(sf) - require(visNetwork) - require(foreach) - require(doParallel) - require(prettyR) - require(vcd) - ## GLOBAL PARAMETERS - # Create directories - dir.create(paste0(name.simulation, "/VALIDATION"), recursive = TRUE) - dir.create(paste0(name.simulation, "/VALIDATION/HABITAT"), recursive = TRUE) - dir.create(paste0(name.simulation, "/VALIDATION/HABITAT/", sim.version), recursive = TRUE) + dir.create(file.path(name.simulation, "VALIDATION", "HABITAT", sim.version), showWarnings = FALSE) # General output.path = paste0(name.simulation, "/VALIDATION") + year = year # Useful elements to extract from the simulation - simulation.map=raster(paste0(name.simulation,"/DATA/MASK/MASK_Champsaur.tif")) + name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), + flag = "MASK", + flag.split = "^--.*--$", + is.num = FALSE) + simulation.map = raster(paste0(name)) # For habitat validation # CBNA releves data habitat map @@ -115,14 +93,14 @@ POST_FATE.validation_habitat = function(name.simulation habitat.FATE.map <- crop(hab.obs.modif, simulation.map) validation.mask<-raster(paste0(obs.path, validation.mask)) - # Provide a color df - col.df<-data.frame( - habitat=c("agricultural.grassland","coniferous.forest","deciduous.forest","natural.grassland","woody.heatland","crops"), - failure=c("yellow","blueviolet","aquamarine","chartreuse1","lightsalmon","slategray3"), - success=c("darkorange1","blue4","aquamarine3","chartreuse3","firebrick4","slategrey")) - # Other - studied.habitat=c("coniferous.forest","deciduous.forest","natural.grassland","woody.heatland","agricultural.grassland","crops") + if(is.null(studied.habitat)){ + studied.habitat = studied.habitat + } else if(is.character(studied.habitat)){ + studied.habitat = studied.habitat + } else{ + stop("studied.habitat is not a vector of character") + } RF.param = list( share.training=0.7, ntree=500) @@ -151,10 +129,17 @@ POST_FATE.validation_habitat = function(name.simulation , sim.version = sim.version , name.simulation = name.simulation , perStrata = F - , hab.obs = hab.obs) + , hab.obs = hab.obs + , year = year) ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT + # Provide a color df + col.df<-data.frame( + habitat = RF.model$classes, + failure = terrain.colors(length(RF.model$classes), alpha = 0.5), + success = terrain.colors(length(RF.model$classes), alpha = 1)) + prediction.map <- plot.predicted.habitat(predicted.habitat = habitats.results , col.df = col.df , simulation.map = simulation.map diff --git a/R/PRE_FATE.skeletonDirectory.R b/R/PRE_FATE.skeletonDirectory.R index 6201040..919f745 100644 --- a/R/PRE_FATE.skeletonDirectory.R +++ b/R/PRE_FATE.skeletonDirectory.R @@ -70,6 +70,13 @@ ##' \code{\link{PRE_FATE.params_simulParameters}})} ##' \item{\code{RESULTS}}{this folder will collect all the results produced by the ##' software with a folder for each simulation} +##' \item{\code{VALIDATION}}{this folder will collect all the validation files produced +##' by POST_FATE validation functions +##' \describe{ +##' \item{\code{HABITAT}}{this folder will collect all the validation files produces +##' by the function POST_FATE.validation.habitat} +##' } +##' } ##' } ##' ##' \strong{NB :} \cr @@ -136,6 +143,9 @@ PRE_FATE.skeletonDirectory = function(name.simulation = "FATE_simulation") dir.create(file.path(name.simulation, "PARAM_SIMUL"), showWarnings = FALSE) ## the RESULTS dir dir.create(file.path(name.simulation, "RESULTS"), showWarnings = FALSE) + ## the VALIDATION dir + dir.create(file.path(name.simulation, "VALIDATION"), showWarnings = FALSE) + dir.create(file.path(name.simulation, "VALIDATION", "HABITAT"), showWarnings = FALSE) message("\n Your directory tree for your FATE simulation (" , name.simulation, ") is ready!\n") diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index c0dae0e..2296d4e 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -5,7 +5,7 @@ ##' ##' @name do.habitat.validation ##' -##' @author Matthieu Combaud, Maxime Delprat +##' @author Matthieu Combaud & Maxime Delprat ##' ##' @description To compare observations and simulations, this function compute ##' confusion matrix between observation and prediction and then compute the TSS @@ -22,12 +22,13 @@ ##' the consistency between simulation map and the observed habitat map. ##' @param predict.all.map a TRUE/FALSE vector. If TRUE, the script will predict ##' habitat for the whole map. -##' @param sim.version name of the simulation we want to validate. +##' @param sim.version name of the simulation to validate. ##' @param name.simulation simulation folder name. ##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined ##' by strata in each pixel. If FALSE, PFG abundance is defined for all strata. ##' @param hab.obs a raster map of the observed habitat in the ##' extended studied area. +##' @param year year of simulation for validation. ##' ##' @details ##' @@ -44,10 +45,24 @@ ##' If option selected, the function returns an habitat prediction file with ##' observed and simulated habitat for each pixel of the whole map. ##' +##' @export +##' +##' @importFrom raster compareCRS res projectRaster extent crop origin compareRaster +##' getValues aggregate predict +##' @importFrom stringr str_sub +##' @importFrom dplyr select filter rename group_by %>% mutate rename +##' @importFrom foreach foreach %dopar% +##' @importFrom forcats fct_expand +##' @importFrom reshape2 dcast +##' @importFrom randomForest +##' @importFrom vcd +##' @importFrom caret confusionMatrix +##' @importFrom utils write.csv +##' ### END OF HEADER ############################################################## -do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs) { +do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs, year) { #notes # we prepare the relevé data in this function, but in fact we could provide them directly if we adjust the code @@ -152,7 +167,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #get simulated abundance per pixel*strata*PFG for pixels in the simulation area simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,-c(3:44)] + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] colnames(simu_PFG) = c("PFG", "pixel", "abs") #aggregate per strata group with the correspondance provided in input @@ -185,7 +200,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat simu_PFG$strata<-fct_expand(simu_PFG$strata,list.strata) #cast - simu_PFG<-dcast(simu_PFG,pixel~PFG*strata,value.var=c("relative.abundance"),fill=0,drop=F) + simu_PFG<-reshape2::dcast(simu_PFG,pixel~PFG*strata,value.var=c("relative.abundance"),fill=0,drop=F) #merge PFG info and habitat + transform habitat into factor diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 80e9be2..0504edb 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -31,6 +31,17 @@ ##' @return ##' ##' a synthetic.prediction.png file which contain the final prediction plot. +##' +##' @export +##' +##' @importFrom dplyr select all_of +##' @importFrom data.table melt rename +##' @importFrom utils write.csv +##' @importFrom raster raster crs extent res ratify writeRaster +##' @importFrom stats complete.cases +##' @importFrom ggplot2 ggplot geom_raster coord_equal scale_fill_manual +##' ggtitle guides theme ggsave +##' ### END OF HEADER ############################################################## @@ -81,7 +92,7 @@ plot.predicted.habitat<-function(predicted.habitat #merge the prediction df with the df containing color and habitat code predicted.habitat<-merge(predicted.habitat,habitat.code.df,by.x=c("modal.predicted.habitat","prediction.code"),by.y=c("habitat","prediction.code")) write.csv(x = predicted.habitat, file = paste0(output.path, "/HABITAT/", sim.version, "/hab.pred.csv")) - + #plot diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index d7f2393..ba833e0 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -19,8 +19,9 @@ ##' extended studied area. ##' @param external.training.mask default \code{NULL}. (optional) Keep only ##' releves data in a specific area. -##' @param studied.habitat a vector that specifies habitats that we take -##' into account for the validation. +##' @param studied.habitat If \code{NULL}, the function will +##' take into account of all habitats in the hab.obs map. Otherwise, please specify +##' in a vector the habitats that we take into account for the validation. ##' @param RF.param a list of 2 parameters for random forest model : ##' share.training defines the size of the trainig part of the data base. ##' ntree is the number of trees build by the algorithm, it allows to reduce @@ -34,8 +35,8 @@ ##' @details ##' ##' This function transform PFG Braund-Blanquet abundance in relative abundance, -##' get habitat information from the relevés map, keep only relevés on interesting -##' habitat and then builds the random forest model. Finally, the function analyzes +##' get habitat information from the releves map, keep only relees on interesting +##' habitat and then builds de random forest model. Finally, the function analyzes ##' the model performance with computation of confusion matrix and TSS for ##' the traning and testing sample. ##' @@ -47,13 +48,24 @@ ##' the performance analyzes (confusion matrix and TSS) for the training and ##' testing parts. ##' +##' @export +##' +##' @importFrom dplyr filter %>% group_by select +##' @importFrom data.table dcast setDT +##' @importFrom raster extract aggregate compareCRS +##' @importFrom sf st_transform st_crop st_write +##' @importFrom randomForest randomForest tuneRF +##' @importFrom caret confusionMatrix +##' @importFrom tidyverse write_rds +##' @importFrom utils read.csv +##' ### END OF HEADER ############################################################## train.RF.habitat<-function(releves.PFG , releves.sites , hab.obs - , external.training.mask=NULL + , external.training.mask = NULL , studied.habitat , RF.param , output.path @@ -127,9 +139,12 @@ train.RF.habitat<-function(releves.PFG # 4. Keep only releve on interesting habitat ###################################################" - aggregated.releves.PFG<-filter(aggregated.releves.PFG,is.element(habitat,studied.habitat)) #filter non interesting habitat + NA - - print(cat("habitat classes used in the RF algo: ",unique(aggregated.releves.PFG$habitat),"\n",sep="\t")) + if (!is.null(studied.habitat)){ + aggregated.releves.PFG<-filter(aggregated.releves.PFG,is.element(habitat,studied.habitat)) #filter non interesting habitat + NA + print(cat("habitat classes used in the RF algo: ",unique(aggregated.releves.PFG$habitat),"\n",sep="\t")) + } else{ + print(cat("habitat classes used in the RF algo: ",unique(aggregated.releves.PFG$habitat),"\n",sep="\t")) + } # 5. Save data ##################### @@ -161,7 +176,7 @@ train.RF.habitat<-function(releves.PFG x=select(releves.training,-c(code.habitat,site,habitat,geometry)), y=releves.training$habitat, strata=releves.training$habitat, - sampsize=min(table(releves.training$habitat)), + sampsize=nrow(releves.training), ntreeTry=RF.param$ntree, stepFactor=2, improve=0.05,doBest=FALSE,plot=F,trace=F ) @@ -177,7 +192,7 @@ train.RF.habitat<-function(releves.PFG xtest=select(releves.testing,-c(code.habitat,site,habitat,geometry)), ytest=releves.testing$habitat, strata=releves.training$habitat, - min(table(releves.training$habitat)), + sampsize=nrow(releves.training), ntree=RF.param$ntree, mtry=mtry, norm.votes=TRUE, From 50fc4de08e59dc77727b3cd7ba17939bef3a8220 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 1 Mar 2022 16:25:41 +0100 Subject: [PATCH 035/176] Add files via upload Files corrected --- man/POST_FATE.validation.habitat.Rd | 12 ++++++++++-- man/PRE_FATE.skeletonDirectory.Rd | 7 +++++++ man/do.habitat.validation.Rd | 9 ++++++--- man/train.RF.habitat.Rd | 11 ++++++----- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/man/POST_FATE.validation.habitat.Rd b/man/POST_FATE.validation.habitat.Rd index 6f4905d..81d101a 100644 --- a/man/POST_FATE.validation.habitat.Rd +++ b/man/POST_FATE.validation.habitat.Rd @@ -13,7 +13,9 @@ POST_FATE.validation_habitat( releves.PFG, releves.sites, hab.obs, - validation.mask + validation.mask, + studied.habitat = NULL, + year ) } \arguments{ @@ -32,6 +34,12 @@ and each PFG and strata (with extension).} \item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation (with extension).} +\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will +take into account of all habitats in the hab.obs map. Otherwise, please specify +in a vector the habitats that we take into account for the validation.} + +\item{year}{year of simulation for validation.} + \item{releves.site}{name of the file which contain coordinates and a description of the habitat associated with the dominant species of each site in the studied map (with extension).} } @@ -46,7 +54,7 @@ observed and simulated habitat for each pixel of the whole map and the final pre } \description{ This script compare habitat simulations and observations and -create a map to visualize this comparison with all the the \code{FATE} and +create a map to visualize this comparison with all the \code{FATE} and observed data. } \details{ diff --git a/man/PRE_FATE.skeletonDirectory.Rd b/man/PRE_FATE.skeletonDirectory.Rd index 60f61f9..7bc8802 100644 --- a/man/PRE_FATE.skeletonDirectory.Rd +++ b/man/PRE_FATE.skeletonDirectory.Rd @@ -76,6 +76,13 @@ The tree structure is detailed below : \code{\link{PRE_FATE.params_simulParameters}})} \item{\code{RESULTS}}{this folder will collect all the results produced by the software with a folder for each simulation} + \item{\code{VALIDATION}}{this folder will collect all the validation files produced + by POST_FATE validation functions + \describe{ + \item{\code{HABITAT}}{this folder will collect all the validation files produces + by the function POST_FATE.validation.habitat} + } + } } \strong{NB :} \cr diff --git a/man/do.habitat.validation.Rd b/man/do.habitat.validation.Rd index 5163b04..cccd6f2 100644 --- a/man/do.habitat.validation.Rd +++ b/man/do.habitat.validation.Rd @@ -5,7 +5,7 @@ \title{Compare observed and simulated habitat of a \code{FATE} simulation at the last simulation year.} \usage{ -do.habitat.validation( +\method{do}{habitat.validation}( output.path, RF.model, habitat.FATE.map, @@ -15,7 +15,8 @@ do.habitat.validation( sim.version, name.simulation, perStrata, - hab.obs + hab.obs, + year ) } \arguments{ @@ -36,7 +37,7 @@ the consistency between simulation map and the observed habitat map.} \item{predict.all.map}{a TRUE/FALSE vector. If TRUE, the script will predict habitat for the whole map.} -\item{sim.version}{name of the simulation we want to validate.} +\item{sim.version}{name of the simulation to validate.} \item{name.simulation}{simulation folder name.} @@ -45,6 +46,8 @@ by strata in each pixel. If FALSE, PFG abundance is defined for all strata.} \item{hab.obs}{a raster map of the observed habitat in the extended studied area.} + +\item{year}{year of simulation for validation.} } \value{ Habitat performance file diff --git a/man/train.RF.habitat.Rd b/man/train.RF.habitat.Rd index 21560e5..1e7cd04 100644 --- a/man/train.RF.habitat.Rd +++ b/man/train.RF.habitat.Rd @@ -4,7 +4,7 @@ \alias{train.RF.habitat} \title{Create a random forest algorithm trained on CBNA data.} \usage{ -train.RF.habitat( +\method{train}{RF.habitat}( releves.PFG, releves.sites, hab.obs, @@ -30,8 +30,9 @@ extended studied area.} \item{external.training.mask}{default \code{NULL}. (optional) Keep only releves data in a specific area.} -\item{studied.habitat}{a vector that specifies habitats that we take -into account for the validation.} +\item{studied.habitat}{If \code{NULL}, the function will +take into account of all habitats in the hab.obs map. Otherwise, please specify +in a vector the habitats that we take into account for the validation.} \item{RF.param}{a list of 2 parameters for random forest model : share.training defines the size of the trainig part of the data base. @@ -60,8 +61,8 @@ habitat. } \details{ This function transform PFG Braund-Blanquet abundance in relative abundance, -get habitat information from the relevés map, keep only relevés on interesting -habitat and then builds the random forest model. Finally, the function analyzes +get habitat information from the releves map, keep only relees on interesting +habitat and then builds de random forest model. Finally, the function analyzes the model performance with computation of confusion matrix and TSS for the traning and testing sample. } From d4dd212c4ae96bc5efad1074414beea9f84b1e72 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 1 Mar 2022 16:26:51 +0100 Subject: [PATCH 036/176] Add files via upload Files corrected --- .../POST_FATE.validation.habitat.html | 16 +- .../reference/PRE_FATE.skeletonDirectory.html | 318 ++++++++---------- docs/reference/do.habitat.validation.html | 10 +- docs/reference/train.RF.habitat.html | 12 +- 4 files changed, 169 insertions(+), 187 deletions(-) diff --git a/docs/reference/POST_FATE.validation.habitat.html b/docs/reference/POST_FATE.validation.habitat.html index 1a0d761..b03c91a 100644 --- a/docs/reference/POST_FATE.validation.habitat.html +++ b/docs/reference/POST_FATE.validation.habitat.html @@ -2,7 +2,7 @@ Compute habitat performance and create a prediction plot of habitat for a whole map of a FATE simulation. — POST_FATE.validation.habitat • RFate - - - - - - -Create the skeleton folder for a FATE simulation — PRE_FATE.skeletonDirectory • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create the skeleton folder for a FATE simulation — PRE_FATE.skeletonDirectory • RFate - - - - + + -
-
- -
- -
+
@@ -206,129 +140,163 @@

Create the skeleton folder for a FATE simulation

tree to run a FATE simulation.

-
PRE_FATE.skeletonDirectory(name.simulation = "FATE_simulation")
- -

Arguments

- - - - - - -
name.simulation

a string that will be used as the main -directory and simulation name

- -

Value

+
+
PRE_FATE.skeletonDirectory(name.simulation = "FATE_simulation")
+
+
+

Arguments

+
name.simulation
+

a string that will be used as the main +directory and simulation name

+
+
+

Value

A directory tree with folders to contain the parameter files, the simulation files and the results.

-

Details

- +
+
+

Details

FATE requires only one input parameter (see -PRE_FATE.params_simulParameters),

-
+ +
+

Author

Maya Guéguen

+
-

Examples

-
-## Create a skeleton folder with the default name ('FATE_simulation') ------------------------
-PRE_FATE.skeletonDirectory()
+    
+

Examples

+

+## Create a skeleton folder with the default name ('FATE_simulation') ------------------------
+PRE_FATE.skeletonDirectory()
 
-## Create a skeleton folder with a specific name ---------------------------------------------
-PRE_FATE.skeletonDirectory(name.simulation = 'FATE_AlpineForest')
+## Create a skeleton folder with a specific name ---------------------------------------------
+PRE_FATE.skeletonDirectory(name.simulation = 'FATE_AlpineForest')
 
-
+
+
+
- - - + + diff --git a/docs/reference/do.habitat.validation.html b/docs/reference/do.habitat.validation.html index 9dd1092..5914180 100644 --- a/docs/reference/do.habitat.validation.html +++ b/docs/reference/do.habitat.validation.html @@ -146,7 +146,8 @@

Compare observed and simulated habitat of a FATE simulation
-
do.habitat.validation(
+    
# S3 method for habitat.validation
+do(
   output.path,
   RF.model,
   habitat.FATE.map,
@@ -156,7 +157,8 @@ 

Compare observed and simulated habitat of a FATE simulation sim.version, name.simulation, perStrata, - hab.obs + hab.obs, + year )

@@ -180,7 +182,7 @@

Arguments

a TRUE/FALSE vector. If TRUE, the script will predict habitat for the whole map.

sim.version
-

name of the simulation we want to validate.

+

name of the simulation to validate.

name.simulation

simulation folder name.

perStrata
@@ -189,6 +191,8 @@

Arguments

hab.obs

a raster map of the observed habitat in the extended studied area.

+
year
+

year of simulation for validation.

Value

diff --git a/docs/reference/train.RF.habitat.html b/docs/reference/train.RF.habitat.html index 009f4ec..c2d6c39 100644 --- a/docs/reference/train.RF.habitat.html +++ b/docs/reference/train.RF.habitat.html @@ -143,7 +143,8 @@

Create a random forest algorithm trained on CBNA data.

-
train.RF.habitat(
+    
# S3 method for RF.habitat
+train(
   releves.PFG,
   releves.sites,
   hab.obs,
@@ -172,8 +173,9 @@ 

Arguments

default NULL. (optional) Keep only releves data in a specific area.

studied.habitat
-

a vector that specifies habitats that we take -into account for the validation.

+

If NULL, the function will +take into account of all habitats in the hab.obs map. Otherwise, please specify +in a vector the habitats that we take into account for the validation.

RF.param

a list of 2 parameters for random forest model : share.training defines the size of the trainig part of the data base. @@ -199,8 +201,8 @@

Value

Details

This function transform PFG Braund-Blanquet abundance in relative abundance, -get habitat information from the relevés map, keep only relevés on interesting -habitat and then builds the random forest model. Finally, the function analyzes +get habitat information from the releves map, keep only relees on interesting +habitat and then builds de random forest model. Finally, the function analyzes the model performance with computation of confusion matrix and TSS for the traning and testing sample.

From 54de8087eb3b281eeb6d7e317363a4c6b8aff37e Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 1 Mar 2022 16:27:39 +0100 Subject: [PATCH 037/176] Add files via upload file corrected --- NAMESPACE | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 0891e0a..790e668 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,8 @@ # Generated by roxygen2: do not edit by hand +S3method(do,habitat.validation) +S3method(plot,predicted.habitat) +S3method(train,RF.habitat) export(.adaptMaps) export(.cropMaps) export(.getCutoff) @@ -25,6 +28,7 @@ export(POST_FATE.graphic_validationStatistics) export(POST_FATE.graphics) export(POST_FATE.relativeAbund) export(POST_FATE.temporalEvolution) +export(POST_FATE.validation_habitat) export(PRE_FATE.abundBraunBlanquet) export(PRE_FATE.params_PFGdispersal) export(PRE_FATE.params_PFGdisturbance) @@ -74,15 +78,28 @@ importFrom(adehabitatHR,kernelUD) importFrom(adehabitatMA,ascgen) importFrom(ape,as.phylo) importFrom(ape,plot.phylo) +importFrom(caret,confusionMatrix) importFrom(cluster,silhouette) importFrom(colorspace,heat_hcl) importFrom(colorspace,sequential_hcl) importFrom(cowplot,get_legend) importFrom(cowplot,ggdraw) +importFrom(data.table,dcast) importFrom(data.table,fread) importFrom(data.table,fwrite) +importFrom(data.table,melt) importFrom(data.table,rbindlist) +importFrom(data.table,rename) +importFrom(data.table,setDT) importFrom(doParallel,registerDoParallel) +importFrom(dplyr,"%>%") +importFrom(dplyr,all_of) +importFrom(dplyr,filter) +importFrom(dplyr,group_by) +importFrom(dplyr,mutate) +importFrom(dplyr,rename) +importFrom(dplyr,select) +importFrom(forcats,fct_expand) importFrom(foreach,"%do%") importFrom(foreach,"%dopar%") importFrom(foreach,foreach) @@ -115,6 +132,8 @@ importFrom(ggplot2,geom_smooth) importFrom(ggplot2,geom_vline) importFrom(ggplot2,ggplot) importFrom(ggplot2,ggsave) +importFrom(ggplot2,ggtitle) +importFrom(ggplot2,guides) importFrom(ggplot2,labs) importFrom(ggplot2,scale_alpha) importFrom(ggplot2,scale_color_continuous) @@ -151,29 +170,46 @@ importFrom(huge,huge.npn) importFrom(methods,as) importFrom(parallel,mclapply) importFrom(phyloclim,niche.overlap) +importFrom(randomForest,) +importFrom(randomForest,randomForest) +importFrom(randomForest,tuneRF) +importFrom(raster,aggregate) importFrom(raster,as.data.frame) importFrom(raster,as.matrix) importFrom(raster,cellFromXY) importFrom(raster,cellStats) +importFrom(raster,compareCRS) +importFrom(raster,compareRaster) importFrom(raster,coordinates) importFrom(raster,crop) +importFrom(raster,crs) importFrom(raster,extension) importFrom(raster,extent) importFrom(raster,extract) +importFrom(raster,getValues) importFrom(raster,mask) importFrom(raster,nlayers) +importFrom(raster,origin) +importFrom(raster,predict) importFrom(raster,projectRaster) importFrom(raster,projection) importFrom(raster,raster) importFrom(raster,rasterToPoints) +importFrom(raster,ratify) importFrom(raster,res) importFrom(raster,stack) importFrom(raster,writeRaster) importFrom(raster,xyFromCell) +importFrom(reshape2,dcast) importFrom(reshape2,melt) +importFrom(sf,st_crop) +importFrom(sf,st_read) +importFrom(sf,st_transform) +importFrom(sf,st_write) importFrom(shiny,runApp) importFrom(sp,SpatialPoints) importFrom(stats,as.dist) +importFrom(stats,complete.cases) importFrom(stats,cophenetic) importFrom(stats,cor) importFrom(stats,cutree) @@ -188,10 +224,13 @@ importFrom(stats,runif) importFrom(stats,sd) importFrom(stats,var) importFrom(stats,weighted.mean) +importFrom(stringr,str_sub) +importFrom(tidyverse,write_rds) importFrom(utils,combn) importFrom(utils,download.file) importFrom(utils,install.packages) importFrom(utils,packageDescription) +importFrom(utils,read.csv) importFrom(utils,read.delim) importFrom(utils,setTxtProgressBar) importFrom(utils,tail) @@ -199,4 +238,5 @@ importFrom(utils,txtProgressBar) importFrom(utils,write.csv) importFrom(utils,write.table) importFrom(utils,zip) +importFrom(vcd,) useDynLib(RFate, .registration = TRUE) From 0b8a1d4f321a6027ef13003925e1564ec963a429 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:13:15 +0100 Subject: [PATCH 038/176] Delete POST_FATE.validation_habitat.R Creation of a POST_FATE.validation function, which include habitat validation --- R/POST_FATE.validation_habitat.R | 160 ------------------------------- 1 file changed, 160 deletions(-) delete mode 100644 R/POST_FATE.validation_habitat.R diff --git a/R/POST_FATE.validation_habitat.R b/R/POST_FATE.validation_habitat.R deleted file mode 100644 index b13062f..0000000 --- a/R/POST_FATE.validation_habitat.R +++ /dev/null @@ -1,160 +0,0 @@ -### HEADER ##################################################################### -##' -##' @title Compute habitat performance and create a prediction plot of habitat -##' for a whole map of a \code{FATE} simulation. -##' -##' @name POST_FATE.validation.habitat -##' -##' @author Matthieu Combaud, Maxime Delprat -##' -##' @description This script compare habitat simulations and observations and -##' create a map to visualize this comparison with all the \code{FATE} and -##' observed data. -##' -##' @param name.simulation simulation folder name. -##' @param sim.version name of the simulation we want to validate (it works with -##' only one sim.version). -##' @param obs.path the function needs observed data, please create a folder for them in your -##' simulation folder and then indicate in this parameter the access path to this new folder. -##' @param releves.PFG name of file which contain the observed Braund-Blanquet abundance at each site -##' and each PFG and strata (with extension). -##' @param releves.site name of the file which contain coordinates and a description of -##' the habitat associated with the dominant species of each site in the studied map (with extension). -##' @param hab.obs name of the file which contain the extended studied map in the simulation (with extension). -##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation (with extension). -##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will -##' take into account of all habitats in the hab.obs map. Otherwise, please specify -##' in a vector the habitats that we take into account for the validation. -##' @param year year of simulation for validation. -##' -##' @details -##' -##' The observed habitat is derived from the cesbio map, the simulated habitat -##' is derived from FATE simulated relative abundance, based on a random forest -##' algorithm trained on CBNA data. To compare observations and simulations, the function -##' compute confusion matrix between observation and prediction and then compute the TSS -##' for each habitat h (number of prediction of habitat h/number of observation -##' of habitat h + number of non-prediction of habitat h/number of non-observation -##' of habitat h). The final metrics this script use is the mean of TSS per habitat over all -##' habitats, weighted by the share of each habitat in the observed habitat distribution. -##' -##' @return -##' -##' Two folders are created in name.simulation folder : -##' \describe{ -##' \item{\file{VALIDATION/HABITAT/sim.version}}{containing the prepared CBNA data, -##' RF model, the performance analyzes (confusion matrix and TSS) for the training and -##' testing parts of the RF model, the habitat performance file, the habitat prediction file with -##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} -##' } -##' -##' @export -##' -##' @importFrom raster raster projectRaster res crs -##' @importFrom sf st_read -##' @importFrom utils read.csv -##' -### END OF HEADER ############################################################## - - -POST_FATE.validation_habitat = function(name.simulation - , sim.version - , obs.path - , releves.PFG - , releves.sites - , hab.obs - , validation.mask - , studied.habitat = NULL - , year) -{ - - ## GLOBAL PARAMETERS - - dir.create(file.path(name.simulation, "VALIDATION", "HABITAT", sim.version), showWarnings = FALSE) - - # General - output.path = paste0(name.simulation, "/VALIDATION") - year = year - - # Useful elements to extract from the simulation - name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), - flag = "MASK", - flag.split = "^--.*--$", - is.num = FALSE) - simulation.map = raster(paste0(name)) - - # For habitat validation - # CBNA releves data habitat map - releves.PFG<-read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) - releves.sites<-st_read(paste0(obs.path, releves.sites)) - hab.obs<-raster(paste0(obs.path, hab.obs)) - # Habitat mask at FATE simu resolution - hab.obs.modif <- projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") - habitat.FATE.map <- crop(hab.obs.modif, simulation.map) - validation.mask<-raster(paste0(obs.path, validation.mask)) - - # Other - if(is.null(studied.habitat)){ - studied.habitat = studied.habitat - } else if(is.character(studied.habitat)){ - studied.habitat = studied.habitat - } else{ - stop("studied.habitat is not a vector of character") - } - RF.param = list( - share.training=0.7, - ntree=500) - predict.all.map<-T - - ## TRAIN A RF ON CBNA DATA - - RF.model <- train.RF.habitat(releves.PFG = releves.PFG - , releves.sites = releves.sites - , hab.obs = hab.obs - , external.training.mask = NULL - , studied.habitat = studied.habitat - , RF.param = RF.param - , output.path = output.path - , perStrata = F - , sim.version = sim.version) - - ## USE THE RF MODEL TO VALIDATE OUTPUT - - habitats.results <- do.habitat.validation(output.path = output.path - , RF.model = RF.model - , habitat.FATE.map = habitat.FATE.map - , validation.mask = validation.mask - , simulation.map = simulation.map - , predict.all.map = predict.all.map - , sim.version = sim.version - , name.simulation = name.simulation - , perStrata = F - , hab.obs = hab.obs - , year = year) - - ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT - - # Provide a color df - col.df<-data.frame( - habitat = RF.model$classes, - failure = terrain.colors(length(RF.model$classes), alpha = 0.5), - success = terrain.colors(length(RF.model$classes), alpha = 1)) - - prediction.map <- plot.predicted.habitat(predicted.habitat = habitats.results - , col.df = col.df - , simulation.map = simulation.map - , output.path = output.path - , sim.version = sim.version) - - ## COMPARISON FAILURE/SUCCESS - - hab.pred = read.csv(paste0(output.path, "/HABITAT/", sim.version, "/hab.pred.csv")) - failure = as.numeric((table(hab.pred$prediction.code)[1]/sum(table(hab.pred$prediction.code)))*100) - success = as.numeric((table(hab.pred$prediction.code)[2]/sum(table(hab.pred$prediction.code)))*100) - cat("\n ---------- END OF THE SIMULATION \n") - cat(paste0("\n ---------- ", round(failure, digits = 2), "% of habitats are not correctly predicted by ", sim.version, " \n")) - cat(paste0("\n ---------- ", round(success, digits = 2), "% of habitats are correctly predicted by ", sim.version, " \n")) - return(prediction.map) - -} - From 65bf12a69d0e63eff2984083338241a11df70f6c Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:13:56 +0100 Subject: [PATCH 039/176] Delete POST_FATE.validation.habitat.html Creation of a POST_FATE.validation function, which include habitat validation --- .../POST_FATE.validation.habitat.html | 238 ------------------ 1 file changed, 238 deletions(-) delete mode 100644 docs/reference/POST_FATE.validation.habitat.html diff --git a/docs/reference/POST_FATE.validation.habitat.html b/docs/reference/POST_FATE.validation.habitat.html deleted file mode 100644 index b03c91a..0000000 --- a/docs/reference/POST_FATE.validation.habitat.html +++ /dev/null @@ -1,238 +0,0 @@ - -Compute habitat performance and create a prediction plot of habitat -for a whole map of a FATE simulation. — POST_FATE.validation.habitat • RFate - - -
-
- - - -
-
- - -
-

This script compare habitat simulations and observations and -create a map to visualize this comparison with all the FATE and -observed data.

-
- -
-
POST_FATE.validation_habitat(
-  name.simulation,
-  sim.version,
-  obs.path,
-  releves.PFG,
-  releves.sites,
-  hab.obs,
-  validation.mask,
-  studied.habitat = NULL,
-  year
-)
-
- -
-

Arguments

-
name.simulation
-

simulation folder name.

-
sim.version
-

name of the simulation we want to validate (it works with -only one sim.version).

-
obs.path
-

the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parameter the access path to this new folder.

-
releves.PFG
-

name of file which contain the observed Braund-Blanquet abundance at each site -and each PFG and strata (with extension).

-
hab.obs
-

name of the file which contain the extended studied map in the simulation (with extension).

-
validation.mask
-

name of the file which contain a raster mask that specified which pixels need validation (with extension).

-
studied.habitat
-

default NULL. If NULL, the function will -take into account of all habitats in the hab.obs map. Otherwise, please specify -in a vector the habitats that we take into account for the validation.

-
year
-

year of simulation for validation.

-
releves.site
-

name of the file which contain coordinates and a description of -the habitat associated with the dominant species of each site in the studied map (with extension).

-
-
-

Value

-

Two folders are created in name.simulation folder :

VALIDATION/HABITAT/sim.version
-

containing the prepared CBNA data, - RF model, the performance analyzes (confusion matrix and TSS) for the training and -testing parts of the RF model, the habitat performance file, the habitat prediction file with -observed and simulated habitat for each pixel of the whole map and the final prediction plot.

- - -
-
-

Details

-

The observed habitat is derived from the cesbio map, the simulated habitat -is derived from FATE simulated relative abundance, based on a random forest -algorithm trained on CBNA data. To compare observations and simulations, the function -compute confusion matrix between observation and prediction and then compute the TSS -for each habitat h (number of prediction of habitat h/number of observation -of habitat h + number of non-prediction of habitat h/number of non-observation -of habitat h). The final metrics this script use is the mean of TSS per habitat over all -habitats, weighted by the share of each habitat in the observed habitat distribution.

-
-
-

Author

-

Matthieu Combaud, Maxime Delprat

-
- -
- -
- - -
- - - - - - - - From 8d3898c289d41bcc8dc78389e6ceb74a7e021972 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:14:13 +0100 Subject: [PATCH 040/176] Delete POST_FATE.validation.habitat.Rd Creation of a POST_FATE.validation function, which include habitat validation --- man/POST_FATE.validation.habitat.Rd | 72 ----------------------------- 1 file changed, 72 deletions(-) delete mode 100644 man/POST_FATE.validation.habitat.Rd diff --git a/man/POST_FATE.validation.habitat.Rd b/man/POST_FATE.validation.habitat.Rd deleted file mode 100644 index 81d101a..0000000 --- a/man/POST_FATE.validation.habitat.Rd +++ /dev/null @@ -1,72 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/POST_FATE.validation_habitat.R -\name{POST_FATE.validation.habitat} -\alias{POST_FATE.validation.habitat} -\alias{POST_FATE.validation_habitat} -\title{Compute habitat performance and create a prediction plot of habitat -for a whole map of a \code{FATE} simulation.} -\usage{ -POST_FATE.validation_habitat( - name.simulation, - sim.version, - obs.path, - releves.PFG, - releves.sites, - hab.obs, - validation.mask, - studied.habitat = NULL, - year -) -} -\arguments{ -\item{name.simulation}{simulation folder name.} - -\item{sim.version}{name of the simulation we want to validate (it works with -only one sim.version).} - -\item{obs.path}{the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parameter the access path to this new folder.} - -\item{releves.PFG}{name of file which contain the observed Braund-Blanquet abundance at each site -and each PFG and strata (with extension).} - -\item{hab.obs}{name of the file which contain the extended studied map in the simulation (with extension).} - -\item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation (with extension).} - -\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will -take into account of all habitats in the hab.obs map. Otherwise, please specify -in a vector the habitats that we take into account for the validation.} - -\item{year}{year of simulation for validation.} - -\item{releves.site}{name of the file which contain coordinates and a description of -the habitat associated with the dominant species of each site in the studied map (with extension).} -} -\value{ -Two folders are created in name.simulation folder : -\describe{ - \item{\file{VALIDATION/HABITAT/sim.version}}{containing the prepared CBNA data, - RF model, the performance analyzes (confusion matrix and TSS) for the training and -testing parts of the RF model, the habitat performance file, the habitat prediction file with -observed and simulated habitat for each pixel of the whole map and the final prediction plot.} -} -} -\description{ -This script compare habitat simulations and observations and -create a map to visualize this comparison with all the \code{FATE} and -observed data. -} -\details{ -The observed habitat is derived from the cesbio map, the simulated habitat -is derived from FATE simulated relative abundance, based on a random forest -algorithm trained on CBNA data. To compare observations and simulations, the function -compute confusion matrix between observation and prediction and then compute the TSS -for each habitat h (number of prediction of habitat h/number of observation -of habitat h + number of non-prediction of habitat h/number of non-observation -of habitat h). The final metrics this script use is the mean of TSS per habitat over all -habitats, weighted by the share of each habitat in the observed habitat distribution. -} -\author{ -Matthieu Combaud, Maxime Delprat -} From 3772cee19c60d4293f8855afd1658f72dd644bbc Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:15:31 +0100 Subject: [PATCH 041/176] Add files via upload Addition of a POST_FATE.validation function & updates of NAMESPACE & pkgdown.yml files --- NAMESPACE | 10 ++++++---- _pkgdown.yml | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 790e668..acd8cd2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +S3method(do,PFG.composition.validation) S3method(do,habitat.validation) S3method(plot,predicted.habitat) S3method(train,RF.habitat) @@ -28,7 +29,7 @@ export(POST_FATE.graphic_validationStatistics) export(POST_FATE.graphics) export(POST_FATE.relativeAbund) export(POST_FATE.temporalEvolution) -export(POST_FATE.validation_habitat) +export(POST_FATE.validation) export(PRE_FATE.abundBraunBlanquet) export(PRE_FATE.params_PFGdispersal) export(PRE_FATE.params_PFGdisturbance) @@ -59,6 +60,7 @@ export(designLHDNorm) export(divLeinster) export(dunn) export(ecospat.kd) +export(get.observed.distribution) importFrom(FD,gowdis) importFrom(PresenceAbsence,auc) importFrom(PresenceAbsence,cmx) @@ -87,7 +89,6 @@ importFrom(cowplot,ggdraw) importFrom(data.table,dcast) importFrom(data.table,fread) importFrom(data.table,fwrite) -importFrom(data.table,melt) importFrom(data.table,rbindlist) importFrom(data.table,rename) importFrom(data.table,setDT) @@ -170,7 +171,6 @@ importFrom(huge,huge.npn) importFrom(methods,as) importFrom(parallel,mclapply) importFrom(phyloclim,niche.overlap) -importFrom(randomForest,) importFrom(randomForest,randomForest) importFrom(randomForest,tuneRF) importFrom(raster,aggregate) @@ -188,6 +188,7 @@ importFrom(raster,extent) importFrom(raster,extract) importFrom(raster,getValues) importFrom(raster,mask) +importFrom(raster,ncell) importFrom(raster,nlayers) importFrom(raster,origin) importFrom(raster,predict) @@ -200,6 +201,7 @@ importFrom(raster,res) importFrom(raster,stack) importFrom(raster,writeRaster) importFrom(raster,xyFromCell) +importFrom(readr,write_rds) importFrom(reshape2,dcast) importFrom(reshape2,melt) importFrom(sf,st_crop) @@ -224,6 +226,7 @@ importFrom(stats,runif) importFrom(stats,sd) importFrom(stats,var) importFrom(stats,weighted.mean) +importFrom(stringr,str_split) importFrom(stringr,str_sub) importFrom(tidyverse,write_rds) importFrom(utils,combn) @@ -238,5 +241,4 @@ importFrom(utils,txtProgressBar) importFrom(utils,write.csv) importFrom(utils,write.table) importFrom(utils,zip) -importFrom(vcd,) useDynLib(RFate, .registration = TRUE) diff --git a/_pkgdown.yml b/_pkgdown.yml index c9d2eda..af61946 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -129,7 +129,7 @@ reference: - "POST_FATE.binaryMaps" - "POST_FATE.graphic_mapPFGvsHS" - "POST_FATE.graphic_mapPFG" - - "POST_FATE.validation_habitat" + - "POST_FATE.validation" - title: Save FATE simulation contents: - "SAVE_FATE.step1_PFG" @@ -145,6 +145,8 @@ reference: - ".scaleMaps" - ".getCutoff" - ".unzip_ALL" - - "do_habitat_validation" - - "plot_predicted_habitat" - - "train_RF_habitat" + - "train.RF.habitat" + - "do.habitat.validation" + - "plot.predicted.habitat" + - "get.observed.distribution" + - "do.PFG.composition.validation" From c275cf8e74986aa1fe283c87950a955fa3b2a1b6 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:17:47 +0100 Subject: [PATCH 042/176] Add files via upload Creation of a new POST_FATE.validation & updates of utils functions associated --- R/POST_FATE.validation.R | 360 ++++++++++++++++++++++++ R/UTILS.do_PFG_composition_validation.R | 315 +++++++++++++++++++++ R/UTILS.do_habitat_validation.R | 11 +- R/UTILS.get_observed_distribution.R | 190 +++++++++++++ R/UTILS.plot_predicted_habitat.R | 3 +- R/UTILS.train_RF_habitat.R | 6 +- 6 files changed, 875 insertions(+), 10 deletions(-) create mode 100644 R/POST_FATE.validation.R create mode 100644 R/UTILS.do_PFG_composition_validation.R create mode 100644 R/UTILS.get_observed_distribution.R diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R new file mode 100644 index 0000000..dd85f77 --- /dev/null +++ b/R/POST_FATE.validation.R @@ -0,0 +1,360 @@ +### HEADER ########################################################################## +##' +##' @title Computes validation data for habitat, PFG richness and composition for a \code{FATE} simulation. +##' +##' @name POST_FATE.validation +##' +##' @author Matthieu Combaud, Maxime Delprat +##' +##' @description This script is designed to compute validation data for : +##' \code{Habitat} : compares habitat simulations and observations and +##' create a map to visualize this comparison with all the \code{FATE} and +##' observed data. +##' \code{PFG Composition} : produced a computation of observed distribution +##' of relative abundance in the simulation area and a computation of distance between +##' observed and simulated distribution. +##' \code{PFG Richness} : computes the PFG richness over the whole simulation area +##' for a \code{FATE} simulation and computes the difference between observed and simulated PFG richness. +##' +##' @param name.simulation simulation folder name. +##' @param sim.version name of the simulation to validate (it works with only one \code{sim.version}). +##' @param year year of simulation for validation. +##' @param doHabitat logical. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, +##' if \code{FALSE}, habitat validation module is disabled. +##' @param obs.path the function needs observed data, please create a folder for them in your +##' simulation folder and then indicate in this parameter the access path to this new folder (habitat & PFG composition validation). +##' @param releves.PFG name of file which contain the observed Braund-Blanquet abundance at each site +##' and each PFG and strata (habitat & PFG composition validation). +##' @param releves.site name of the file which contain coordinates and a description of +##' the habitat associated with the dominant species of each site in the studied map (habitat & PFG composition validation). +##' @param hab.obs name of the file which contain the extended studied map in the simulation (habitat & PFG composition validation). +##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation +##' (habitat & PFG composition validation). +##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will +##' take into account of all habitats in the \code{hab.obs} map. Otherwise, please specify +##' in a vector habitats that will be take into account for the validation (habitat validation). +##' @param doComposition logical. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, +##' if \code{FALSE}, PFG composition validation module is disabled. +##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered +##' in the validation (PFG composition validation). +##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) +##' considered in the validation (PFG composition validation). +##' @param doRichness logical. Default \code{TRUE}. If \code{TRUE}, PFG richness validation module is activated, +##' if \code{FALSE}, PFG richness validation module is disabled. +##' @param list.PFG a character vector which contain all the PFGs taken account in +##' the simulation and observed in the simulation area (PFG richness validation). +##' @param exclude.PFG default \code{NULL}. A character vector containing the names +##' of the PFG you want to exclude from the analysis (PFG richness validation). +##' +##' @details +##' +##' \describe{ +##' \item{Habitat validation}{The observed habitat is derived from the cesbio map, the simulated habitat +##' is derived from FATE simulated relative abundance, based on a random forest +##' algorithm trained on CBNA data. To compare observations and simulations, the function +##' compute confusion matrix between observation and prediction and then compute the TSS +##' for each habitat h (number of prediction of habitat h/number of observation +##' of habitat h + number of non-prediction of habitat h/number of non-observation +##' of habitat h). The final metrics this script use is the mean of TSS per habitat over all +##' habitats, weighted by the share of each habitat in the observed habitat distribution.} +##' \item{PFG composition validation}{This code firstly run the \code{get.observed.distribution} function in order to have +##' a \code{obs.distri} file which contain the observed distribution per PFG, strata and habitat. +##' This file is also an argument for the \code{do.PFG.composition.validation} function run next. +##' This second sub function provide the computation of distance between observed and simulated distribution. \cr +##' NB : The argument \code{strata.considered_PFG.compo} is by default "A" in the 2 sub functions because +##' it's easier for a \code{FATE} simulation to provide PFG abundances for all strata. \cr The argument +##' \code{perStrata.compo} is by default \code{NULL} for the same reasons.} +##' \item{PFG richness validation}{Firstly, the function updates the \code{list.PFG} with \code{exclude.PFG} vector. +##' Then, the script takes the abundance per PFG file from the results of the \code{FATE} +##' simulation and computes the difference between the \code{list.PFG} and all the PFG +##' which are presents in the abundance file, in order to obtain the PFG richness for a simulation. +##' The function also determine if an observed PFG is missing in the results of the simulation at +##' a specific year.} +##' } +##' +##' @return +##' +##' Output files : +##' \describe{ +##' \item{\file{VALIDATION/HABITAT/sim.version}}{containing the prepared CBNA data, +##' RF model, the performance analyzes (confusion matrix and TSS) for the training and +##' testing parts of the RF model, the habitat performance file, the habitat prediction file with +##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} +##' } +##' \describe{ +##' \item{\file{VALIDATION/PFG_COMPOSITION/sim.version}}{1 .csv file which contain the proximity +##' between observed and simulated data computed for each PFG/strata/habitat. \cr 1 .csv file which +##' contain the observed relevés transformed into relative metrics. \cr 1 .csv file which contain +##' the final output with the distribution per PFG, strata and habitat.} +##' } +##' \describe{ +##' \item{\file{VALIDATION/PFG_RICHNESS/sim.version}}{1 .csv file of PFG richness in a \code{FATE} simulation. +##' \cr 1 .csv fie of the PFG extinction frequency in a \code{FATE} simulation. \cr 1 .rds file which is +##' the abundance per PFG file. +##' } +##' +##' @examples +##' +##' ## Habitat validation --------------------------------------------------------------------------------- +##' POST_FATE.validation(name.simulation = "FATE_Champsaur" +##' , sim.version = "SIMUL_V4.1" +##' , year = 2000 +##' , doHabitat = TRUE +##' , obs.path = "FATE_Champsaur/DATA_OBS/" +##' , releves.PFG = "releves.PFG.abundance.csv" +##' , releves.sites = "releves.sites.shp" +##' , hab.obs = "simplified.cesbio.map.grd" +##' , validation.mask = "certain.habitat.100m.restricted.grd" +##' , studied.habitat = NULL +##' , doComposition = FALSE +##' , doRichness = FALSE) +##' +##' ## PFG composition validation -------------------------------------------------------------------------- +##' list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +##' habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") +##' POST_FATE.validation(name.simulation = "FATE_Champsaur" +##' , sim.version = "SIMUL_V4.1" +##' , year = 2000 +##' , doHabitat = FALSE +##' , obs.path = "FATE_Champsaur/DATA_OBS/" +##' , releves.PFG = "releves.PFG.abundance.csv" +##' , releves.sites = "releves.sites.shp" +##' , hab.obs = "simplified.cesbio.map.grd" +##' , validation.mask = "certain.habitat.100m.restricted.grd" +##' , studied.habitat = NULL +##' , doComposition = TRUE +##' , PFG.considered_PFG.compo = list.PFG +##' , habitat.considered_PFG.compo = habitat.considered +##' , doRichness = FALSE) +##' +##' ## PFG richness validation ----------------------------------------------------------------------------- +##' list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +##' POST_FATE.validation(name.simulation = "FATE_CHampsaur" +##' , sim.version = "SIMUL_V4.1" +##' , year = 2000 +##' , doHabitat = FALSE +##' , doComposition = FALSE +##' , doRichness = TRUE +##' , list.PFG = list.PFG +##' , exclude.PFG = NULL) +##' +##' @export +##' +##' @importFrom stringr str_split +##' @importFrom raster raster projectRaster res crs crop +##' @importFrom utils read.csv write.csv +##' @importFrom sf st_read +##' @foreach foreach foreach %dopar% +##' @importFrom forcats fct_expand +##' @importFrom readr write_rds +##' +### END OF HEADER ################################################################### + + +POST_FATE.validation = function(name.simulation + , sim.version + , year + , doHabitat = TRUE + , obs.path + , releves.PFG + , releves.sites + , hab.obs + , validation.mask + , studied.habitat = NULL + , doComposition = TRUE + , PFG.considered_PFG.compo + , habitat.considered_PFG.compo + , doRichness = TRUE + , list.PFG + , exclude.PFG = NULL){ + + if(doHabitat == TRUE){ + + ## GLOBAL PARAMETERS + + dir.create(file.path(name.simulation, "VALIDATION", "HABITAT", sim.version), showWarnings = FALSE) + + # General + output.path = paste0(name.simulation, "/VALIDATION") + year = year # choice in the year for validation + + # Useful elements to extract from the simulation + name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), + flag = "MASK", + flag.split = "^--.*--$", + is.num = FALSE) #isolate the access path to the simulation mask for any FATE simulation + simulation.map = raster(paste0(name)) + + # For habitat validation + # CBNA releves data habitat map + releves.PFG<-read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) + releves.sites<-st_read(paste0(obs.path, releves.sites)) + hab.obs<-raster(paste0(obs.path, hab.obs)) + # Habitat mask at FATE simu resolution + hab.obs.modif <- projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") + habitat.FATE.map <- crop(hab.obs.modif, simulation.map) #reprojection and croping of the extended habitat map in order to have a reduced observed habitat map + validation.mask<-raster(paste0(obs.path, validation.mask)) + + # Other + if(is.null(studied.habitat)){ + studied.habitat = studied.habitat #if null, the function will study all the habitats in the map + } else if(is.character(studied.habitat)){ + studied.habitat = studied.habitat #if a character vector with habitat names, the functuon will study only the habitats in the vector + } else{ + stop("studied.habitat is not a vector of character") + } + RF.param = list( + share.training=0.7, + ntree=500) + predict.all.map<-T + + ## TRAIN A RF ON OBSERVED DATA + + RF.model <- train.RF.habitat(releves.PFG = releves.PFG + , releves.sites = releves.sites + , hab.obs = hab.obs + , external.training.mask = NULL + , studied.habitat = studied.habitat + , RF.param = RF.param + , output.path = output.path + , perStrata = F + , sim.version = sim.version) + + ## USE THE RF MODEL TO VALIDATE FATE OUTPUT + + habitats.results <- do.habitat.validation(output.path = output.path + , RF.model = RF.model + , habitat.FATE.map = habitat.FATE.map + , validation.mask = validation.mask + , simulation.map = simulation.map + , predict.all.map = predict.all.map + , sim.version = sim.version + , name.simulation = name.simulation + , perStrata = F + , hab.obs = hab.obs + , year = year) + + ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT + + # Provide a color df + col.df<-data.frame( + habitat = RF.model$classes, + failure = terrain.colors(length(RF.model$classes), alpha = 0.5), + success = terrain.colors(length(RF.model$classes), alpha = 1)) + + prediction.map <- plot.predicted.habitat(predicted.habitat = habitats.results + , col.df = col.df + , simulation.map = simulation.map + , output.path = output.path + , sim.version = sim.version) + + } + + if(doComposition == TRUE){ + + ## GLOBAL PARAMETERS + + if(doHabitat == FALSE){ + + # Get observed distribution + releves.PFG = read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) + releves.sites = st_read(paste0(obs.path, releves.sites)) + hab.obs = raster(paste0(obs.path, hab.obs)) + # Do PFG composition validation + validation.mask = raster(paste0(obs.path, validation.mask)) + } + + ## GET OBSERVED DISTRIBUTION + + obs.distri = get.observed.distribution(name.simulation = name.simulation + , obs.path = obs.path + , releves.PFG = releves.PFG + , releves.sites = releves.sites + , hab.obs = hab.obs + , PFG.considered_PFG.compo = PFG.considered_PFG.compo + , strata.considered_PFG.compo = "A" + , habitat.considered_PFG.compo = habitat.considered_PFG.compo + , perStrata.compo = FALSE + , sim.version = sim.version) + + ## DO PFG COMPOSITION VALIDATION + + performance.composition = do.PFG.composition.validation(name.simulation = name.simulation + , obs.path = obs.path + , sim.version = sim.version + , hab.obs = hab.obs + , PFG.considered_PFG.compo = PFG.considered_PFG.compo + , strata.considered_PFG.compo = "A" + , habitat.considered_PFG.compo = habitat.considered_PFG.compo + , observed.distribution = obs.distri + , perStrata.compo = FALSE + , validation.mask = validation.mask + , year = year) + + } + + if(doRichness == TRUE){ + + output.path = paste0(name.simulation, "/VALIDATION/PFG_RICHNESS/", sim.version) + + #exclude PFG : character vector containing the names of the PFG you want to exclude from the analysis #optional + + #list of PFG of interest + list.PFG<-setdiff(list.PFG,exclude.PFG) + + dying.PFG.list<-foreach(i=1:length(sim.version)) %dopar% { + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] + colnames(simu_PFG) = c("PFG", "pixel", "abs") + + return(setdiff(list.PFG,unique(simu_PFG$PFG))) + } + + #names the results + names(dying.PFG.list) = sim.version + + #get table with PFG richness + PFG.richness.df<-data.frame(simulation=names(dying.PFG.list),richness=length(list.PFG)-unlist(lapply(dying.PFG.list,FUN="length"))) + + #get vector with one occurence per PFG*simulation with dying of the PFG, as factor with completed levels in order to have table with all PFG, including those which never die + dyingPFG.vector<-as.factor(unlist(dying.PFG.list)) + dyingPFG.vector<-fct_expand(dyingPFG.vector,list.PFG) + dying.distribution<-round(table(dyingPFG.vector)/length(sim.version),digits=2) + + #output + output = list(PFG.richness.df, dying.distribution ,dying.PFG.list) + names(output)<-c("PFG.richness.df","dying.distribution","dying.PFG.list") + + dir.create(output.path,recursive = TRUE, showWarnings = FALSE) + + write.csv(PFG.richness.df,paste0(output.path,"/performance.richness.csv"),row.names=F) + write.csv(dying.distribution,paste0(output.path,"/PFG.extinction.frequency.csv"),row.names=F) + write_rds(dying.PFG.list,file=paste0(output.path,"/dying.PFG.list.rds"),compress="none") + + } + + cat("\n ---------- END OF FUNCTION \n") + + if(doRichness == TRUE){ + cat("\n ---------- PFG RICHNESS VALIDATION RESULTS \n") + cat(paste0("\n Richness at year ", year, " : ", output[[1]][2], "\n")) + cat(paste0("\n Number of PFG extinction at year ", year, " : ", sum(output[[2]]), "\n")) + } else{cat("\n ---------- PFG RICHNESS VALIDATION DISABLED \n") + } + if(doHabitat == TRUE){ + hab.pred = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/", sim.version, "/hab.pred.csv")) + failure = as.numeric((table(hab.pred$prediction.code)[1]/sum(table(hab.pred$prediction.code)))*100) + success = as.numeric((table(hab.pred$prediction.code)[2]/sum(table(hab.pred$prediction.code)))*100) + cat("\n ---------- HABITAT VALIDATION RESULTS \n") + cat(paste0("\n", round(failure, digits = 2), "% of habitats are not correctly predicted by ", sim.version, " \n")) + cat(paste0("\n", round(success, digits = 2), "% of habitats are correctly predicted by ", sim.version, " \n")) + plot(prediction.map) + } else{cat("\n ---------- HABITAT VALIDATION DISABLED \n") + } + if(doComposition == TRUE){ + cat("\n ---------- PFG COMPOSITION VALIDATION RESULTS \n") + return(performance.composition) + } else{cat("\n ---------- PFG COMPOSITION VALIDATION DISABLED \n") + } +} diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R new file mode 100644 index 0000000..6c6dea1 --- /dev/null +++ b/R/UTILS.do_PFG_composition_validation.R @@ -0,0 +1,315 @@ +### HEADER ##################################################################### +##' +##' @title Compute distance between observed and simulated distribution +##' +##' @name do.PFG.composition.validation +##' +##' @author Matthieu Combaud, Maxime Delprat +##' +##' @description This script is designed to compare the difference between the +##' PFG distribution in observed and simulated data. For a set of PFG, strata and +##' habitats chosen, the function compute distance between observed and simulated +##' distribution for a precise \code{FATE} simulation. +##' +##' @param name.simulation simulation folder name. +##' @param obs.path the function needs observed data, please create a folder for them in your +##' simulation folder and then indicate in this parameter the access path to this new folder. +##' @param sim.version name of the simulation we want to validate (it works with +##' only one \code{sim.version}). +##' @param hab.obs file which contain the extended studied map in the simulation. +##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered +##' in the validation. +##' @param strata.considered_PFG.compo a character vector of the list of precise +##' strata considered in the validation. +##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) +##' considered in the validation. +##' @param observed.distribution PFG observed distribution table. +##' @param perStrata.compo Logical. All strata together (FALSE) or per strata (TRUE). +##' @param validation.mask file which contain a raster mask that specified +##' which pixels need validation. +##' @param year year of simulation to validate. +##' +##' @details +##' +##' After preliminary checks, this code extract observed habitat from the \code{hab.obs} +##' map and, then, merge it with the simulated PFG abundance file from results of a \code{FATE} +##' simulation. After filtration of the required PFG, strata and habitats, the function +##' transform the data into relative metrics and, then, compute distribution per PFG, strata +##' and habitat (if necessary). Finally, the code computes proximity between observed +##' and simulated data, per PFG, strata and habitat. +##' +##' @return +##' +##' 1 file is created in +##' \describe{ +##' \item{\file{VALIDATION/PFG_COMPOSITION/sim.version} : +##' A .csv file which contain the proximity between observed and simulated data computed +##' for each PFG/strata/habitat. +##' +##' @export +##' +##' @importFrom raster raster projectRaster res crs crop extent origin compareRaster +##' getValues ncell aggregate compareCRS +##' @importFrom utils read.csv write.csv +##' @importFrom dplyr rename filter group_by mutate %>% select +##' @importFrom data.table setDT +##' +### END OF HEADER ############################################################## + + +do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, hab.obs, PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, observed.distribution, perStrata.compo, validation.mask, year){ + + output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) + name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), + flag = "MASK", + flag.split = "^--.*--$", + is.num = FALSE) #isolate the access path to the simulation mask for any FATE simulation + simulation.map = raster(paste0(name)) + hab.obs.modif = projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") + habitat.FATE.map = crop(hab.obs.modif, simulation.map) #reprojection and croping of the extended habitat map in order to have a reduced observed habitat map + + #Auxiliary function to compute proximity (on a 0 to 1 scale, 1 means quantile equality) + compute.proximity<-function(simulated.quantile,observed.quantile){ + #for a given PFG*habitat*strata, return a "distance", computed as the sum of the absolute gap between observed and simulated quantile + return(1-sum(abs(simulated.quantile-observed.quantile))/4) + } + + ############################ + # 1. Preliminary checks + ############################ + + #check if strata definition used in the RF model is the same as the one used to analyze FATE output + if(perStrata.compo==F){ + list.strata<-"all" + }else{ + stop("check 'perStrata' parameter and/or the names of strata in param$list.strata.releves & param$list.strata.simul") + } + + #consistency between habitat.FATE.map and simulation.map + if(!compareCRS(simulation.map,habitat.FATE.map)){ + print("reprojecting habitat.FATE.map to match simulation.map crs") + habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) + } + if(!all(res(habitat.FATE.map)==res(simulation.map))){ + stop("provide habitat.FATE.map with same resolution as simulation.map") + } + if(extent(simulation.map)!=extent(habitat.FATE.map)){ + print("cropping habitat.FATE.map to match simulation.map") + habitat.FATE.map<-crop(x=habitat.FATE.map,y=simulation.map) + } + if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ + print("setting origin habitat.FATE.map to match simulation.map") + origin(habitat.FATE.map)<-origin(simulation.map) + } + if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates + stop("habitat.FATE.map could not be coerced to match simulation.map") + }else{ + print("simulation.map & habitat.FATE.map are (now) consistent") + } + + #adjust validation.mask accordingly + if(!all(res(habitat.FATE.map)==res(validation.mask))){ + validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") + } + if(extent(validation.mask)!=extent(habitat.FATE.map)){ + validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) + } + if(!compareRaster(validation.mask,habitat.FATE.map)){ + stop("error in correcting validation.mask to match habitat.FATE.map") + }else{ + print("validation.mask is (now) consistent with (modified) habitat.FATE.map") + } + + + ######################################### + # 2. Get observed habitat + ######################################### + + #index of the pixels in the simulation area + in.region.pixels<-which(getValues(simulation.map)==1) + + #habitat df for the whole simulation area + habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) + habitat.whole.area.df<-filter(habitat.whole.area.df,is.element(pixel,in.region.pixels)&for.validation==1) + habitat.whole.area.df<-merge(habitat.whole.area.df,dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)),by.x="code.habitat",by.y="ID") + + print("Habitat in the simulation area:") + table(habitat.whole.area.df$habitat,useNA="always") + + + ############################## + # 3. Loop on simulations + ############################## + + print("processing simulations") + + results.simul<-list() + for(i in 1:length(sim.version)) { + + # 3.1. Data preparation + ######################### + + #get simulated abundance per pixel*strata*PFG for pixels in the simulation area + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] + colnames(simu_PFG) = c("PFG", "pixel", "abs") + + #aggregate per strata group with the correspondence provided in input + simu_PFG$new.strata<-NA + + #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) + if(perStrata.compo==F){ + simu_PFG$new.strata<-"A" + } + + simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") + + #agggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) + simu_PFG<-aggregate(abs~pixel+strata+PFG,data=simu_PFG,FUN="sum") #sum and not mean because for a given CBNA strata some PFG are present in 2 FATE strata (let's say 1 unit in each) and other are present in 3 FATE strata (let's say one unit in each), so taking the mean would suppress the info that the second PFG is more present! + + # 3.2. Merge with habitat + ########################### + + #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable + simu_PFG<-merge(simu_PFG,habitat.whole.area.df,by="pixel") #at this stage we have all the pixels in the simulation area + + # 3.3. Filter the required PFG, strata and habitat + ################################################### + + simu_PFG<-filter( + simu_PFG, + is.element(PFG,PFG.considered_PFG.compo)& + is.element(strata,strata.considered_PFG.compo)& + is.element(habitat,habitat.considered_PFG.compo) + ) + + # 3.4.Transform into a relative metrics (here relative.metric is relative coverage) + ##################################################################################### + + #important to do it only here, because if we filter some PFG, it changes the value of the relative metric (no impact of filtering for habitat or for strata since we do it per strata, and habitat is constant across a given pixel) + + + #careful: if several strata/habitat are selected, the computation is made for each strata separately + simu_PFG<-as.data.frame(simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.metric= round(prop.table(abs),digits = 2))) + simu_PFG$relative.metric[is.na(simu_PFG$relative.metric)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) + simu_PFG$abs<-NULL + + # 3.5. Compute distribution per PFG, and if require per strata/habitat (else all strata/habitat will be considered together) + ############################################################################################################################## + + + #prepare the df storing quantile values + simulated.distribution<-expand.grid( + PFG=PFG.considered_PFG.compo, + habitat=habitat.considered_PFG.compo, + strata=strata.considered_PFG.compo + ) + + null.quantile<-data.frame(rank=seq(0,4,1)) #to have 5 rows per PFG*strata*habitat + simulated.distribution<-merge(simulated.distribution,null.quantile,all=T) + + if(dim(simu_PFG)[1]>0){ + + distribution<-setDT(simu_PFG)[, quantile(relative.metric), by=c("PFG","habitat","strata")] + distribution<-rename(distribution,"quantile"="V1") + distribution<-data.frame(distribution,rank=seq(0,4,1)) #add the rank number + + simulated.distribution<-merge(simulated.distribution,distribution,by=c("PFG","habitat","strata","rank"),all.x=T) # add the simulated quantiles, "all.x=T" to keep the unobserved combination (with quantile=NA then) + + simulated.distribution$quantile[is.na(simulated.distribution$quantile)]<-0 # "NA" in the previous line means that the corresponding combination PFG*strata*habitat is not present, so as a null relative abundance ! + + }else{ + simulated.distribution$quantile<-0 + } + + simulated.distribution$habitat<-as.character(simulated.distribution$habitat) #else may generate problem in ordering the database + simulated.distribution$strata<-as.character(simulated.distribution$strata) #else may generate problem in ordering the database + simulated.distribution$PFG<-as.character(simulated.distribution$PFG) #else may generate problem in ordering the database + simulated.distribution$rank<-as.numeric(simulated.distribution$rank) #else may generate problem in ordering the database + + + # 3.6. Order the table to be able to have output in the right format + ##################################################################### + simulated.distribution<-setDT(simulated.distribution) + simulated.distribution<-simulated.distribution[order(habitat,strata,PFG,rank)] + + + # 3.7. Rename + ############## + simulated.distribution<-rename(simulated.distribution,"simulated.quantile"="quantile") + + + # 3.8 Rename and reorder the observed database + ############################################### + + observed.distribution$habitat<-as.character(observed.distribution$habitat) #else may generate problem in ordering the database + observed.distribution$strata<-as.character(observed.distribution$strata) #else may generate problem in ordering the database + observed.distribution$PFG<-as.character(observed.distribution$PFG) #else may generate problem in ordering the database + observed.distribution$rank<-as.numeric(observed.distribution$rank) #else may generate problem in ordering the database + + observed.distribution<-setDT(observed.distribution) + observed.distribution<-observed.distribution[order(habitat,strata,PFG,rank)] + + # "if" to check that observed and simulated databases are in the same order + if( + !( + all(simulated.distribution$PFG==observed.distribution$PFG)& + all(simulated.distribution$habitat==observed.distribution$habitat)& + all(simulated.distribution$strata==observed.distribution$strata)& + all(simulated.distribution$rank==observed.distribution$rank) + ) + ){ + stop("Problem in observed vs simulated database (problem in the PFG*strata*habitat considered or in the database order)") + } + + # 3.9. Merge observed and simulated data + ######################################### + + simulated.distribution<-cbind(simulated.distribution,observed.quantile=observed.distribution$observed.quantile) #quicker than a merge, but we can do it only because we have worked on the order of the DT + + # 3.10 Compute proximity between observed and simulated data, per PFG*strata*habitat + ##################################################################################### + + #we get rid off rank==0 because there is good chance that it is nearly always equal to zero both in observed and simulated data, and that would provide a favorable bias in the results + + simulated.distribution<-filter(simulated.distribution,rank!=0) + + proximity<-simulated.distribution[,compute.proximity(simulated.quantile=simulated.quantile,observed.quantile=observed.quantile),by=c("PFG","habitat","strata")] + + + proximity<-rename(proximity,"proximity"="V1") + + proximity<-proximity[order(habitat,strata,PFG)] #to have output in the same order for all simulations + + + # 3.11. Aggregate results for the different PFG + ################################################ + + aggregated.proximity<-proximity[,mean(proximity),by=c("habitat","strata")] + aggregated.proximity<-rename(aggregated.proximity,"aggregated.proximity"="V1") + aggregated.proximity$aggregated.proximity<-round(aggregated.proximity$aggregated.proximity,digits=2) + aggregated.proximity$simul<-sim.version + + # return(aggregated.proximity) + + #line added because the foreach method does not work + results.simul[[i]]<-aggregated.proximity + + } + + # 4. Put in the output format + ############################## + + results<-sapply(results.simul,function(X){X$aggregated.proximity}) + rownames(results)<-paste0(results.simul[[1]]$habitat,"_",results.simul[[1]]$strata) + colnames(results)<-sim.version + results<-t(results) + results<-as.data.frame(results) + results$simulation<-rownames(results) + + #save and return + write.csv(results,paste0(output.path,"/performance.composition.csv"),row.names = F) + + return(results) +} + diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 2296d4e..7321724 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -54,8 +54,6 @@ ##' @importFrom foreach foreach %dopar% ##' @importFrom forcats fct_expand ##' @importFrom reshape2 dcast -##' @importFrom randomForest -##' @importFrom vcd ##' @importFrom caret confusionMatrix ##' @importFrom utils write.csv ##' @@ -167,7 +165,8 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #get simulated abundance per pixel*strata*PFG for pixels in the simulation area simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns + #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function colnames(simu_PFG) = c("PFG", "pixel", "abs") #aggregate per strata group with the correspondance provided in input @@ -213,7 +212,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat ################################# data.validation<-filter(data.FATE.PFG.habitat,for.validation==1) - x.validation<-select(data.validation,all_of(RF.predictors)) + x.validation<-dplyr::select(data.validation,all_of(RF.predictors)) y.validation<-data.validation$habitat y.validation.predicted<-predict(object=RF.model,newdata=x.validation,type="response",norm.votes=T) @@ -235,7 +234,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat if(predict.all.map==T){ - y.all.map.predicted = predict(object=RF.model,newdata=select(data.FATE.PFG.habitat,all_of(RF.predictors)),type="response",norm.votes=T) + y.all.map.predicted = predict(object=RF.model,newdata=dplyr::select(data.FATE.PFG.habitat,all_of(RF.predictors)),type="response",norm.votes=T) y.all.map.predicted = as.data.frame(y.all.map.predicted) y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel colnames(y.all.map.predicted) = c(sim.version, "pixel") @@ -268,7 +267,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #deal with the results regarding habitat prediction over the whole map all.map.prediction = results.simul[[1]]$y.all.map.predicted - all.map.prediction = merge(all.map.prediction, select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") + all.map.prediction = merge(all.map.prediction, dplyr::select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") all.map.prediction = rename(all.map.prediction,"true.habitat"="habitat") #save diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R new file mode 100644 index 0000000..f7560d6 --- /dev/null +++ b/R/UTILS.get_observed_distribution.R @@ -0,0 +1,190 @@ +### HEADER ##################################################################### +##' +##' @title Compute distribution of relative abundance over observed relevés +##' +##' @name get.observed.distribution +##' +##' @author Matthieu Combaud, Maxime Delprat +##' +##' @description This script is designed to compute distribution, per PFG/strata/habitat, +##' of relative abundance, from observed data. +##' +##' @param name.simulation simulation folder name. +##' @param obs.path the function needs observed data, please create a folder for them in your +##' simulation folder and then indicate in this parameter the access path to this new folder. +##' @param releves.PFG file which contain the observed Braund-Blanquet abundance at each site +##' and each PFG and strata. +##' @param releves.sites file which contain coordinates and a description of +##' the habitat associated with the dominant species of each site in the studied map. +##' @param hab.obs raster map of the extended studied area in the simulation. +##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered +##' in the validation. +##' @param strata.considered_PFG.compo a character vector of the list of precise +##' strata considered in the validation. +##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) +##' considered in the validation. +##' @param perStrata.compo Logical. All strata together (FALSE) or per strata (TRUE). +##' @param sim.version name of the simulation we want to validate (it works with +##' only one \code{sim.version}). +##' +##' @details +##' +##' The function takes the \code{releves.PFG} and \code{releves.sites} files and +##' aggregate coverage per PFG. Then, the code get habitat information from also +##' the \code{hab.obs} map, keep only interesting habitat, strata and PFG, and +##' transform the data into relative metrics. Finally, the script computes distribution +##' per PFG, and if require per strata/habitat (else all strata/habitat will be considered together). +##' +##' @return +##' +##' 2 files are created in +##' \describe{ +##' \item{\file{VALIDATION/PFG_COMPOSITION/sim.version} : +##' 1 .csv file which contain the observed relevés transformed into relative metrics. +##' 1 .csv file which contain the final output with the distribution per PFG, strata and habitat. +##' +##' @export +##' +##' @importFrom dplyr filter select filter group_by mutate %>% rename +##' @importFrom raster aggregate compareCRS res crs +##' @importFrom sf st_transform st_crop +##' @importFrom utils write.csv +##' @importFrom data.table setDT +##' +### END OF HEADER ############################################################## + + +get.observed.distribution<-function(name.simulation + , obs.path + , releves.PFG + , releves.sites + , hab.obs + , PFG.considered_PFG.compo + , strata.considered_PFG.compo + , habitat.considered_PFG.compo + , perStrata.compo + , sim.version){ + + composition.mask = NULL + output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) + dir.create(file.path(output.path), recursive = TRUE, showWarnings = FALSE) + + #1. Aggregate coverage per PFG + ######################################### + + #identify sites with wrong BB values (ie values that cannot be converted by the PRE_FATE.abundBraunBlanquet function) + releves.PFG<-filter(releves.PFG,is.element(BB,c(NA, "NA", 0, "+", "r", 1:5))) + + #transformation into coverage percentage + releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage + + if(perStrata.compo==T){ + aggregated.releves.PFG<-aggregate(coverage~site+PFG+strata,data=releves.PFG,FUN="sum") + }else if(perStrata.compo==F){ + aggregated.releves.PFG<-aggregate(coverage~site+PFG,data=releves.PFG,FUN="sum") + aggregated.releves.PFG$strata<-"A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) + } + + + #2. Get habitat information + ################################### + + #get sites coordinates + aggregated.releves.PFG<-merge(dplyr::select(releves.sites,c(site)),aggregated.releves.PFG,by="site") + + #get habitat code and name + if(compareCRS(aggregated.releves.PFG,hab.obs)){ + aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) + }else{ + aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(hab.obs)) + aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) + } + + #correspondance habitat code/habitat name + table.habitat.releve<-levels(hab.obs)[[1]] + + aggregated.releves.PFG<-merge(aggregated.releves.PFG,dplyr::select(table.habitat.releve,c(ID,habitat)),by.x="code.habitat",by.y="ID") + + #(optional) keep only releves data in a specific area + if(!is.null(composition.mask)){ + + if(compareCRS(aggregated.releves.PFG,composition.mask)==F){ #as this stage it is not a problem to transform crs(aggregated.releves.PFG) since we have no more merge to do (we have already extracted habitat info from the map) + aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(composition.mask)) + } + + aggregated.releves.PFG<-st_crop(x=aggregated.releves.PFG,y=composition.mask) + print("'releve' map has been cropped to match 'external.training.mask'.") + } + + + # 3. Keep only releve on interesting habitat, strata and PFG + ##################################################################" + + aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG) + aggregated.releves.PFG<-dplyr::select(aggregated.releves.PFG,c(site,PFG,strata,coverage,habitat)) + + aggregated.releves.PFG<-filter( + aggregated.releves.PFG, + is.element(PFG,PFG.considered_PFG.compo)& + is.element(strata,strata.considered_PFG.compo)& + is.element(habitat,habitat.considered_PFG.compo) + ) + + + #4.Transform into a relative metrics (here relative.metric is relative coverage) + ################################################################################### + + #important to do it only here, because if we filter some PFG, it changes the value of the relative metric + #careful: if several strata are selected, the computation is made for each strata separately + aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG %>% group_by(site,strata) %>% mutate(relative.metric= round(prop.table(coverage),digits = 2))) + aggregated.releves.PFG$relative.metric[is.na(aggregated.releves.PFG$relative.metric)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) + aggregated.releves.PFG$coverage<-NULL + + print("releve data have been transformed into a relative metric") + + + # 5. Save data + ##################### + write.csv(aggregated.releves.PFG,paste0(output.path,"/CBNA.releves.prepared.csv"),row.names = F) + + + # 6. Compute distribution per PFG, and if require per strata/habitat (else all strata/habitat will be considered together) + #################################### + + distribution<-setDT(aggregated.releves.PFG)[, quantile(relative.metric), by=c("PFG","habitat","strata")] + distribution<-rename(distribution,"quantile"="V1") + distribution<-data.frame(distribution,rank=seq(0,5,1)) #to be able to sort on quantile + + # 7. Add the missing PFG*habitat*strata + #final distribution is the distribution once the missing combination have been added. For these combination, all quantiles are set to 0 + + observed.distribution<-expand.grid( + PFG=PFG.considered_PFG.compo, + habitat=habitat.considered_PFG.compo, + strata=strata.considered_PFG.compo + ) + + null.quantile<-data.frame(rank=seq(0,4,1)) #to have 5 rows per PFG*strata*habitat + observed.distribution<-merge(observed.distribution,null.quantile,all=T) + + observed.distribution<-merge(observed.distribution,distribution,by=c("PFG","habitat","strata","rank"),all.x=T) # "all.x=T" to keep the unobserved combination + + observed.distribution$quantile[is.na(observed.distribution$quantile)]<-0 + + # 8. Order the table to be able to have output in the right format + observed.distribution<-setDT(observed.distribution) + observed.distribution<-observed.distribution[order(habitat,strata,PFG,rank)] + + observed.distribution<-rename(observed.distribution,"observed.quantile"="quantile") + + + # 9. Save results + ########################################## + write.csv(observed.distribution,paste0(output.path,"/observed.distribution.csv"),row.names = F) + + # 8. Return + #################### + + return(observed.distribution) + +} diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 0504edb..266d0ca 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -35,12 +35,13 @@ ##' @export ##' ##' @importFrom dplyr select all_of -##' @importFrom data.table melt rename +##' @importFrom data.table rename ##' @importFrom utils write.csv ##' @importFrom raster raster crs extent res ratify writeRaster ##' @importFrom stats complete.cases ##' @importFrom ggplot2 ggplot geom_raster coord_equal scale_fill_manual ##' ggtitle guides theme ggsave +##' @importFrom reshape2 melt ##' ### END OF HEADER ############################################################## diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index ba833e0..885e6be 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -173,7 +173,7 @@ train.RF.habitat<-function(releves.PFG #run optimization algo (careful : optimization over OOB...) mtry.perf<-as.data.frame( tuneRF( - x=select(releves.training,-c(code.habitat,site,habitat,geometry)), + x=dplyr::select(releves.training,-c(code.habitat,site,habitat,geometry)), y=releves.training$habitat, strata=releves.training$habitat, sampsize=nrow(releves.training), @@ -187,9 +187,9 @@ train.RF.habitat<-function(releves.PFG #run real model model<- randomForest( - x=select(releves.training,-c(code.habitat,site,habitat,geometry)), + x=dplyr::select(releves.training,-c(code.habitat,site,habitat,geometry)), y=releves.training$habitat, - xtest=select(releves.testing,-c(code.habitat,site,habitat,geometry)), + xtest=dplyr::select(releves.testing,-c(code.habitat,site,habitat,geometry)), ytest=releves.testing$habitat, strata=releves.training$habitat, sampsize=nrow(releves.training), From 91f44dc67e0933bcfc4198a70b0dd5d91f78378c Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:18:43 +0100 Subject: [PATCH 043/176] Add files via upload Creation of a new POST_FATE.validation & updates of utils functions associated --- man/POST_FATE.validation.Rd | 159 +++++++++++++++++++++++++++ man/do.PFG.composition.validation.Rd | 69 ++++++++++++ man/get.observed.distribution.Rd | 64 +++++++++++ 3 files changed, 292 insertions(+) create mode 100644 man/POST_FATE.validation.Rd create mode 100644 man/do.PFG.composition.validation.Rd create mode 100644 man/get.observed.distribution.Rd diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd new file mode 100644 index 0000000..6a9e87f --- /dev/null +++ b/man/POST_FATE.validation.Rd @@ -0,0 +1,159 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/POST_FATE.validation.R +\name{POST_FATE.validation} +\alias{POST_FATE.validation} +\title{Computes validation data for habitat, PFG richness and composition for a \code{FATE} simulation.} +\usage{ +POST_FATE.validation( + name.simulation, + sim.version, + year, + doHabitat = TRUE, + obs.path, + releves.PFG, + releves.sites, + hab.obs, + validation.mask, + studied.habitat = NULL, + doComposition = TRUE, + PFG.considered_PFG.compo, + habitat.considered_PFG.compo, + doRichness = TRUE, + list.PFG, + exclude.PFG = NULL +) +} +\arguments{ +\item{name.simulation}{simulation folder name.} + +\item{sim.version}{name of the simulation to validate (it works with only one \code{sim.version}).} + +\item{year}{year of simulation for validation.} + +\item{doHabitat}{logical. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, +if \code{FALSE}, habitat validation module is disabled.} + +\item{obs.path}{the function needs observed data, please create a folder for them in your +simulation folder and then indicate in this parameter the access path to this new folder (habitat & PFG composition validation).} + +\item{releves.PFG}{name of file which contain the observed Braund-Blanquet abundance at each site +and each PFG and strata (habitat & PFG composition validation).} + +\item{hab.obs}{name of the file which contain the extended studied map in the simulation (habitat & PFG composition validation).} + +\item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation +(habitat & PFG composition validation).} + +\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will +take into account of all habitats in the \code{hab.obs} map. Otherwise, please specify +in a vector habitats that will be take into account for the validation (habitat validation).} + +\item{doComposition}{logical. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, +if \code{FALSE}, PFG composition validation module is disabled.} + +\item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered +in the validation (PFG composition validation).} + +\item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) +considered in the validation (PFG composition validation).} + +\item{doRichness}{logical. Default \code{TRUE}. If \code{TRUE}, PFG richness validation module is activated, +if \code{FALSE}, PFG richness validation module is disabled.} + +\item{list.PFG}{a character vector which contain all the PFGs taken account in +the simulation and observed in the simulation area (PFG richness validation).} + +\item{exclude.PFG}{default \code{NULL}. A character vector containing the names +of the PFG you want to exclude from the analysis (PFG richness validation).} + +\item{releves.site}{name of the file which contain coordinates and a description of +the habitat associated with the dominant species of each site in the studied map (habitat & PFG composition validation).} +} +\value{ + +} +\description{ +This script is designed to compute validation data for : +\code{Habitat} : compares habitat simulations and observations and +create a map to visualize this comparison with all the \code{FATE} and +observed data. +\code{PFG Composition} : produced a computation of observed distribution +of relative abundance in the simulation area and a computation of distance between +observed and simulated distribution. +\code{PFG Richness} : computes the PFG richness over the whole simulation area +for a \code{FATE} simulation and computes the difference between observed and simulated PFG richness. +} +\details{ +\describe{ + \item{Habitat validation}{The observed habitat is derived from the cesbio map, the simulated habitat +is derived from FATE simulated relative abundance, based on a random forest +algorithm trained on CBNA data. To compare observations and simulations, the function +compute confusion matrix between observation and prediction and then compute the TSS +for each habitat h (number of prediction of habitat h/number of observation +of habitat h + number of non-prediction of habitat h/number of non-observation +of habitat h). The final metrics this script use is the mean of TSS per habitat over all +habitats, weighted by the share of each habitat in the observed habitat distribution.} + \item{PFG composition validation}{This code firstly run the \code{get.observed.distribution} function in order to have +a \code{obs.distri} file which contain the observed distribution per PFG, strata and habitat. +This file is also an argument for the \code{do.PFG.composition.validation} function run next. +This second sub function provide the computation of distance between observed and simulated distribution. \cr +NB : The argument \code{strata.considered_PFG.compo} is by default "A" in the 2 sub functions because +it's easier for a \code{FATE} simulation to provide PFG abundances for all strata. \cr The argument +\code{perStrata.compo} is by default \code{NULL} for the same reasons.} + \item{PFG richness validation}{Firstly, the function updates the \code{list.PFG} with \code{exclude.PFG} vector. +Then, the script takes the abundance per PFG file from the results of the \code{FATE} +simulation and computes the difference between the \code{list.PFG} and all the PFG +which are presents in the abundance file, in order to obtain the PFG richness for a simulation. +The function also determine if an observed PFG is missing in the results of the simulation at +a specific year.} +} +} +\examples{ + +## Habitat validation --------------------------------------------------------------------------------- +POST_FATE.validation(name.simulation = "FATE_Champsaur" + , sim.version = "SIMUL_V4.1" + , year = 2000 + , doHabitat = TRUE + , obs.path = "FATE_Champsaur/DATA_OBS/" + , releves.PFG = "releves.PFG.abundance.csv" + , releves.sites = "releves.sites.shp" + , hab.obs = "simplified.cesbio.map.grd" + , validation.mask = "certain.habitat.100m.restricted.grd" + , studied.habitat = NULL + , doComposition = FALSE + , doRichness = FALSE) + +## PFG composition validation -------------------------------------------------------------------------- +list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") +POST_FATE.validation(name.simulation = "FATE_Champsaur" + , sim.version = "SIMUL_V4.1" + , year = 2000 + , doHabitat = FALSE + , obs.path = "FATE_Champsaur/DATA_OBS/" + , releves.PFG = "releves.PFG.abundance.csv" + , releves.sites = "releves.sites.shp" + , hab.obs = "simplified.cesbio.map.grd" + , validation.mask = "certain.habitat.100m.restricted.grd" + , studied.habitat = NULL + , doComposition = TRUE + , PFG.considered_PFG.compo = list.PFG + , habitat.considered_PFG.compo = habitat.considered + , doRichness = FALSE) + +## PFG richness validation ----------------------------------------------------------------------------- +list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +POST_FATE.validation(name.simulation = "FATE_CHampsaur" + , sim.version = "SIMUL_V4.1" + , year = 2000 + , doHabitat = FALSE + , doComposition = FALSE + , doRichness = TRUE + , list.PFG = list.PFG + , exclude.PFG = NULL) + +} +\author{ +Matthieu Combaud, Maxime Delprat +} diff --git a/man/do.PFG.composition.validation.Rd b/man/do.PFG.composition.validation.Rd new file mode 100644 index 0000000..f4a934b --- /dev/null +++ b/man/do.PFG.composition.validation.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/UTILS.do_PFG_composition_validation.R +\name{do.PFG.composition.validation} +\alias{do.PFG.composition.validation} +\title{Compute distance between observed and simulated distribution} +\usage{ +\method{do}{PFG.composition.validation}( + name.simulation, + obs.path, + sim.version, + hab.obs, + PFG.considered_PFG.compo, + strata.considered_PFG.compo, + habitat.considered_PFG.compo, + observed.distribution, + perStrata.compo, + validation.mask, + year +) +} +\arguments{ +\item{name.simulation}{simulation folder name.} + +\item{obs.path}{the function needs observed data, please create a folder for them in your +simulation folder and then indicate in this parameter the access path to this new folder.} + +\item{sim.version}{name of the simulation we want to validate (it works with +only one \code{sim.version}).} + +\item{hab.obs}{file which contain the extended studied map in the simulation.} + +\item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered +in the validation.} + +\item{strata.considered_PFG.compo}{a character vector of the list of precise +strata considered in the validation.} + +\item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) +considered in the validation.} + +\item{observed.distribution}{PFG observed distribution table.} + +\item{perStrata.compo}{Logical. All strata together (FALSE) or per strata (TRUE).} + +\item{validation.mask}{file which contain a raster mask that specified +which pixels need validation.} + +\item{year}{year of simulation to validate.} +} +\value{ + +} +\description{ +This script is designed to compare the difference between the +PFG distribution in observed and simulated data. For a set of PFG, strata and +habitats chosen, the function compute distance between observed and simulated +distribution for a precise \code{FATE} simulation. +} +\details{ +After preliminary checks, this code extract observed habitat from the \code{hab.obs} +map and, then, merge it with the simulated PFG abundance file from results of a \code{FATE} +simulation. After filtration of the required PFG, strata and habitats, the function +transform the data into relative metrics and, then, compute distribution per PFG, strata +and habitat (if necessary). Finally, the code computes proximity between observed +and simulated data, per PFG, strata and habitat. +} +\author{ +Matthieu Combaud, Maxime Delprat +} diff --git a/man/get.observed.distribution.Rd b/man/get.observed.distribution.Rd new file mode 100644 index 0000000..6d0c1c8 --- /dev/null +++ b/man/get.observed.distribution.Rd @@ -0,0 +1,64 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/UTILS.get_observed_distribution.R +\name{get.observed.distribution} +\alias{get.observed.distribution} +\title{Compute distribution of relative abundance over observed relevés} +\usage{ +get.observed.distribution( + name.simulation, + obs.path, + releves.PFG, + releves.sites, + hab.obs, + PFG.considered_PFG.compo, + strata.considered_PFG.compo, + habitat.considered_PFG.compo, + perStrata.compo, + sim.version +) +} +\arguments{ +\item{name.simulation}{simulation folder name.} + +\item{obs.path}{the function needs observed data, please create a folder for them in your +simulation folder and then indicate in this parameter the access path to this new folder.} + +\item{releves.PFG}{file which contain the observed Braund-Blanquet abundance at each site +and each PFG and strata.} + +\item{releves.sites}{file which contain coordinates and a description of +the habitat associated with the dominant species of each site in the studied map.} + +\item{hab.obs}{raster map of the extended studied area in the simulation.} + +\item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered +in the validation.} + +\item{strata.considered_PFG.compo}{a character vector of the list of precise +strata considered in the validation.} + +\item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) +considered in the validation.} + +\item{perStrata.compo}{Logical. All strata together (FALSE) or per strata (TRUE).} + +\item{sim.version}{name of the simulation we want to validate (it works with +only one \code{sim.version}).} +} +\value{ + +} +\description{ +This script is designed to compute distribution, per PFG/strata/habitat, +of relative abundance, from observed data. +} +\details{ +The function takes the \code{releves.PFG} and \code{releves.sites} files and +aggregate coverage per PFG. Then, the code get habitat information from also +the \code{hab.obs} map, keep only interesting habitat, strata and PFG, and +transform the data into relative metrics. Finally, the script computes distribution +per PFG, and if require per strata/habitat (else all strata/habitat will be considered together). +} +\author{ +Matthieu Combaud, Maxime Delprat +} From 7612aed51c997f46c3f9d2f05fdbbf3d9420c183 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:19:50 +0100 Subject: [PATCH 044/176] Add files via upload Creation of a new POST_FATE.validation & updates of utils functions associated --- docs/reference/POST_FATE.validation.html | 338 ++++++++++++++++++ .../do.PFG.composition.validation.html | 236 ++++++++++++ docs/reference/get.observed.distribution.html | 228 ++++++++++++ 3 files changed, 802 insertions(+) create mode 100644 docs/reference/POST_FATE.validation.html create mode 100644 docs/reference/do.PFG.composition.validation.html create mode 100644 docs/reference/get.observed.distribution.html diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html new file mode 100644 index 0000000..450d8ff --- /dev/null +++ b/docs/reference/POST_FATE.validation.html @@ -0,0 +1,338 @@ + +Computes validation data for habitat, PFG richness and composition for a FATE simulation. — POST_FATE.validation • RFate + + +
+
+ + + +
+
+ + +
+

This script is designed to compute validation data for : +Habitat : compares habitat simulations and observations and +create a map to visualize this comparison with all the FATE and +observed data. +PFG Composition : produced a computation of observed distribution +of relative abundance in the simulation area and a computation of distance between +observed and simulated distribution. +PFG Richness : computes the PFG richness over the whole simulation area +for a FATE simulation and computes the difference between observed and simulated PFG richness.

+
+ +
+
POST_FATE.validation(
+  name.simulation,
+  sim.version,
+  year,
+  doHabitat = TRUE,
+  obs.path,
+  releves.PFG,
+  releves.sites,
+  hab.obs,
+  validation.mask,
+  studied.habitat = NULL,
+  doComposition = TRUE,
+  PFG.considered_PFG.compo,
+  habitat.considered_PFG.compo,
+  doRichness = TRUE,
+  list.PFG,
+  exclude.PFG = NULL
+)
+
+ +
+

Arguments

+
name.simulation
+

simulation folder name.

+
sim.version
+

name of the simulation to validate (it works with only one sim.version).

+
year
+

year of simulation for validation.

+
doHabitat
+

logical. Default TRUE. If TRUE, habitat validation module is activated, +if FALSE, habitat validation module is disabled.

+
obs.path
+

the function needs observed data, please create a folder for them in your +simulation folder and then indicate in this parameter the access path to this new folder (habitat & PFG composition validation).

+
releves.PFG
+

name of file which contain the observed Braund-Blanquet abundance at each site +and each PFG and strata (habitat & PFG composition validation).

+
hab.obs
+

name of the file which contain the extended studied map in the simulation (habitat & PFG composition validation).

+
validation.mask
+

name of the file which contain a raster mask that specified which pixels need validation +(habitat & PFG composition validation).

+
studied.habitat
+

default NULL. If NULL, the function will +take into account of all habitats in the hab.obs map. Otherwise, please specify +in a vector habitats that will be take into account for the validation (habitat validation).

+
doComposition
+

logical. Default TRUE. If TRUE, PFG composition validation module is activated, +if FALSE, PFG composition validation module is disabled.

+
PFG.considered_PFG.compo
+

a character vector of the list of PFG considered +in the validation (PFG composition validation).

+
habitat.considered_PFG.compo
+

a character vector of the list of habitat(s) +considered in the validation (PFG composition validation).

+
doRichness
+

logical. Default TRUE. If TRUE, PFG richness validation module is activated, +if FALSE, PFG richness validation module is disabled.

+
list.PFG
+

a character vector which contain all the PFGs taken account in +the simulation and observed in the simulation area (PFG richness validation).

+
exclude.PFG
+

default NULL. A character vector containing the names +of the PFG you want to exclude from the analysis (PFG richness validation).

+
releves.site
+

name of the file which contain coordinates and a description of +the habitat associated with the dominant species of each site in the studied map (habitat & PFG composition validation).

+
+
+

Value

+ +
+
+

Details

+ +
Habitat validation
+

The observed habitat is derived from the cesbio map, the simulated habitat +is derived from FATE simulated relative abundance, based on a random forest +algorithm trained on CBNA data. To compare observations and simulations, the function +compute confusion matrix between observation and prediction and then compute the TSS +for each habitat h (number of prediction of habitat h/number of observation +of habitat h + number of non-prediction of habitat h/number of non-observation +of habitat h). The final metrics this script use is the mean of TSS per habitat over all +habitats, weighted by the share of each habitat in the observed habitat distribution.

+ +
PFG composition validation
+

This code firstly run the get.observed.distribution function in order to have +a obs.distri file which contain the observed distribution per PFG, strata and habitat. +This file is also an argument for the do.PFG.composition.validation function run next. +This second sub function provide the computation of distance between observed and simulated distribution.
+NB : The argument strata.considered_PFG.compo is by default "A" in the 2 sub functions because +it's easier for a FATE simulation to provide PFG abundances for all strata.
The argument +perStrata.compo is by default NULL for the same reasons.

+ +
PFG richness validation
+

Firstly, the function updates the list.PFG with exclude.PFG vector. +Then, the script takes the abundance per PFG file from the results of the FATE +simulation and computes the difference between the list.PFG and all the PFG +which are presents in the abundance file, in order to obtain the PFG richness for a simulation. +The function also determine if an observed PFG is missing in the results of the simulation at +a specific year.

+ + +
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+

Examples

+

+## Habitat validation ---------------------------------------------------------------------------------
+POST_FATE.validation(name.simulation = "FATE_Champsaur"
+                      , sim.version = "SIMUL_V4.1"
+                      , year = 2000
+                      , doHabitat = TRUE
+                      , obs.path = "FATE_Champsaur/DATA_OBS/"
+                      , releves.PFG = "releves.PFG.abundance.csv"
+                      , releves.sites = "releves.sites.shp"
+                      , hab.obs = "simplified.cesbio.map.grd"
+                      , validation.mask = "certain.habitat.100m.restricted.grd"
+                      , studied.habitat = NULL
+                      , doComposition = FALSE
+                      , doRichness = FALSE)
+                      
+## PFG composition validation --------------------------------------------------------------------------
+list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5"))
+habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland")
+POST_FATE.validation(name.simulation = "FATE_Champsaur"
+                      , sim.version = "SIMUL_V4.1"
+                      , year = 2000
+                      , doHabitat = FALSE
+                      , obs.path = "FATE_Champsaur/DATA_OBS/"
+                      , releves.PFG = "releves.PFG.abundance.csv"
+                      , releves.sites = "releves.sites.shp"
+                      , hab.obs = "simplified.cesbio.map.grd"
+                      , validation.mask = "certain.habitat.100m.restricted.grd"
+                      , studied.habitat = NULL
+                      , doComposition = TRUE
+                      , PFG.considered_PFG.compo = list.PFG
+                      , habitat.considered_PFG.compo = habitat.considered
+                      , doRichness = FALSE)
+                      
+## PFG richness validation -----------------------------------------------------------------------------
+list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5"))
+POST_FATE.validation(name.simulation = "FATE_CHampsaur"
+                      , sim.version = "SIMUL_V4.1"
+                      , year = 2000
+                      , doHabitat = FALSE
+                      , doComposition = FALSE
+                      , doRichness = TRUE
+                      , list.PFG = list.PFG
+                      , exclude.PFG = NULL)
+
+
+
+
+ +
+ + +
+ + + + + + + + diff --git a/docs/reference/do.PFG.composition.validation.html b/docs/reference/do.PFG.composition.validation.html new file mode 100644 index 0000000..b2a9b5c --- /dev/null +++ b/docs/reference/do.PFG.composition.validation.html @@ -0,0 +1,236 @@ + +Compute distance between observed and simulated distribution — do.PFG.composition.validation • RFate + + +
+
+ + + +
+
+ + +
+

This script is designed to compare the difference between the +PFG distribution in observed and simulated data. For a set of PFG, strata and +habitats chosen, the function compute distance between observed and simulated +distribution for a precise FATE simulation.

+
+ +
+
# S3 method for PFG.composition.validation
+do(
+  name.simulation,
+  obs.path,
+  sim.version,
+  hab.obs,
+  PFG.considered_PFG.compo,
+  strata.considered_PFG.compo,
+  habitat.considered_PFG.compo,
+  observed.distribution,
+  perStrata.compo,
+  validation.mask,
+  year
+)
+
+ +
+

Arguments

+
name.simulation
+

simulation folder name.

+
obs.path
+

the function needs observed data, please create a folder for them in your +simulation folder and then indicate in this parameter the access path to this new folder.

+
sim.version
+

name of the simulation we want to validate (it works with +only one sim.version).

+
hab.obs
+

file which contain the extended studied map in the simulation.

+
PFG.considered_PFG.compo
+

a character vector of the list of PFG considered +in the validation.

+
strata.considered_PFG.compo
+

a character vector of the list of precise +strata considered in the validation.

+
habitat.considered_PFG.compo
+

a character vector of the list of habitat(s) +considered in the validation.

+
observed.distribution
+

PFG observed distribution table.

+
perStrata.compo
+

Logical. All strata together (FALSE) or per strata (TRUE).

+
validation.mask
+

file which contain a raster mask that specified +which pixels need validation.

+
year
+

year of simulation to validate.

+
+
+

Value

+ +
+
+

Details

+

After preliminary checks, this code extract observed habitat from the hab.obs +map and, then, merge it with the simulated PFG abundance file from results of a FATE +simulation. After filtration of the required PFG, strata and habitats, the function +transform the data into relative metrics and, then, compute distribution per PFG, strata +and habitat (if necessary). Finally, the code computes proximity between observed +and simulated data, per PFG, strata and habitat.

+
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/docs/reference/get.observed.distribution.html b/docs/reference/get.observed.distribution.html new file mode 100644 index 0000000..94f4e09 --- /dev/null +++ b/docs/reference/get.observed.distribution.html @@ -0,0 +1,228 @@ + +Compute distribution of relative abundance over observed relevés — get.observed.distribution • RFate + + +
+
+ + + +
+
+ + +
+

This script is designed to compute distribution, per PFG/strata/habitat, +of relative abundance, from observed data.

+
+ +
+
get.observed.distribution(
+  name.simulation,
+  obs.path,
+  releves.PFG,
+  releves.sites,
+  hab.obs,
+  PFG.considered_PFG.compo,
+  strata.considered_PFG.compo,
+  habitat.considered_PFG.compo,
+  perStrata.compo,
+  sim.version
+)
+
+ +
+

Arguments

+
name.simulation
+

simulation folder name.

+
obs.path
+

the function needs observed data, please create a folder for them in your +simulation folder and then indicate in this parameter the access path to this new folder.

+
releves.PFG
+

file which contain the observed Braund-Blanquet abundance at each site +and each PFG and strata.

+
releves.sites
+

file which contain coordinates and a description of +the habitat associated with the dominant species of each site in the studied map.

+
hab.obs
+

raster map of the extended studied area in the simulation.

+
PFG.considered_PFG.compo
+

a character vector of the list of PFG considered +in the validation.

+
strata.considered_PFG.compo
+

a character vector of the list of precise +strata considered in the validation.

+
habitat.considered_PFG.compo
+

a character vector of the list of habitat(s) +considered in the validation.

+
perStrata.compo
+

Logical. All strata together (FALSE) or per strata (TRUE).

+
sim.version
+

name of the simulation we want to validate (it works with +only one sim.version).

+
+
+

Value

+ +
+
+

Details

+

The function takes the releves.PFG and releves.sites files and +aggregate coverage per PFG. Then, the code get habitat information from also +the hab.obs map, keep only interesting habitat, strata and PFG, and +transform the data into relative metrics. Finally, the script computes distribution +per PFG, and if require per strata/habitat (else all strata/habitat will be considered together).

+
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + From fc92bae21e5215938388b6ee0fc8236297f98180 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 11:40:17 +0100 Subject: [PATCH 045/176] Add files via upload correction of import errors --- NAMESPACE | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index acd8cd2..1b9bd0a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,9 +1,6 @@ # Generated by roxygen2: do not edit by hand -S3method(do,PFG.composition.validation) -S3method(do,habitat.validation) S3method(plot,predicted.habitat) -S3method(train,RF.habitat) export(.adaptMaps) export(.cropMaps) export(.getCutoff) @@ -58,9 +55,12 @@ export(betapart.core) export(cluster.stats) export(designLHDNorm) export(divLeinster) +export(do.PFG.composition.validation) +export(do.habitat.validation) export(dunn) export(ecospat.kd) export(get.observed.distribution) +export(train.RF.habitat) importFrom(FD,gowdis) importFrom(PresenceAbsence,auc) importFrom(PresenceAbsence,cmx) @@ -90,7 +90,6 @@ importFrom(data.table,dcast) importFrom(data.table,fread) importFrom(data.table,fwrite) importFrom(data.table,rbindlist) -importFrom(data.table,rename) importFrom(data.table,setDT) importFrom(doParallel,registerDoParallel) importFrom(dplyr,"%>%") @@ -169,6 +168,7 @@ importFrom(grid,unit) importFrom(gridExtra,grid.arrange) importFrom(huge,huge.npn) importFrom(methods,as) +importFrom(parallel,detectCores) importFrom(parallel,mclapply) importFrom(phyloclim,niche.overlap) importFrom(randomForest,randomForest) @@ -228,7 +228,6 @@ importFrom(stats,var) importFrom(stats,weighted.mean) importFrom(stringr,str_split) importFrom(stringr,str_sub) -importFrom(tidyverse,write_rds) importFrom(utils,combn) importFrom(utils,download.file) importFrom(utils,install.packages) From 8a827f98c961250f087571b68769378df3cec6d6 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 11:42:21 +0100 Subject: [PATCH 046/176] Add files via upload correction of packages imports mistakes --- R/POST_FATE.validation.R | 3 +++ R/PRE_FATE.skeletonDirectory.R | 6 ++++++ R/UTILS.do_habitat_validation.R | 4 +++- R/UTILS.plot_predicted_habitat.R | 3 +-- R/UTILS.train_RF_habitat.R | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index dd85f77..326bcea 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -147,6 +147,8 @@ ##' @foreach foreach foreach %dopar% ##' @importFrom forcats fct_expand ##' @importFrom readr write_rds +##' @importFrom doParallel registerDoParallel +##' @importFrom parallel detectCores ##' ### END OF HEADER ################################################################### @@ -302,6 +304,7 @@ POST_FATE.validation = function(name.simulation #list of PFG of interest list.PFG<-setdiff(list.PFG,exclude.PFG) + registerDoParallel(detectCores()-2) dying.PFG.list<-foreach(i=1:length(sim.version)) %dopar% { simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) diff --git a/R/PRE_FATE.skeletonDirectory.R b/R/PRE_FATE.skeletonDirectory.R index 919f745..a243359 100644 --- a/R/PRE_FATE.skeletonDirectory.R +++ b/R/PRE_FATE.skeletonDirectory.R @@ -75,6 +75,10 @@ ##' \describe{ ##' \item{\code{HABITAT}}{this folder will collect all the validation files produces ##' by the function POST_FATE.validation.habitat} +##' \item{\code{PFG_RICHNESS}}{this folder will collect all the validation files produces +##' by the function POST_FATE.validation_PFG_richness} +##' \item{\code{PFG_COMPOSITION}}{this folder will collect all the validation files produces +##' by the function POST_FATE.validation_PFG_composition} ##' } ##' } ##' } @@ -146,6 +150,8 @@ PRE_FATE.skeletonDirectory = function(name.simulation = "FATE_simulation") ## the VALIDATION dir dir.create(file.path(name.simulation, "VALIDATION"), showWarnings = FALSE) dir.create(file.path(name.simulation, "VALIDATION", "HABITAT"), showWarnings = FALSE) + dir.create(file.path(name.simulation, "VALIDATION", "PFG_RICHNESS"), showWarnings = FALSE) + dir.create(file.path(name.simulation, "VALIDATION", "PFG_COMPOSITION"), showWarnings = FALSE) message("\n Your directory tree for your FATE simulation (" , name.simulation, ") is ready!\n") diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 7321724..b080596 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -36,7 +36,7 @@ ##' database by extracting the observed habitat from a raster map. Then, for each ##' simulations (sim.version), the script take the evolution abundance for each PFG ##' and all strata file and predict the habitat for the whole map (if option selected) -##' thanks to the RF model.Finally, the function compute habitat performance based on +##' thanks to the RF model. Finally, the function computes habitat performance based on ##' TSS for each habitat. ##' ##' @return @@ -56,6 +56,8 @@ ##' @importFrom reshape2 dcast ##' @importFrom caret confusionMatrix ##' @importFrom utils write.csv +##' @importFrom doParallel registerDoParallel +##' @importFrom parallel detectCores ##' ### END OF HEADER ############################################################## diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 266d0ca..e5903e4 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -34,8 +34,7 @@ ##' ##' @export ##' -##' @importFrom dplyr select all_of -##' @importFrom data.table rename +##' @importFrom dplyr select all_of rename ##' @importFrom utils write.csv ##' @importFrom raster raster crs extent res ratify writeRaster ##' @importFrom stats complete.cases diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 885e6be..6276839 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -56,7 +56,7 @@ ##' @importFrom sf st_transform st_crop st_write ##' @importFrom randomForest randomForest tuneRF ##' @importFrom caret confusionMatrix -##' @importFrom tidyverse write_rds +##' @importFrom readr write_rds ##' @importFrom utils read.csv ##' ### END OF HEADER ############################################################## From 4a5b3f29e3638f44ff029a420682419882fb6a8f Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 11:43:25 +0100 Subject: [PATCH 047/176] Add files via upload correction of mistakes in header --- man/do.habitat.validation.Rd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/do.habitat.validation.Rd b/man/do.habitat.validation.Rd index cccd6f2..28c246f 100644 --- a/man/do.habitat.validation.Rd +++ b/man/do.habitat.validation.Rd @@ -5,7 +5,7 @@ \title{Compare observed and simulated habitat of a \code{FATE} simulation at the last simulation year.} \usage{ -\method{do}{habitat.validation}( +do.habitat.validation( output.path, RF.model, habitat.FATE.map, @@ -64,7 +64,7 @@ After several preliminary checks, the function is going to prepare the observati database by extracting the observed habitat from a raster map. Then, for each simulations (sim.version), the script take the evolution abundance for each PFG and all strata file and predict the habitat for the whole map (if option selected) -thanks to the RF model.Finally, the function compute habitat performance based on +thanks to the RF model. Finally, the function computes habitat performance based on TSS for each habitat. } \author{ From 7f70f924a396613773946f3e241ccb892cd6db9f Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 4 Mar 2022 11:45:13 +0100 Subject: [PATCH 048/176] Add files via upload Correction of small mistakes --- docs/do.habitat.validation.html | 239 ++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 docs/do.habitat.validation.html diff --git a/docs/do.habitat.validation.html b/docs/do.habitat.validation.html new file mode 100644 index 0000000..b33aa00 --- /dev/null +++ b/docs/do.habitat.validation.html @@ -0,0 +1,239 @@ + +Compare observed and simulated habitat of a FATE simulation +at the last simulation year. — do.habitat.validation • RFate + + +
+
+ + + +
+
+ + +
+

To compare observations and simulations, this function compute +confusion matrix between observation and prediction and then compute the TSS +for each habitat.

+
+ +
+
do.habitat.validation(
+  output.path,
+  RF.model,
+  habitat.FATE.map,
+  validation.mask,
+  simulation.map,
+  predict.all.map,
+  sim.version,
+  name.simulation,
+  perStrata,
+  hab.obs,
+  year
+)
+
+ +
+

Arguments

+
output.path
+

access path to the for the folder where output files +will be created.

+
RF.model
+

random forest model trained on CBNA data (train.RF.habitat +function)

+
habitat.FATE.map
+

a raster map of the observed habitat in the +studied area.

+
validation.mask
+

a raster mask that specified which pixels need validation.

+
simulation.map
+

a raster map of the whole studied area use to check +the consistency between simulation map and the observed habitat map.

+
predict.all.map
+

a TRUE/FALSE vector. If TRUE, the script will predict +habitat for the whole map.

+
sim.version
+

name of the simulation to validate.

+
name.simulation
+

simulation folder name.

+
perStrata
+

a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +by strata in each pixel. If FALSE, PFG abundance is defined for all strata.

+
hab.obs
+

a raster map of the observed habitat in the +extended studied area.

+
year
+

year of simulation for validation.

+
+
+

Value

+

Habitat performance file +If option selected, the function returns an habitat prediction file with +observed and simulated habitat for each pixel of the whole map.

+
+
+

Details

+

After several preliminary checks, the function is going to prepare the observations +database by extracting the observed habitat from a raster map. Then, for each +simulations (sim.version), the script take the evolution abundance for each PFG +and all strata file and predict the habitat for the whole map (if option selected) +thanks to the RF model. Finally, the function computes habitat performance based on +TSS for each habitat.

+
+
+

Author

+

Matthieu Combaud & Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + From 9fc07046dc10b89a6de0d63afc3360786094a060 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 8 Mar 2022 11:07:00 +0100 Subject: [PATCH 049/176] Add files via upload Update of the names space for the validation functions --- NAMESPACE | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 1b9bd0a..71d3d62 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -86,7 +86,6 @@ importFrom(colorspace,heat_hcl) importFrom(colorspace,sequential_hcl) importFrom(cowplot,get_legend) importFrom(cowplot,ggdraw) -importFrom(data.table,dcast) importFrom(data.table,fread) importFrom(data.table,fwrite) importFrom(data.table,rbindlist) @@ -133,6 +132,7 @@ importFrom(ggplot2,geom_vline) importFrom(ggplot2,ggplot) importFrom(ggplot2,ggsave) importFrom(ggplot2,ggtitle) +importFrom(ggplot2,guide_legend) importFrom(ggplot2,guides) importFrom(ggplot2,labs) importFrom(ggplot2,scale_alpha) @@ -171,9 +171,9 @@ importFrom(methods,as) importFrom(parallel,detectCores) importFrom(parallel,mclapply) importFrom(phyloclim,niche.overlap) +importFrom(prettyR,Mode) importFrom(randomForest,randomForest) importFrom(randomForest,tuneRF) -importFrom(raster,aggregate) importFrom(raster,as.data.frame) importFrom(raster,as.matrix) importFrom(raster,cellFromXY) @@ -187,6 +187,7 @@ importFrom(raster,extension) importFrom(raster,extent) importFrom(raster,extract) importFrom(raster,getValues) +importFrom(raster,levels) importFrom(raster,mask) importFrom(raster,ncell) importFrom(raster,nlayers) @@ -210,6 +211,7 @@ importFrom(sf,st_transform) importFrom(sf,st_write) importFrom(shiny,runApp) importFrom(sp,SpatialPoints) +importFrom(stats,aggregate) importFrom(stats,as.dist) importFrom(stats,complete.cases) importFrom(stats,cophenetic) From 29256188e584f27818140ba836a49612cee81e6a Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 8 Mar 2022 11:08:12 +0100 Subject: [PATCH 050/176] Add files via upload Update of the names space for the validation functions --- R/POST_FATE.validation.R | 4 ++-- R/UTILS.do_PFG_composition_validation.R | 9 +++++---- R/UTILS.do_habitat_validation.R | 15 ++++++++------- R/UTILS.get_observed_distribution.R | 5 +++-- R/UTILS.plot_predicted_habitat.R | 11 ++++++----- R/UTILS.train_RF_habitat.R | 12 +++++++----- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 326bcea..a5cc134 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -144,7 +144,7 @@ ##' @importFrom raster raster projectRaster res crs crop ##' @importFrom utils read.csv write.csv ##' @importFrom sf st_read -##' @foreach foreach foreach %dopar% +##' @importFrom foreach foreach %dopar% ##' @importFrom forcats fct_expand ##' @importFrom readr write_rds ##' @importFrom doParallel registerDoParallel @@ -191,7 +191,7 @@ POST_FATE.validation = function(name.simulation # CBNA releves data habitat map releves.PFG<-read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) releves.sites<-st_read(paste0(obs.path, releves.sites)) - hab.obs<-raster(paste0(obs.path, hab.obs)) + hab.obs = raster(paste0(obs.path, hab.obs)) # Habitat mask at FATE simu resolution hab.obs.modif <- projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") habitat.FATE.map <- crop(hab.obs.modif, simulation.map) #reprojection and croping of the extended habitat map in order to have a reduced observed habitat map diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index 6c6dea1..72c2284 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -48,10 +48,11 @@ ##' ##' @export ##' -##' @importFrom raster raster projectRaster res crs crop extent origin compareRaster -##' getValues ncell aggregate compareCRS -##' @importFrom utils read.csv write.csv ##' @importFrom dplyr rename filter group_by mutate %>% select +##' @importFrom raster raster projectRaster res crs crop extent origin compareRaster +##' getValues ncell compareCRS levels +##' @importFrom stats aggregate +##' @importFrom utils read.csv write.csv ##' @importFrom data.table setDT ##' ### END OF HEADER ############################################################## @@ -99,7 +100,7 @@ do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, } if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ print("setting origin habitat.FATE.map to match simulation.map") - origin(habitat.FATE.map)<-origin(simulation.map) + raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) } if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates stop("habitat.FATE.map could not be coerced to match simulation.map") diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index b080596..097265e 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -47,10 +47,11 @@ ##' ##' @export ##' -##' @importFrom raster compareCRS res projectRaster extent crop origin compareRaster -##' getValues aggregate predict +##' @importFrom dplyr filter rename group_by %>% mutate rename select +##' @importFrom raster compareCRS res projectRaster extent crop origin compareRaster +##' getValues predict levels +##' @importFrom stats aggregate ##' @importFrom stringr str_sub -##' @importFrom dplyr select filter rename group_by %>% mutate rename ##' @importFrom foreach foreach %dopar% ##' @importFrom forcats fct_expand ##' @importFrom reshape2 dcast @@ -97,7 +98,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ print("setting origin habitat.FATE.map to match simulation.map") - origin(habitat.FATE.map)<-origin(simulation.map) + raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) } if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates stop("habitat.FATE.map could not be coerced to match simulation.map") @@ -141,8 +142,8 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) habitat.whole.area.df<-habitat.whole.area.df[in.region.pixels,] habitat.whole.area.df<-subset(habitat.whole.area.df, for.validation!="NA") - habitat.whole.area.df<-merge(habitat.whole.area.df,dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)),by.x="code.habitat",by.y="ID") - habitat.whole.area.df<-filter(habitat.whole.area.df,is.element(habitat,RF.model$classes)) + habitat.whole.area.df<-merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x="code.habitat", by.y="ID") + habitat.whole.area.df<-filter(habitat.whole.area.df, is.element(habitat,RF.model$classes)) print(cat("Habitat considered in the prediction exercise: ",c(unique(habitat.whole.area.df$habitat)),"\n",sep="\t")) @@ -159,7 +160,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat print("processing simulations") registerDoParallel(detectCores()-2) - results.simul <- foreach(i=1:length(sim.version),.packages = c("dplyr","forcats","reshape2","randomForest","vcd","caret")) %dopar%{ + results.simul <- foreach(i=1:length(sim.version)) %dopar%{ ########################" # III.1. Data preparation diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index f7560d6..1fb9f86 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -45,8 +45,9 @@ ##' ##' @export ##' -##' @importFrom dplyr filter select filter group_by mutate %>% rename -##' @importFrom raster aggregate compareCRS res crs +##' @importFrom dplyr select filter group_by mutate %>% rename +##' @importFrom raster compareCRS res crs levels +##' @importFrom stats aggregate ##' @importFrom sf st_transform st_crop ##' @importFrom utils write.csv ##' @importFrom data.table setDT diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index e5903e4..b9b1beb 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -34,13 +34,14 @@ ##' ##' @export ##' -##' @importFrom dplyr select all_of rename +##' @importFrom dplyr all_of rename select ##' @importFrom utils write.csv -##' @importFrom raster raster crs extent res ratify writeRaster +##' @importFrom raster raster crs extent res ratify writeRaster levels ##' @importFrom stats complete.cases -##' @importFrom ggplot2 ggplot geom_raster coord_equal scale_fill_manual -##' ggtitle guides theme ggsave +##' @importFrom ggplot2 ggplot geom_raster coord_equal scale_fill_manual +##' ggtitle guides theme ggsave guide_legend ##' @importFrom reshape2 melt +##' @importFrom prettyR Mode ##' ### END OF HEADER ############################################################## @@ -60,7 +61,7 @@ plot.predicted.habitat<-function(predicted.habitat } #compute modal predicted habitat and the proportion of simulations predicting this habitat (for each pixel) - predicted.habitat$modal.predicted.habitat<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version))),1,Mode) + predicted.habitat$modal.predicted.habitat<-apply(dplyr::select(predicted.habitat,sim.version),1,Mode) predicted.habitat$modal.predicted.habitat[predicted.habitat$modal.predicted.habitat==">1 mode"]<-"ambiguous" predicted.habitat$confidence<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version),modal.predicted.habitat)),1,FUN=function(x) count.habitat(x)) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 6276839..ddd4f0e 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -51,13 +51,15 @@ ##' @export ##' ##' @importFrom dplyr filter %>% group_by select -##' @importFrom data.table dcast setDT -##' @importFrom raster extract aggregate compareCRS +##' @importFrom stats aggregate +##' @importFrom reshape2 dcast +##' @importFrom data.table setDT +##' @importFrom raster extract compareCRS levels ##' @importFrom sf st_transform st_crop st_write ##' @importFrom randomForest randomForest tuneRF ##' @importFrom caret confusionMatrix ##' @importFrom readr write_rds -##' @importFrom utils read.csv +##' @importFrom utils read.csv write.csv ##' ### END OF HEADER ############################################################## @@ -109,8 +111,8 @@ train.RF.habitat<-function(releves.PFG ################################### #get sites coordinates - aggregated.releves.PFG<-merge(dplyr::select(releves.sites,c(site)),aggregated.releves.PFG,by="site") - + aggregated.releves.PFG<-merge(dplyr::select(releves.sites,c(site)), aggregated.releves.PFG,by="site") + #get habitat code and name if(compareCRS(aggregated.releves.PFG,hab.obs)){ aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) From 5e9b5f6127d9f8a5fb0eedc516535cc2bacc3056 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Tue, 8 Mar 2022 11:09:47 +0100 Subject: [PATCH 051/176] Add files via upload Update of the names space for the validation functions From 6043db9072dfade3f058b38d71ba0989dcd62f24 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 9 Mar 2022 09:06:36 +0100 Subject: [PATCH 052/176] Update POST_FATE.validation.html Correction --- docs/reference/POST_FATE.validation.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index 450d8ff..e0ae816 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -231,10 +231,10 @@

Value

Details

Habitat validation
-

The observed habitat is derived from the cesbio map, the simulated habitat +

The observed habitat is derived from a map of the area, the simulated habitat is derived from FATE simulated relative abundance, based on a random forest -algorithm trained on CBNA data. To compare observations and simulations, the function -compute confusion matrix between observation and prediction and then compute the TSS +algorithm trained on observed data. To compare observations and simulations, the function +computes confusion matrix between observation and prediction and then computes the TSS for each habitat h (number of prediction of habitat h/number of observation of habitat h + number of non-prediction of habitat h/number of non-observation of habitat h). The final metrics this script use is the mean of TSS per habitat over all From 7e051b56ea79898147f4c9b25603b182dd4869ef Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 9 Mar 2022 09:07:59 +0100 Subject: [PATCH 053/176] Update POST_FATE.validation.R correction --- R/POST_FATE.validation.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index a5cc134..1bb2ddf 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -49,10 +49,10 @@ ##' @details ##' ##' \describe{ -##' \item{Habitat validation}{The observed habitat is derived from the cesbio map, the simulated habitat +##' \item{Habitat validation}{The observed habitat is derived from a map of the area, the simulated habitat ##' is derived from FATE simulated relative abundance, based on a random forest -##' algorithm trained on CBNA data. To compare observations and simulations, the function -##' compute confusion matrix between observation and prediction and then compute the TSS +##' algorithm trained on observed data. To compare observations and simulations, the function +##' computes confusion matrix between observation and prediction and then computes the TSS ##' for each habitat h (number of prediction of habitat h/number of observation ##' of habitat h + number of non-prediction of habitat h/number of non-observation ##' of habitat h). The final metrics this script use is the mean of TSS per habitat over all From 77bc54e1f1693d109f1b026d0c24893dd48d4b56 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 9 Mar 2022 09:09:04 +0100 Subject: [PATCH 054/176] Update POST_FATE.validation.Rd correction --- man/POST_FATE.validation.Rd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index 6a9e87f..8eed0d9 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -85,10 +85,10 @@ for a \code{FATE} simulation and computes the difference between observed and si } \details{ \describe{ - \item{Habitat validation}{The observed habitat is derived from the cesbio map, the simulated habitat + \item{Habitat validation}{The observed habitat is derived from a map of the area, the simulated habitat is derived from FATE simulated relative abundance, based on a random forest -algorithm trained on CBNA data. To compare observations and simulations, the function -compute confusion matrix between observation and prediction and then compute the TSS +algorithm trained on observed data. To compare observations and simulations, the function +computes confusion matrix between observation and prediction and then computes the TSS for each habitat h (number of prediction of habitat h/number of observation of habitat h + number of non-prediction of habitat h/number of non-observation of habitat h). The final metrics this script use is the mean of TSS per habitat over all From fd81ce0b50a90af5d0bc593453159b5d97743495 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 9 Mar 2022 09:20:29 +0100 Subject: [PATCH 055/176] Update PRE_FATE.skeletonDirectory.R correction --- R/PRE_FATE.skeletonDirectory.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/PRE_FATE.skeletonDirectory.R b/R/PRE_FATE.skeletonDirectory.R index a243359..857a4a1 100644 --- a/R/PRE_FATE.skeletonDirectory.R +++ b/R/PRE_FATE.skeletonDirectory.R @@ -71,14 +71,14 @@ ##' \item{\code{RESULTS}}{this folder will collect all the results produced by the ##' software with a folder for each simulation} ##' \item{\code{VALIDATION}}{this folder will collect all the validation files produced -##' by POST_FATE validation functions +##' by POST_FATE.validation function ##' \describe{ ##' \item{\code{HABITAT}}{this folder will collect all the validation files produces -##' by the function POST_FATE.validation.habitat} +##' by the function POST_FATE.validation with habitat validation activated} ##' \item{\code{PFG_RICHNESS}}{this folder will collect all the validation files produces -##' by the function POST_FATE.validation_PFG_richness} +##' by the function POST_FATE.validation with PFG richness validation activated} ##' \item{\code{PFG_COMPOSITION}}{this folder will collect all the validation files produces -##' by the function POST_FATE.validation_PFG_composition} +##' by the function POST_FATE.validation with PFG composition validation activated} ##' } ##' } ##' } From abf55e2ae7307483bb6b9178b4af03933e7daeb0 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 9 Mar 2022 09:25:33 +0100 Subject: [PATCH 056/176] Add files via upload correction of documentation --- man/PRE_FATE.skeletonDirectory.Rd | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/man/PRE_FATE.skeletonDirectory.Rd b/man/PRE_FATE.skeletonDirectory.Rd index 7bc8802..1e35d48 100644 --- a/man/PRE_FATE.skeletonDirectory.Rd +++ b/man/PRE_FATE.skeletonDirectory.Rd @@ -77,10 +77,14 @@ The tree structure is detailed below : \item{\code{RESULTS}}{this folder will collect all the results produced by the software with a folder for each simulation} \item{\code{VALIDATION}}{this folder will collect all the validation files produced - by POST_FATE validation functions + by POST_FATE.validation function \describe{ \item{\code{HABITAT}}{this folder will collect all the validation files produces - by the function POST_FATE.validation.habitat} + by the function POST_FATE.validation with habitat validation activated} + \item{\code{PFG_RICHNESS}}{this folder will collect all the validation files produces + by the function POST_FATE.validation with PFG richness validation activated} + \item{\code{PFG_COMPOSITION}}{this folder will collect all the validation files produces + by the function POST_FATE.validation with PFG composition validation activated} } } } From 59459c3b34d4446e8a353427bc825312debc4ddf Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Wed, 9 Mar 2022 09:26:15 +0100 Subject: [PATCH 057/176] Add files via upload correction of documentation --- docs/reference/PRE_FATE.skeletonDirectory.html | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/reference/PRE_FATE.skeletonDirectory.html b/docs/reference/PRE_FATE.skeletonDirectory.html index 5b64a49..492b294 100644 --- a/docs/reference/PRE_FATE.skeletonDirectory.html +++ b/docs/reference/PRE_FATE.skeletonDirectory.html @@ -235,9 +235,17 @@

Details

VALIDATION

this folder will collect all the validation files produced - by POST_FATE validation functions

HABITAT
+ by POST_FATE.validation function

HABITAT

this folder will collect all the validation files produces - by the function POST_FATE.validation.habitat

+ by the function POST_FATE.validation with habitat validation activated

+ +
PFG_RICHNESS
+

this folder will collect all the validation files produces + by the function POST_FATE.validation with PFG richness validation activated

+ +
PFG_COMPOSITION
+

this folder will collect all the validation files produces + by the function POST_FATE.validation with PFG composition validation activated

From 737f56ce6efdb5554ac8432b9a4caff62ccf8e12 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 18 Mar 2022 14:15:25 +0100 Subject: [PATCH 058/176] Add files via upload Update of the validation functions and the temporal evolution function with strata definition --- R/POST_FATE.temporalEvolution.R | 521 ++++++++++++++++-------- R/POST_FATE.validation.R | 170 +++++--- R/UTILS.do_PFG_composition_validation.R | 57 ++- R/UTILS.do_habitat_validation.R | 65 ++- R/UTILS.get_observed_distribution.R | 12 +- R/UTILS.plot_predicted_habitat.R | 4 +- R/UTILS.train_RF_habitat.R | 4 +- 7 files changed, 577 insertions(+), 256 deletions(-) diff --git a/R/POST_FATE.temporalEvolution.R b/R/POST_FATE.temporalEvolution.R index b32c884..c3420df 100644 --- a/R/POST_FATE.temporalEvolution.R +++ b/R/POST_FATE.temporalEvolution.R @@ -25,6 +25,9 @@ ##' @param opt.no_CPU (\emph{optional}) default \code{1}. \cr The number of ##' resources that can be used to parallelize the \code{unzip/zip} of raster ##' files, as well as the extraction of values from raster files +##' @param perStrata default \code{FALSE}. \cr If abundance per PFG & per Strata +##' activated in global parameters, the function saved a temporal evolution file +##' per PFG & per Strata. ##' ##' ##' @details @@ -34,14 +37,17 @@ ##' preanalytical tables that can then be used to create graphics}. \cr \cr ##' ##' For each PFG and each selected simulation year, raster maps are retrieved -##' from the results folder \code{ABUND_perPFG_allStrata} and unzipped. +##' from the results folder \code{ABUND_perPFG_allStrata} and unzipped. If +##' \code{perStrata} = \code{TRUE}, raster maps are retrieved from the folder +##' \code{ABUND_perPFG_perStrata} and unzipped. ##' Informations extracted lead to the production of one table before the maps ##' are compressed again : ##' ##' \itemize{ -##' \item{the value of \strong{abundance for each Plant Functional Group} -##' for each selected simulation year(s) in every pixel in which the PFG is -##' present for at least one of the selected simulation year(s) \cr \cr +##' \item{the value of \strong{abundance for each Plant Functional Group} +##' for each selected simulation year(s) and, if option selected, each height +##' stratum in every pixel in which the PFG is present for at least one of +##' the selected simulation year(s) \cr \cr ##' } ##' } ##' @@ -95,7 +101,7 @@ ##' following columns : ##' \describe{ ##' \item{\code{PFG}}{concerned plant functional group (for abundance)} -##' \item{\code{STRATUM}}{concerned height stratum (for LIGHT)} +##' \item{\code{STRATUM}}{concerned height stratum (for LIGHT & abundance if option selected)} ##' \item{\code{ID.pixel}}{number of the concerned pixel} ##' \item{\code{X, Y}}{coordinates of the concerned pixel} ##' \item{\code{HAB}}{habitat of the concerned pixel} @@ -121,7 +127,7 @@ ##' ##' @export ##' -##' @importFrom foreach foreach %do% %dopar% +##' @importFrom foreach foreach %do% %dopar% %:% ##' @importFrom data.table rbindlist fwrite ##' @importFrom raster raster stack ##' rasterToPoints as.data.frame extract cellFromXY @@ -137,6 +143,7 @@ POST_FATE.temporalEvolution = function( , no_years , opt.ras_habitat = NULL , opt.no_CPU = 1 + , perStrata = FALSE ){ ############################################################################# @@ -164,8 +171,7 @@ POST_FATE.temporalEvolution = function( ############################################################################# - res = foreach (abs.simulParam = abs.simulParams) %do% - { + res = foreach (abs.simulParam = abs.simulParams) %do% { cat("\n+++++++\n") cat("\n Simulation name : ", name.simulation) @@ -192,170 +198,319 @@ POST_FATE.temporalEvolution = function( } ## Get list of arrays and extract years of simulation ------------------- - raster.perPFG.allStrata = .getRasterNames(years = NULL, "allStrata", "ABUND", GLOB_DIR) - years = sapply(sub("Abund_YEAR_", "", raster.perPFG.allStrata) - , function(x) strsplit(as.character(x), "_")[[1]][1]) - years = sort(unique(as.numeric(years))) - years = years[round(seq(1, length(years) - , length.out = min(no_years, length(years))))] - no_years = length(years) + if(perStrata == FALSE){ + raster.perPFG.allStrata = .getRasterNames(years = NULL, "allStrata", "ABUND", GLOB_DIR) + years = sapply(sub("Abund_YEAR_", "", raster.perPFG.allStrata) + , function(x) strsplit(as.character(x), "_")[[1]][1]) + years = sort(unique(as.numeric(years))) + years = years[round(seq(1, length(years) + , length.out = min(no_years, length(years))))] + no_years = length(years) + + cat("\n Selected years : ", years) + cat("\n Number of years : ", no_years) + cat("\n") + + } else if(perStrata == TRUE){ + raster.perPFG.perStrata = .getRasterNames(years = NULL, "perStrata", "ABUND", GLOB_DIR) + years = sapply(sub("Abund_YEAR_", "", raster.perPFG.perStrata) + , function(x) strsplit(as.character(x), "_")[[1]][1]) + years = sort(unique(as.numeric(years))) + years = years[round(seq(1, length(years) + , length.out = min(no_years, length(years))))] + no_years = length(years) + + cat("\n Selected years : ", years) + cat("\n Number of years : ", no_years) + cat("\n") + } - cat("\n Selected years : ", years) - cat("\n Number of years : ", no_years) - cat("\n") ## UNZIP the raster saved ----------------------------------------------- - raster.perPFG.allStrata = .getRasterNames(years, "allStrata", "ABUND", GLOB_DIR) - .unzip(folder_name = GLOB_DIR$dir.output.perPFG.allStrata - , list_files = raster.perPFG.allStrata - , no_cores = opt.no_CPU) - if (GLOB_SIM$doLight){ - .unzip(folder_name = GLOB_DIR$dir.output.light - , list_files = list.files(path = GLOB_DIR$dir.output.light - , pattern = paste0("YEAR_", years, "_", collapse = "|")) + if(perStrata == FALSE){ + raster.perPFG.allStrata = .getRasterNames(years, "allStrata", "ABUND", GLOB_DIR) + .unzip(folder_name = GLOB_DIR$dir.output.perPFG.allStrata + , list_files = raster.perPFG.allStrata , no_cores = opt.no_CPU) - } - if (GLOB_SIM$doSoil){ - .unzip(folder_name = GLOB_DIR$dir.output.soil - , list_files = list.files(path = GLOB_DIR$dir.output.soil - , pattern = paste0("YEAR_", years, collapse = "|")) + if (GLOB_SIM$doLight){ + .unzip(folder_name = GLOB_DIR$dir.output.light + , list_files = list.files(path = GLOB_DIR$dir.output.light + , pattern = paste0("YEAR_", years, "_", collapse = "|")) + , no_cores = opt.no_CPU) + } + if (GLOB_SIM$doSoil){ + .unzip(folder_name = GLOB_DIR$dir.output.soil + , list_files = list.files(path = GLOB_DIR$dir.output.soil + , pattern = paste0("YEAR_", years, collapse = "|")) + , no_cores = opt.no_CPU) + } + + doWriting.abund = TRUE + doWriting.light = ifelse(GLOB_SIM$doLight, TRUE, FALSE) + doWriting.soil = ifelse(GLOB_SIM$doSoil, TRUE, FALSE) + + }else if(perStrata == TRUE){ + raster.perPFG.perStrata = .getRasterNames(years, "perStrata", "ABUND", GLOB_DIR) + .unzip(folder_name = GLOB_DIR$dir.output.perPFG.perStrata + , list_files = raster.perPFG.perStrata , no_cores = opt.no_CPU) + if (GLOB_SIM$doLight){ + .unzip(folder_name = GLOB_DIR$dir.output.light + , list_files = list.files(path = GLOB_DIR$dir.output.light + , pattern = paste0("YEAR_", years, "_", collapse = "|")) + , no_cores = opt.no_CPU) + } + if (GLOB_SIM$doSoil){ + .unzip(folder_name = GLOB_DIR$dir.output.soil + , list_files = list.files(path = GLOB_DIR$dir.output.soil + , pattern = paste0("YEAR_", years, collapse = "|")) + , no_cores = opt.no_CPU) + } + + doWriting.abund = TRUE + doWriting.light = ifelse(GLOB_SIM$doLight, TRUE, FALSE) + doWriting.soil = ifelse(GLOB_SIM$doSoil, TRUE, FALSE) } - doWriting.abund = TRUE - doWriting.light = ifelse(GLOB_SIM$doLight, TRUE, FALSE) - doWriting.soil = ifelse(GLOB_SIM$doSoil, TRUE, FALSE) - ## get the data inside the rasters -------------------------------------- - cat("\n ---------- GETTING ABUNDANCE for pfg") - if (opt.no_CPU > 1) - { - if (.getOS() != "windows") - { - registerDoParallel(cores = opt.no_CPU) - } else + ## get the data inside the rasters (abundance) -------------------------------------- + + if(perStrata == FALSE){ + + cat("\n ---------- GETTING ABUNDANCE for pfg") + if (opt.no_CPU > 1) { - warning("Parallelisation with `foreach` is not available for Windows. Sorry.") + if (.getOS() != "windows") + { + registerDoParallel(cores = opt.no_CPU) + } else + { + warning("Parallelisation with `foreach` is not available for Windows. Sorry.") + } } - } - tabAbund.list = foreach (pfg = GLOB_SIM$PFG) %dopar% - { - cat(" ", pfg) - file_name = paste0(GLOB_DIR$dir.output.perPFG.allStrata, - "Abund_YEAR_", - years, - "_", - pfg, - "_STRATA_all") - if (length(which(file.exists(paste0(file_name, ".tif")))) > 0) - { - file_name = paste0(file_name, ".tif") - } else if (length(which(file.exists(paste0(file_name, ".img")))) > 0) + tabAbund.list = foreach (pfg = GLOB_SIM$PFG) %dopar% + { + cat(" ", pfg) + file_name = paste0(GLOB_DIR$dir.output.perPFG.allStrata, + "Abund_YEAR_", + years, + "_", + pfg, + "_STRATA_all") + if (length(which(file.exists(paste0(file_name, ".tif")))) > 0) + { + file_name = paste0(file_name, ".tif") + } else if (length(which(file.exists(paste0(file_name, ".img")))) > 0) + { + file_name = paste0(file_name, ".img") + } else if (length(which(file.exists(paste0(file_name, ".asc")))) > 0) + { + file_name = paste0(file_name, ".asc") + } + + ye = years[which(file.exists(file_name))] + file_name = file_name[which(file.exists(file_name))] + + if (length(file_name) > 0) + { + ras = stack(file_name) * GLOB_MASK$ras.mask + ras.df = rasterToPoints(ras) + ras.df = as.data.frame(ras.df) + colnames(ras.df) = c("X", "Y", ye) + ID.abund = rowSums(ras.df[, 3:ncol(ras.df), drop = FALSE]) + ras.df = ras.df[which(ID.abund > 0), , drop = FALSE] + + if (nrow(ras.df) > 0) + { + ras.df$ID.pixel = cellFromXY(GLOB_MASK$ras.mask, ras.df[, c("X", "Y")]) + ras.df$PFG = pfg + + if (exists("ras.habitat")) + { + ras.df$HAB = extract(ras.habitat, ras.df[, c("X", "Y")]) + } else + { + ras.df$HAB = "ALL" + } + ras.df = ras.df[, c("PFG", "ID.pixel", "X", "Y", "HAB", ye)] + + return(ras.df) + } + } + } ## END loop on PFG + cat("\n") + + tabAbund = rbindlist(tabAbund.list, fill = TRUE) + tabAbund = as.data.frame(tabAbund, stringsAsFactors = FALSE) + + if (nrow(tabAbund) > 0 && ncol(tabAbund) > 0) { - file_name = paste0(file_name, ".img") - } else if (length(which(file.exists(paste0(file_name, ".asc")))) > 0) + fwrite(tabAbund + , file = paste0(name.simulation + , "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_" + , basename(GLOB_DIR$dir.save) + , ".csv") + , row.names = FALSE) + } else { - file_name = paste0(file_name, ".asc") + tabAbund = NA + doWriting.abund = FALSE + warning("No abundance values were found! Please check.") } - ye = years[which(file.exists(file_name))] - file_name = file_name[which(file.exists(file_name))] + }else if(perStrata == TRUE){ - if (length(file_name) > 0) + cat("\n ---------- GETTING ABUNDANCE for pfg") + if (opt.no_CPU > 1) { - ras = stack(file_name) * GLOB_MASK$ras.mask - ras.df = rasterToPoints(ras) - ras.df = as.data.frame(ras.df) - colnames(ras.df) = c("X", "Y", ye) - ID.abund = rowSums(ras.df[, 3:ncol(ras.df), drop = FALSE]) - ras.df = ras.df[which(ID.abund > 0), , drop = FALSE] - - if (nrow(ras.df) > 0) + if (.getOS() != "windows") { - ras.df$ID.pixel = cellFromXY(GLOB_MASK$ras.mask, ras.df[, c("X", "Y")]) - ras.df$PFG = pfg - - if (exists("ras.habitat")) + registerDoParallel(cores = opt.no_CPU) + } else + { + warning("Parallelisation with `foreach` is not available for Windows. Sorry.") + } + } + tabAbund.list = foreach (pfg = GLOB_SIM$PFG) %dopar% { + no_strata = NULL + for(y in years){ + strata = list.files(GLOB_DIR$dir.output.perPFG.perStrata, pattern = paste0("YEAR_", y , "_", pfg)) # take all the numbers of abundance files for each years + no_strata = c(no_strata, length(strata)/2) # divide it by 2 because there are 2 files for each year*strata + } + no_strata = max(no_strata) # the maximum is taken to have the number of the highest strata for each PFG + + registerDoParallel(cores = opt.no_CPU) + tabAbund = foreach (str = 1:no_strata, .combine = "rbind") %dopar% { + cat(paste0(" ", pfg, "_", str)) + file_name = paste0(GLOB_DIR$dir.output.perPFG.perStrata, + "Abund_YEAR_", + years, + "_", + pfg, + "_STRATA_", + str) + if (length(which(file.exists(paste0(file_name, ".tif")))) > 0) + { + file_name = paste0(file_name, ".tif") + } else if (length(which(file.exists(paste0(file_name, ".img")))) > 0) { - ras.df$HAB = extract(ras.habitat, ras.df[, c("X", "Y")]) - } else + file_name = paste0(file_name, ".img") + } else if (length(which(file.exists(paste0(file_name, ".asc")))) > 0) { - ras.df$HAB = "ALL" + file_name = paste0(file_name, ".asc") } - ras.df = ras.df[, c("PFG", "ID.pixel", "X", "Y", "HAB", ye)] - return(ras.df) + ye = years[which(file.exists(file_name))] + file_name = file_name[which(file.exists(file_name))] + + if (length(file_name) > 0) + { + ras = stack(file_name) * GLOB_MASK$ras.mask + ras.df = rasterToPoints(ras) + ras.df = as.data.frame(ras.df) + colnames(ras.df) = c("X", "Y", ye) + ID.abund = rowSums(ras.df[, 3:ncol(ras.df), drop = FALSE]) + ras.df = ras.df[which(ID.abund > 0), , drop = FALSE] + + if (nrow(ras.df) > 0) + { + ras.df$ID.pixel = cellFromXY(GLOB_MASK$ras.mask, ras.df[, c("X", "Y")]) + ras.df$PFG = pfg + ras.df$strata = str + + if (exists("ras.habitat")) + { + ras.df$HAB = extract(ras.habitat, ras.df[, c("X", "Y")]) + } else + { + ras.df$HAB = "ALL" + } + if(length(setdiff(years,ye)) > 0){ + missing = data.frame(matrix(0, ncol = length(setdiff(years,ye)), nrow = nrow(ras.df))) # create a new data frame with value 0 for the missing years in the files for each PFG, if files are missing + colnames(missing) = setdiff(years,ye) + missing$ID.pixel = ras.df$ID.pixel + ras.df = merge(ras.df, missing, by = "ID.pixel") # adding of a common column with ras.df and then merge the two data frame + } + yea = c(ye, setdiff(years,ye)) + yea = as.character(sort(as.numeric(yea))) + + ras.df = ras.df[, c("PFG", "ID.pixel", "X", "Y", "HAB", yea, "strata")] + + return(ras.df) + } + } } + return(tabAbund) + } ## END loop on PFG + cat("\n") + + tabAbund = rbindlist(tabAbund.list, fill = TRUE) + tabAbund = as.data.frame(tabAbund, stringsAsFactors = FALSE) + + if (nrow(tabAbund) > 0 && ncol(tabAbund) > 0) + { + fwrite(tabAbund + , file = paste0(name.simulation + , "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_" + , basename(GLOB_DIR$dir.save) + , ".csv") + , row.names = FALSE) + } else + { + tabAbund = NA + doWriting.abund = FALSE + warning("No abundance values were found! Please check.") } - } ## END loop on PFG - cat("\n") - - tabAbund = rbindlist(tabAbund.list, fill = TRUE) - tabAbund = as.data.frame(tabAbund, stringsAsFactors = FALSE) - - if (nrow(tabAbund) > 0 && ncol(tabAbund) > 0) - { - fwrite(tabAbund - , file = paste0(name.simulation - , "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_" - , basename(GLOB_DIR$dir.save) - , ".csv") - , row.names = FALSE) - } else - { - tabAbund = NA - doWriting.abund = FALSE - warning("No abundance values were found! Please check.") } - ## get the data inside the rasters -------------------------------------- + ## get the data inside the rasters (light) -------------------------------------- if (GLOB_SIM$doLight) { cat("\n ---------- GETTING LIGHT for stratum") tabLight.list = foreach (stra = c(1:GLOB_SIM$no_STRATA)-1) %dopar% - { - cat(" ", stra) - file_name = paste0(GLOB_DIR$dir.output.light - , "Light_Resources_YEAR_" - , years - , "_STRATA_" - , stra) - if (length(which(file.exists(paste0(file_name, ".tif")))) > 0) - { - file_name = paste0(file_name, ".tif") - } else if (length(which(file.exists(paste0(file_name, ".img")))) > 0) - { - file_name = paste0(file_name, ".img") - } else if (length(which(file.exists(paste0(file_name, ".asc")))) > 0) - { - file_name = paste0(file_name, ".asc") - } - ye = years[which(file.exists(file_name))] - file_name = file_name[which(file.exists(file_name))] - - if (length(file_name) > 0) { - ras = stack(file_name) * GLOB_MASK$ras.mask - ras.df = rasterToPoints(ras) - ras.df = as.data.frame(ras.df) - colnames(ras.df) = c("X", "Y", ye) - ras.df$ID.pixel = cellFromXY(GLOB_MASK$ras.mask, ras.df[, c("X", "Y")]) - ras.df$STRATUM = stra - - if (exists("ras.habitat")) + cat(" ", stra) + file_name = paste0(GLOB_DIR$dir.output.light + , "Light_Resources_YEAR_" + , years + , "_STRATA_" + , stra) + if (length(which(file.exists(paste0(file_name, ".tif")))) > 0) { - ras.df$HAB = extract(ras.habitat, ras.df[, c("X", "Y")]) - } else + file_name = paste0(file_name, ".tif") + } else if (length(which(file.exists(paste0(file_name, ".img")))) > 0) { - ras.df$HAB = "ALL" + file_name = paste0(file_name, ".img") + } else if (length(which(file.exists(paste0(file_name, ".asc")))) > 0) + { + file_name = paste0(file_name, ".asc") } - ras.df = ras.df[, c("STRATUM", "ID.pixel", "X", "Y", "HAB", ye)] + ye = years[which(file.exists(file_name))] + file_name = file_name[which(file.exists(file_name))] - return(ras.df) - } - } ## END loop on STRATUM + if (length(file_name) > 0) + { + ras = stack(file_name) * GLOB_MASK$ras.mask + ras.df = rasterToPoints(ras) + ras.df = as.data.frame(ras.df) + colnames(ras.df) = c("X", "Y", ye) + ras.df$ID.pixel = cellFromXY(GLOB_MASK$ras.mask, ras.df[, c("X", "Y")]) + ras.df$STRATUM = stra + + if (exists("ras.habitat")) + { + ras.df$HAB = extract(ras.habitat, ras.df[, c("X", "Y")]) + } else + { + ras.df$HAB = "ALL" + } + ras.df = ras.df[, c("STRATUM", "ID.pixel", "X", "Y", "HAB", ye)] + + return(ras.df) + } + } ## END loop on STRATUM cat("\n") tabLight = rbindlist(tabLight.list, fill = TRUE) @@ -381,7 +536,7 @@ POST_FATE.temporalEvolution = function( } ## END loop for light - ## get the data inside the rasters -------------------------------------- + ## get the data inside the rasters (soil) -------------------------------------- if (GLOB_SIM$doSoil) { cat("\n ---------- GETTING SOIL") @@ -444,35 +599,71 @@ POST_FATE.temporalEvolution = function( ## ZIP the raster saved ------------------------------------------------- - .zip_ALL(folder_name = GLOB_DIR$dir.output.perPFG.allStrata, no_cores= opt.no_CPU) - if (GLOB_SIM$doLight) .zip_ALL(folder_name = GLOB_DIR$dir.output.light, no_cores = opt.no_CPU) - if (GLOB_SIM$doSoil) .zip_ALL(folder_name = GLOB_DIR$dir.output.soil, no_cores = opt.no_CPU) - - cat("\n> Done!\n") - - if(doWriting.abund || doWriting.light || doWriting.soil) - { - message(paste0("\n The output files \n" - , ifelse(doWriting.abund - , paste0(" > POST_FATE_TABLE_PIXEL_evolution_abundance_" - , basename(GLOB_DIR$dir.save) - , ".csv \n") - , "") - , ifelse(doWriting.light - , paste0(" > POST_FATE_TABLE_PIXEL_evolution_light_" - , basename(GLOB_DIR$dir.save) - , ".csv \n") - , "") - , ifelse(doWriting.soil - , paste0(" > POST_FATE_TABLE_PIXEL_evolution_soil_" - , basename(GLOB_DIR$dir.save) - , ".csv \n") - , "") - , "have been successfully created !\n")) + if(perStrata == FALSE){ + + .zip_ALL(folder_name = GLOB_DIR$dir.output.perPFG.allStrata, no_cores= opt.no_CPU) + if (GLOB_SIM$doLight) .zip_ALL(folder_name = GLOB_DIR$dir.output.light, no_cores = opt.no_CPU) + if (GLOB_SIM$doSoil) .zip_ALL(folder_name = GLOB_DIR$dir.output.soil, no_cores = opt.no_CPU) + + cat("\n> Done!\n") + + if(doWriting.abund || doWriting.light || doWriting.soil) + { + message(paste0("\n The output files \n" + , ifelse(doWriting.abund + , paste0(" > POST_FATE_TABLE_PIXEL_evolution_abundance_" + , basename(GLOB_DIR$dir.save) + , ".csv \n") + , "") + , ifelse(doWriting.light + , paste0(" > POST_FATE_TABLE_PIXEL_evolution_light_" + , basename(GLOB_DIR$dir.save) + , ".csv \n") + , "") + , ifelse(doWriting.soil + , paste0(" > POST_FATE_TABLE_PIXEL_evolution_soil_" + , basename(GLOB_DIR$dir.save) + , ".csv \n") + , "") + , "have been successfully created !\n")) + + return(list(tab.abundance = tabAbund + , tab.light = tabLight + , tab.soil = tabSoil)) + } + + }else if(perStrata == TRUE){ + + .zip_ALL(folder_name = GLOB_DIR$dir.output.perPFG.perStrata, no_cores= opt.no_CPU) + if (GLOB_SIM$doLight) .zip_ALL(folder_name = GLOB_DIR$dir.output.light, no_cores = opt.no_CPU) + if (GLOB_SIM$doSoil) .zip_ALL(folder_name = GLOB_DIR$dir.output.soil, no_cores = opt.no_CPU) + + cat("\n> Done!\n") - return(list(tab.abundance = tabAbund - , tab.light = tabLight - , tab.soil = tabSoil)) + if(doWriting.abund || doWriting.light || doWriting.soil) + { + message(paste0("\n The output files \n" + , ifelse(doWriting.abund + , paste0(" > POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_" + , basename(GLOB_DIR$dir.save) + , ".csv \n") + , "") + , ifelse(doWriting.light + , paste0(" > POST_FATE_TABLE_PIXEL_evolution_light_" + , basename(GLOB_DIR$dir.save) + , ".csv \n") + , "") + , ifelse(doWriting.soil + , paste0(" > POST_FATE_TABLE_PIXEL_evolution_soil_" + , basename(GLOB_DIR$dir.save) + , ".csv \n") + , "") + , "have been successfully created !\n")) + + return(list(tab.abundance = tabAbund + , tab.light = tabLight + , tab.soil = tabSoil)) + } } } ## END loop on abs.simulParams names(res) = abs.simulParams diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 1bb2ddf..86c9daa 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -19,7 +19,9 @@ ##' @param name.simulation simulation folder name. ##' @param sim.version name of the simulation to validate (it works with only one \code{sim.version}). ##' @param year year of simulation for validation. -##' @param doHabitat logical. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, +##' @param perStrata \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. +##' If \code{FALSE}, PFG abundance defined for all strata (habitat & PFG composition & PFG richness validation). +##' @param doHabitat \code{Logical}. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, ##' if \code{FALSE}, habitat validation module is disabled. ##' @param obs.path the function needs observed data, please create a folder for them in your ##' simulation folder and then indicate in this parameter the access path to this new folder (habitat & PFG composition validation). @@ -33,13 +35,18 @@ ##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will ##' take into account of all habitats in the \code{hab.obs} map. Otherwise, please specify ##' in a vector habitats that will be take into account for the validation (habitat validation). -##' @param doComposition logical. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, +##' @param list.strata.simulations default \code{NULL}. A character vector which contain \code{FATE} +##' strata definition and correspondence with observed strata definition. +##' @param doComposition \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, ##' if \code{FALSE}, PFG composition validation module is disabled. ##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered ##' in the validation (PFG composition validation). ##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) ##' considered in the validation (PFG composition validation). -##' @param doRichness logical. Default \code{TRUE}. If \code{TRUE}, PFG richness validation module is activated, +##' @param strata.considered_PFG.compo If \code{perStrata} = \code{FALSE}, a character vector with value "A" +##' (selection of one or several specific strata disabled). If \code{perStrata} = \code{TRUE}, a character +##' vector with at least one of the observed strata (PFG composition validation). +##' @param doRichness \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG richness validation module is activated, ##' if \code{FALSE}, PFG richness validation module is disabled. ##' @param list.PFG a character vector which contain all the PFGs taken account in ##' the simulation and observed in the simulation area (PFG richness validation). @@ -50,26 +57,27 @@ ##' ##' \describe{ ##' \item{Habitat validation}{The observed habitat is derived from a map of the area, the simulated habitat -##' is derived from FATE simulated relative abundance, based on a random forest -##' algorithm trained on observed data. To compare observations and simulations, the function -##' computes confusion matrix between observation and prediction and then computes the TSS -##' for each habitat h (number of prediction of habitat h/number of observation -##' of habitat h + number of non-prediction of habitat h/number of non-observation -##' of habitat h). The final metrics this script use is the mean of TSS per habitat over all -##' habitats, weighted by the share of each habitat in the observed habitat distribution.} -##' \item{PFG composition validation}{This code firstly run the \code{get.observed.distribution} function in order to have -##' a \code{obs.distri} file which contain the observed distribution per PFG, strata and habitat. -##' This file is also an argument for the \code{do.PFG.composition.validation} function run next. -##' This second sub function provide the computation of distance between observed and simulated distribution. \cr -##' NB : The argument \code{strata.considered_PFG.compo} is by default "A" in the 2 sub functions because -##' it's easier for a \code{FATE} simulation to provide PFG abundances for all strata. \cr The argument -##' \code{perStrata.compo} is by default \code{NULL} for the same reasons.} +##' is derived from \code{FATE} simulated relative abundance, based on a random forest +##' algorithm trained on observed releves data (see \code{\link{train.RF.habitat}}) \cr +##' To compare observations and simulations, the function computes confusion matrix between +##' observations and predictions and then compute the TSS for each habitat h +##' (number of prediction of habitat h/number of observation of habitat h + number of non-prediction +##' of habitat h/number of non-observation of habitat h). The final metrics this script use is the +##' mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed +##' habitat distribution. The habitat validation also provides a visual comparison of observed and +##' simulated habitat on the whole studied area (see \code{\link{do.habitat.validation}} & +##' \code{\link{plot.predicted.habitat}}).} \cr +##' \item{PFG composition validation}{This code firstly run the \code{get.observed.distribution} +##' function in order to have a \code{obs.distri} file which contain the observed distribution +##' per PFG, strata and habitat. This file is also an argument for the \code{do.PFG.composition.validation} +##' function run next. This second sub function provides the computation of distance between observed +##' and simulated distribution.} ##' \item{PFG richness validation}{Firstly, the function updates the \code{list.PFG} with \code{exclude.PFG} vector. -##' Then, the script takes the abundance per PFG file from the results of the \code{FATE} -##' simulation and computes the difference between the \code{list.PFG} and all the PFG -##' which are presents in the abundance file, in order to obtain the PFG richness for a simulation. -##' The function also determine if an observed PFG is missing in the results of the simulation at -##' a specific year.} +##' Then, the script takes the abundance per PFG (and per strata if option selected) file from the +##' results of the \code{FATE} simulation and computes the difference between the \code{list.PFG} +##' and all the PFG which are presents in the abundance file, in order to obtain the PFG richness +##' for a simulation. The function also determine if an observed PFG is missing in the results of the +##' simulation at a specific year.} ##' } ##' ##' @return @@ -84,7 +92,7 @@ ##' \describe{ ##' \item{\file{VALIDATION/PFG_COMPOSITION/sim.version}}{1 .csv file which contain the proximity ##' between observed and simulated data computed for each PFG/strata/habitat. \cr 1 .csv file which -##' contain the observed relevés transformed into relative metrics. \cr 1 .csv file which contain +##' contain the observed releves transformed into relative metrics. \cr 1 .csv file which contain ##' the final output with the distribution per PFG, strata and habitat.} ##' } ##' \describe{ @@ -96,9 +104,11 @@ ##' @examples ##' ##' ## Habitat validation --------------------------------------------------------------------------------- +##' list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) ##' POST_FATE.validation(name.simulation = "FATE_Champsaur" ##' , sim.version = "SIMUL_V4.1" ##' , year = 2000 +##' , perStrata = TRUE ##' , doHabitat = TRUE ##' , obs.path = "FATE_Champsaur/DATA_OBS/" ##' , releves.PFG = "releves.PFG.abundance.csv" @@ -106,15 +116,19 @@ ##' , hab.obs = "simplified.cesbio.map.grd" ##' , validation.mask = "certain.habitat.100m.restricted.grd" ##' , studied.habitat = NULL +##' , list.strata.simulations = list.strata.simulations ##' , doComposition = FALSE ##' , doRichness = FALSE) ##' ##' ## PFG composition validation -------------------------------------------------------------------------- -##' list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +##' list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) +##' list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) ##' habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") +##' strata.considered_PFG.compo = c("S", "M", "B") ##' POST_FATE.validation(name.simulation = "FATE_Champsaur" ##' , sim.version = "SIMUL_V4.1" ##' , year = 2000 +##' , perStrata = TRUE ##' , doHabitat = FALSE ##' , obs.path = "FATE_Champsaur/DATA_OBS/" ##' , releves.PFG = "releves.PFG.abundance.csv" @@ -122,16 +136,19 @@ ##' , hab.obs = "simplified.cesbio.map.grd" ##' , validation.mask = "certain.habitat.100m.restricted.grd" ##' , studied.habitat = NULL +##' , list.strata.simulations = list.strata.simulations ##' , doComposition = TRUE ##' , PFG.considered_PFG.compo = list.PFG ##' , habitat.considered_PFG.compo = habitat.considered +##' , strata.considered_PFG.compo = strata.considered_PFG.compo ##' , doRichness = FALSE) ##' ##' ## PFG richness validation ----------------------------------------------------------------------------- -##' list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) -##' POST_FATE.validation(name.simulation = "FATE_CHampsaur" +##' list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +##' POST_FATE.validation(name.simulation = "FATE_Champsaur" ##' , sim.version = "SIMUL_V4.1" ##' , year = 2000 +##' , perStrata = TRUE ##' , doHabitat = FALSE ##' , doComposition = FALSE ##' , doRichness = TRUE @@ -156,6 +173,7 @@ POST_FATE.validation = function(name.simulation , sim.version , year + , perStrata = TRUE , doHabitat = TRUE , obs.path , releves.PFG @@ -163,15 +181,21 @@ POST_FATE.validation = function(name.simulation , hab.obs , validation.mask , studied.habitat = NULL + , list.strata.simulations = NULL , doComposition = TRUE , PFG.considered_PFG.compo , habitat.considered_PFG.compo + , strata.considered_PFG.compo , doRichness = TRUE , list.PFG , exclude.PFG = NULL){ if(doHabitat == TRUE){ + cat("\n\n #------------------------------------------------------------#") + cat("\n # HABITAT VALIDATION") + cat("\n #------------------------------------------------------------# \n") + ## GLOBAL PARAMETERS dir.create(file.path(name.simulation, "VALIDATION", "HABITAT", sim.version), showWarnings = FALSE) @@ -179,6 +203,7 @@ POST_FATE.validation = function(name.simulation # General output.path = paste0(name.simulation, "/VALIDATION") year = year # choice in the year for validation + perStrata = perStrata # Useful elements to extract from the simulation name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), @@ -189,8 +214,17 @@ POST_FATE.validation = function(name.simulation # For habitat validation # CBNA releves data habitat map - releves.PFG<-read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) - releves.sites<-st_read(paste0(obs.path, releves.sites)) + releves.PFG = read.csv(paste0(obs.path,releves.PFG),header=T,stringsAsFactors = T) + + if(perStrata==TRUE){ + list.strata.releves = as.character(unique(releves.PFG$strata)) + list.strata.simulations = list.strata.simulations + }else { + list.strata.releves = NULL + list.strata.simulations = NULL + } + + releves.sites = st_read(paste0(obs.path, releves.sites)) hab.obs = raster(paste0(obs.path, hab.obs)) # Habitat mask at FATE simu resolution hab.obs.modif <- projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") @@ -201,7 +235,7 @@ POST_FATE.validation = function(name.simulation if(is.null(studied.habitat)){ studied.habitat = studied.habitat #if null, the function will study all the habitats in the map } else if(is.character(studied.habitat)){ - studied.habitat = studied.habitat #if a character vector with habitat names, the functuon will study only the habitats in the vector + studied.habitat = studied.habitat #if a character vector with habitat names, the function will study only the habitats in the vector } else{ stop("studied.habitat is not a vector of character") } @@ -219,7 +253,7 @@ POST_FATE.validation = function(name.simulation , studied.habitat = studied.habitat , RF.param = RF.param , output.path = output.path - , perStrata = F + , perStrata = perStrata , sim.version = sim.version) ## USE THE RF MODEL TO VALIDATE FATE OUTPUT @@ -232,9 +266,11 @@ POST_FATE.validation = function(name.simulation , predict.all.map = predict.all.map , sim.version = sim.version , name.simulation = name.simulation - , perStrata = F + , perStrata = perStrata , hab.obs = hab.obs - , year = year) + , year = year + , list.strata.releves = list.strata.releves + , list.strata.simulations = list.strata.simulations) ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT @@ -254,16 +290,29 @@ POST_FATE.validation = function(name.simulation if(doComposition == TRUE){ + cat("\n\n #------------------------------------------------------------#") + cat("\n # PFG COMPOSITION VALIDATION") + cat("\n #------------------------------------------------------------# \n") + ## GLOBAL PARAMETERS if(doHabitat == FALSE){ + + perStrata = perStrata - # Get observed distribution - releves.PFG = read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) - releves.sites = st_read(paste0(obs.path, releves.sites)) - hab.obs = raster(paste0(obs.path, hab.obs)) - # Do PFG composition validation - validation.mask = raster(paste0(obs.path, validation.mask)) + # Get observed distribution + releves.PFG = read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) + releves.sites = st_read(paste0(obs.path, releves.sites)) + hab.obs = raster(paste0(obs.path, hab.obs)) + # Do PFG composition validation + validation.mask = raster(paste0(obs.path, validation.mask)) + if(perStrata==TRUE){ + list.strata.releves = as.character(unique(releves.PFG$strata)) + list.strata.simulations = list.strata.simulations + }else { + list.strata.releves = NULL + list.strata.simulations = NULL + } } ## GET OBSERVED DISTRIBUTION @@ -274,9 +323,9 @@ POST_FATE.validation = function(name.simulation , releves.sites = releves.sites , hab.obs = hab.obs , PFG.considered_PFG.compo = PFG.considered_PFG.compo - , strata.considered_PFG.compo = "A" + , strata.considered_PFG.compo = strata.considered_PFG.compo , habitat.considered_PFG.compo = habitat.considered_PFG.compo - , perStrata.compo = FALSE + , perStrata = perStrata , sim.version = sim.version) ## DO PFG COMPOSITION VALIDATION @@ -286,20 +335,25 @@ POST_FATE.validation = function(name.simulation , sim.version = sim.version , hab.obs = hab.obs , PFG.considered_PFG.compo = PFG.considered_PFG.compo - , strata.considered_PFG.compo = "A" + , strata.considered_PFG.compo = strata.considered_PFG.compo , habitat.considered_PFG.compo = habitat.considered_PFG.compo , observed.distribution = obs.distri - , perStrata.compo = FALSE + , perStrata = perStrata , validation.mask = validation.mask - , year = year) + , year = year + , list.strata.simulations = list.strata.simulations + , list.strata.releves = list.strata.releves) } if(doRichness == TRUE){ - output.path = paste0(name.simulation, "/VALIDATION/PFG_RICHNESS/", sim.version) + cat("\n\n #------------------------------------------------------------#") + cat("\n # PFG RICHNESS VALIDATION") + cat("\n #------------------------------------------------------------# \n") - #exclude PFG : character vector containing the names of the PFG you want to exclude from the analysis #optional + output.path = paste0(name.simulation, "/VALIDATION/PFG_RICHNESS/", sim.version) + perStrata = perStrata #list of PFG of interest list.PFG<-setdiff(list.PFG,exclude.PFG) @@ -307,9 +361,19 @@ POST_FATE.validation = function(name.simulation registerDoParallel(detectCores()-2) dying.PFG.list<-foreach(i=1:length(sim.version)) %dopar% { - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] - colnames(simu_PFG) = c("PFG", "pixel", "abs") + if(perStrata==F){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] + colnames(simu_PFG) = c("PFG", "pixel", "abs") + + } else if(perStrata==T){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + + } return(setdiff(list.PFG,unique(simu_PFG$PFG))) } @@ -337,10 +401,12 @@ POST_FATE.validation = function(name.simulation } - cat("\n ---------- END OF FUNCTION \n") + cat("\n\n #------------------------------------------------------------#") + cat("\n # RESULTS : ") + cat("\n #------------------------------------------------------------# \n") if(doRichness == TRUE){ - cat("\n ---------- PFG RICHNESS VALIDATION RESULTS \n") + cat("\n ---------- PFG RICHNESS : \n") cat(paste0("\n Richness at year ", year, " : ", output[[1]][2], "\n")) cat(paste0("\n Number of PFG extinction at year ", year, " : ", sum(output[[2]]), "\n")) } else{cat("\n ---------- PFG RICHNESS VALIDATION DISABLED \n") @@ -349,14 +415,14 @@ POST_FATE.validation = function(name.simulation hab.pred = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/", sim.version, "/hab.pred.csv")) failure = as.numeric((table(hab.pred$prediction.code)[1]/sum(table(hab.pred$prediction.code)))*100) success = as.numeric((table(hab.pred$prediction.code)[2]/sum(table(hab.pred$prediction.code)))*100) - cat("\n ---------- HABITAT VALIDATION RESULTS \n") + cat("\n ---------- HABITAT : \n") cat(paste0("\n", round(failure, digits = 2), "% of habitats are not correctly predicted by ", sim.version, " \n")) cat(paste0("\n", round(success, digits = 2), "% of habitats are correctly predicted by ", sim.version, " \n")) plot(prediction.map) } else{cat("\n ---------- HABITAT VALIDATION DISABLED \n") } if(doComposition == TRUE){ - cat("\n ---------- PFG COMPOSITION VALIDATION RESULTS \n") + cat("\n ---------- PFG COMPOSITION : \n") return(performance.composition) } else{cat("\n ---------- PFG COMPOSITION VALIDATION DISABLED \n") } diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index 72c2284..6cb0c20 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -28,13 +28,18 @@ ##' @param validation.mask file which contain a raster mask that specified ##' which pixels need validation. ##' @param year year of simulation to validate. +##' @param list.strata.simulations a character vector which contain \code{FATE} +##' strata definition and correspondence with observed strata definition. +##' @param list.strata.releves a character vector which contain the observed strata +##' definition, extracted from observed PFG releves. ##' ##' @details ##' ##' After preliminary checks, this code extract observed habitat from the \code{hab.obs} -##' map and, then, merge it with the simulated PFG abundance file from results of a \code{FATE} -##' simulation. After filtration of the required PFG, strata and habitats, the function -##' transform the data into relative metrics and, then, compute distribution per PFG, strata +##' map and, then, merge it with the simulated PFG abundance file (with or without strata definition) +##' from results of the \code{FATE} simulation selected with \code{sim.version}. +##' After filtration of the required PFG, strata and habitats, the function transforms +##' the data into relative metrics and, then, compute distribution per PFG, strata ##' and habitat (if necessary). Finally, the code computes proximity between observed ##' and simulated data, per PFG, strata and habitat. ##' @@ -54,11 +59,14 @@ ##' @importFrom stats aggregate ##' @importFrom utils read.csv write.csv ##' @importFrom data.table setDT +##' @importFrom tidyselect all_of ##' ### END OF HEADER ############################################################## -do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, hab.obs, PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, observed.distribution, perStrata.compo, validation.mask, year){ +do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, hab.obs, PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, observed.distribution, perStrata, validation.mask, year, list.strata.simulations, list.strata.releves){ + + cat("\n ---------- PFG COMPOSITION VALIDATION \n") output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), @@ -80,12 +88,19 @@ do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, ############################ #check if strata definition used in the RF model is the same as the one used to analyze FATE output - if(perStrata.compo==F){ - list.strata<-"all" - }else{ - stop("check 'perStrata' parameter and/or the names of strata in param$list.strata.releves & param$list.strata.simul") + if(perStrata==T){ + if(all(base::intersect(names(list.strata.simulations), list.strata.releves)==names(list.strata.simulations))){ + list.strata = names(list.strata.simulations) + print("strata definition OK") + }else { + stop("wrong strata definition") + } + }else if(perStrata==F){ + list.strata = "all" + }else { + stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulations") } - + #consistency between habitat.FATE.map and simulation.map if(!compareCRS(simulation.map,habitat.FATE.map)){ print("reprojecting habitat.FATE.map to match simulation.map crs") @@ -145,22 +160,36 @@ do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, print("processing simulations") results.simul<-list() - for(i in 1:length(sim.version)) { + for(i in 1:length(all_of(sim.version))) { # 3.1. Data preparation ######################### #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] - colnames(simu_PFG) = c("PFG", "pixel", "abs") + if(perStrata==F){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] + colnames(simu_PFG) = c("PFG", "pixel", "abs") + + }else if(perStrata==T){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + } #aggregate per strata group with the correspondence provided in input simu_PFG$new.strata<-NA #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) - if(perStrata.compo==F){ + if(perStrata==F){ simu_PFG$new.strata<-"A" + }else if(perStrata==T){ + for(p in 1:length(list.strata.simulations)){ + simu_PFG$new.strata[is.element(simu_PFG$strata,list.strata.simulations[[p]])] = names(list.strata.simulations)[p] + } + simu_PFG$strata = NULL } simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 097265e..95612fa 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -20,29 +20,33 @@ ##' @param validation.mask a raster mask that specified which pixels need validation. ##' @param simulation.map a raster map of the whole studied area use to check ##' the consistency between simulation map and the observed habitat map. -##' @param predict.all.map a TRUE/FALSE vector. If TRUE, the script will predict +##' @param predict.all.map \code{Logical}. If TRUE, the script will predict ##' habitat for the whole map. ##' @param sim.version name of the simulation to validate. ##' @param name.simulation simulation folder name. -##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +##' @param perStrata \code{Logical}. If TRUE, the PFG abundance is defined ##' by strata in each pixel. If FALSE, PFG abundance is defined for all strata. ##' @param hab.obs a raster map of the observed habitat in the ##' extended studied area. -##' @param year year of simulation for validation. +##' @param year simulation year selected for validation. +##' @param list.strata.releves a character vector which contain the observed strata +##' definition, extracted from observed PFG releves. +##' @param list.strata.simulations a character vector which contain \code{FATE} +##' strata definition and correspondence with observed strata definition. ##' ##' @details ##' ##' After several preliminary checks, the function is going to prepare the observations -##' database by extracting the observed habitat from a raster map. Then, for each -##' simulations (sim.version), the script take the evolution abundance for each PFG -##' and all strata file and predict the habitat for the whole map (if option selected) -##' thanks to the RF model. Finally, the function computes habitat performance based on -##' TSS for each habitat. +##' database by extracting the observed habitat from a raster map. Then, for the +##' simulation \code{sim.version}, the script take the evolution abundance for each PFG +##' and all strata (or for each PFG & each strata if option selected) file and predict +##' the habitat for the whole map (if option selected) thanks to the RF model. +##' Finally, the function computes habitat performance based on TSS for each habitat. ##' ##' @return ##' -##' Habitat performance file -##' If option selected, the function returns an habitat prediction file with +##' Habitat performance file. \cr +##' If option selected, the function also returns an habitat prediction file with ##' observed and simulated habitat for each pixel of the whole map. ##' ##' @export @@ -59,11 +63,14 @@ ##' @importFrom utils write.csv ##' @importFrom doParallel registerDoParallel ##' @importFrom parallel detectCores +##' @importFrom tidyselect all_of ##' ### END OF HEADER ############################################################## -do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs, year) { +do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs, year, list.strata.releves, list.strata.simulations) { + + cat("\n ---------- FATE OUTPUT ANALYSIS \n") #notes # we prepare the relevé data in this function, but in fact we could provide them directly if we adjust the code @@ -73,10 +80,17 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat ########################### #check if strata definition used in the RF model is the same as the one used to analyze FATE output - if(perStrata==F){ + if(perStrata==T){ + if(all(base::intersect(names(list.strata.simulations), list.strata.releves)==names(list.strata.simulations))){ + list.strata = names(list.strata.simulations) + print("strata definition OK") + }else { + stop("wrong strata definition") + } + }else if(perStrata==F){ list.strata<-"all" }else{ - stop("check 'perStrata' parameter and/or the names of strata in param$list.strata.releves & param$list.strata.simul") + stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) @@ -160,17 +174,27 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat print("processing simulations") registerDoParallel(detectCores()-2) - results.simul <- foreach(i=1:length(sim.version)) %dopar%{ + results.simul <- foreach(i=1:length(all_of(sim.version))) %dopar%{ ########################" # III.1. Data preparation ######################### #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns - #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function - colnames(simu_PFG) = c("PFG", "pixel", "abs") + if(perStrata==F){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns + #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function + colnames(simu_PFG) = c("PFG", "pixel", "abs") + + } else if(perStrata==T){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + + } #aggregate per strata group with the correspondance provided in input simu_PFG$new.strata<-NA @@ -178,6 +202,11 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) if(perStrata==F){ simu_PFG$new.strata<-"A" + }else if(perStrata==T){ + for(i in 1:length(list.strata.simulations)){ + simu_PFG$new.strata[is.element(simu_PFG$strata, list.strata.simulations[[i]])] = names(list.strata.simulations)[i] + } + simu_PFG$strata = NULL } simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index 1fb9f86..6a64f03 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -23,7 +23,7 @@ ##' strata considered in the validation. ##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) ##' considered in the validation. -##' @param perStrata.compo Logical. All strata together (FALSE) or per strata (TRUE). +##' @param perStrata \code{Logical}. All strata together (FALSE) or per strata (TRUE). ##' @param sim.version name of the simulation we want to validate (it works with ##' only one \code{sim.version}). ##' @@ -63,9 +63,11 @@ get.observed.distribution<-function(name.simulation , PFG.considered_PFG.compo , strata.considered_PFG.compo , habitat.considered_PFG.compo - , perStrata.compo + , perStrata , sim.version){ + cat("\n ---------- GET OBSERVED DISTRIBUTION \n") + composition.mask = NULL output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) dir.create(file.path(output.path), recursive = TRUE, showWarnings = FALSE) @@ -79,9 +81,9 @@ get.observed.distribution<-function(name.simulation #transformation into coverage percentage releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage - if(perStrata.compo==T){ + if(perStrata==T){ aggregated.releves.PFG<-aggregate(coverage~site+PFG+strata,data=releves.PFG,FUN="sum") - }else if(perStrata.compo==F){ + }else if(perStrata==F){ aggregated.releves.PFG<-aggregate(coverage~site+PFG,data=releves.PFG,FUN="sum") aggregated.releves.PFG$strata<-"A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) } @@ -154,7 +156,7 @@ get.observed.distribution<-function(name.simulation distribution<-setDT(aggregated.releves.PFG)[, quantile(relative.metric), by=c("PFG","habitat","strata")] distribution<-rename(distribution,"quantile"="V1") - distribution<-data.frame(distribution,rank=seq(0,5,1)) #to be able to sort on quantile + distribution<-data.frame(distribution,rank=seq(0,4,1)) #to be able to sort on quantile # 7. Add the missing PFG*habitat*strata #final distribution is the distribution once the missing combination have been added. For these combination, all quantiles are set to 0 diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index b9b1beb..7e3e65e 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -22,7 +22,7 @@ ##' ##' @details ##' -##' The function determine true/false prediction ('failure' if false, 'success' if true) +##' The function determines true/false prediction ('failure' if false, 'success' if true) ##' and prepare a dataframe containing color and habitat code. Then, the script merge ##' the prediction dataframe with the color and code habitat dataframe. Finally, ##' the function draw a raster map and a plot of prediction habitat over it thanks @@ -53,6 +53,8 @@ plot.predicted.habitat<-function(predicted.habitat , sim.version) { + cat("\n ---------- AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT \n") + #auxiliary function to compute the proportion of simulations lead to the modal prediction count.habitat<-function(df){ index<-which(names(df)=="modal.predicted.habitat") diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index ddd4f0e..34e159c 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -28,7 +28,7 @@ ##' the prediction error. ##' @param output.path access path to the for the folder where output files ##' will be created. -##' @param perStrata a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +##' @param perStrata \code{Logical}. If TRUE, the PFG abundance is defined ##' by strata in each site. If FALSE, PFG abundance is defined for all strata. ##' @param sim.version name of the simulation we want to validate. ##' @@ -75,6 +75,8 @@ train.RF.habitat<-function(releves.PFG , sim.version) { + cat("\n ---------- TRAIN A RANDOM FOREST MODEL ON OBSERVED DATA \n") + #1. Compute relative abundance metric ######################################### From b38567fffab8a3f47b21a7dae0fd6e34115e10be Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 18 Mar 2022 14:21:55 +0100 Subject: [PATCH 059/176] Add files via upload Update of the validation functions and the temporal evolution function with strata definition --- man/POST_FATE.temporalEvolution.Rd | 20 +++++--- man/POST_FATE.validation.Rd | 73 ++++++++++++++++++---------- man/do.PFG.composition.validation.Rd | 25 +++++++--- man/do.habitat.validation.Rd | 30 +++++++----- man/get.observed.distribution.Rd | 4 +- man/plot.predicted.habitat.Rd | 2 +- man/train.RF.habitat.Rd | 4 +- 7 files changed, 103 insertions(+), 55 deletions(-) diff --git a/man/POST_FATE.temporalEvolution.Rd b/man/POST_FATE.temporalEvolution.Rd index 0dfd3c2..97589a3 100644 --- a/man/POST_FATE.temporalEvolution.Rd +++ b/man/POST_FATE.temporalEvolution.Rd @@ -10,7 +10,8 @@ POST_FATE.temporalEvolution( file.simulParam = NULL, no_years, opt.ras_habitat = NULL, - opt.no_CPU = 1 + opt.no_CPU = 1, + perStrata = FALSE ) } \arguments{ @@ -31,13 +32,17 @@ A \code{string} corresponding to the file name of a raster mask, with an \item{opt.no_CPU}{(\emph{optional}) default \code{1}. \cr The number of resources that can be used to parallelize the \code{unzip/zip} of raster files, as well as the extraction of values from raster files} + +\item{perStrata}{default \code{FALSE}. \cr If abundance per PFG & per Strata +activated in global parameters, the function saved a temporal evolution file +per PFG & per Strata.} } \value{ A \code{list} containing three \code{data.frame} objects with the following columns : \describe{ \item{\code{PFG}}{concerned plant functional group (for abundance)} - \item{\code{STRATUM}}{concerned height stratum (for LIGHT)} + \item{\code{STRATUM}}{concerned height stratum (for LIGHT & abundance if option selected)} \item{\code{ID.pixel}}{number of the concerned pixel} \item{\code{X, Y}}{coordinates of the concerned pixel} \item{\code{HAB}}{habitat of the concerned pixel} @@ -64,14 +69,17 @@ a specific parameter file within this simulation, \strong{one to three preanalytical tables that can then be used to create graphics}. \cr \cr For each PFG and each selected simulation year, raster maps are retrieved -from the results folder \code{ABUND_perPFG_allStrata} and unzipped. +from the results folder \code{ABUND_perPFG_allStrata} and unzipped. If +\code{perStrata} = \code{TRUE}, raster maps are retrieved from the folder +\code{ABUND_perPFG_perStrata} and unzipped. Informations extracted lead to the production of one table before the maps are compressed again : \itemize{ - \item{the value of \strong{abundance for each Plant Functional Group} - for each selected simulation year(s) in every pixel in which the PFG is - present for at least one of the selected simulation year(s) \cr \cr + \item{the value of \strong{abundance for each Plant Functional Group} + for each selected simulation year(s) and, if option selected, each height + stratum in every pixel in which the PFG is present for at least one of + the selected simulation year(s) \cr \cr } } diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index 8eed0d9..ad92f2d 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -8,6 +8,7 @@ POST_FATE.validation( name.simulation, sim.version, year, + perStrata = TRUE, doHabitat = TRUE, obs.path, releves.PFG, @@ -15,9 +16,11 @@ POST_FATE.validation( hab.obs, validation.mask, studied.habitat = NULL, + list.strata.simulations = NULL, doComposition = TRUE, PFG.considered_PFG.compo, habitat.considered_PFG.compo, + strata.considered_PFG.compo, doRichness = TRUE, list.PFG, exclude.PFG = NULL @@ -30,7 +33,10 @@ POST_FATE.validation( \item{year}{year of simulation for validation.} -\item{doHabitat}{logical. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, +\item{perStrata}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. +If \code{FALSE}, PFG abundance defined for all strata (habitat & PFG composition & PFG richness validation).} + +\item{doHabitat}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, if \code{FALSE}, habitat validation module is disabled.} \item{obs.path}{the function needs observed data, please create a folder for them in your @@ -48,7 +54,10 @@ and each PFG and strata (habitat & PFG composition validation).} take into account of all habitats in the \code{hab.obs} map. Otherwise, please specify in a vector habitats that will be take into account for the validation (habitat validation).} -\item{doComposition}{logical. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, +\item{list.strata.simulations}{default \code{NULL}. A character vector which contain \code{FATE} +strata definition and correspondence with observed strata definition.} + +\item{doComposition}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, if \code{FALSE}, PFG composition validation module is disabled.} \item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered @@ -57,7 +66,11 @@ in the validation (PFG composition validation).} \item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) considered in the validation (PFG composition validation).} -\item{doRichness}{logical. Default \code{TRUE}. If \code{TRUE}, PFG richness validation module is activated, +\item{strata.considered_PFG.compo}{If \code{perStrata} = \code{FALSE}, a character vector with value "A" +(selection of one or several specific strata disabled). If \code{perStrata} = \code{TRUE}, a character +vector with at least one of the observed strata (PFG composition validation).} + +\item{doRichness}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG richness validation module is activated, if \code{FALSE}, PFG richness validation module is disabled.} \item{list.PFG}{a character vector which contain all the PFGs taken account in @@ -86,34 +99,37 @@ for a \code{FATE} simulation and computes the difference between observed and si \details{ \describe{ \item{Habitat validation}{The observed habitat is derived from a map of the area, the simulated habitat -is derived from FATE simulated relative abundance, based on a random forest -algorithm trained on observed data. To compare observations and simulations, the function -computes confusion matrix between observation and prediction and then computes the TSS -for each habitat h (number of prediction of habitat h/number of observation -of habitat h + number of non-prediction of habitat h/number of non-observation -of habitat h). The final metrics this script use is the mean of TSS per habitat over all -habitats, weighted by the share of each habitat in the observed habitat distribution.} - \item{PFG composition validation}{This code firstly run the \code{get.observed.distribution} function in order to have -a \code{obs.distri} file which contain the observed distribution per PFG, strata and habitat. -This file is also an argument for the \code{do.PFG.composition.validation} function run next. -This second sub function provide the computation of distance between observed and simulated distribution. \cr -NB : The argument \code{strata.considered_PFG.compo} is by default "A" in the 2 sub functions because -it's easier for a \code{FATE} simulation to provide PFG abundances for all strata. \cr The argument -\code{perStrata.compo} is by default \code{NULL} for the same reasons.} +is derived from \code{FATE} simulated relative abundance, based on a random forest +algorithm trained on observed releves data (see \code{\link{train.RF.habitat}}) \cr +To compare observations and simulations, the function computes confusion matrix between +observations and predictions and then compute the TSS for each habitat h +(number of prediction of habitat h/number of observation of habitat h + number of non-prediction +of habitat h/number of non-observation of habitat h). The final metrics this script use is the +mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed +habitat distribution. The habitat validation also provides a visual comparison of observed and +simulated habitat on the whole studied area (see \code{\link{do.habitat.validation}} & + \code{\link{plot.predicted.habitat}}).} \cr + \item{PFG composition validation}{This code firstly run the \code{get.observed.distribution} +function in order to have a \code{obs.distri} file which contain the observed distribution +per PFG, strata and habitat. This file is also an argument for the \code{do.PFG.composition.validation} +function run next. This second sub function provides the computation of distance between observed +and simulated distribution.} \item{PFG richness validation}{Firstly, the function updates the \code{list.PFG} with \code{exclude.PFG} vector. -Then, the script takes the abundance per PFG file from the results of the \code{FATE} -simulation and computes the difference between the \code{list.PFG} and all the PFG -which are presents in the abundance file, in order to obtain the PFG richness for a simulation. -The function also determine if an observed PFG is missing in the results of the simulation at -a specific year.} +Then, the script takes the abundance per PFG (and per strata if option selected) file from the +results of the \code{FATE} simulation and computes the difference between the \code{list.PFG} +and all the PFG which are presents in the abundance file, in order to obtain the PFG richness +for a simulation. The function also determine if an observed PFG is missing in the results of the +simulation at a specific year.} } } \examples{ ## Habitat validation --------------------------------------------------------------------------------- +list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) POST_FATE.validation(name.simulation = "FATE_Champsaur" , sim.version = "SIMUL_V4.1" , year = 2000 + , perStrata = TRUE , doHabitat = TRUE , obs.path = "FATE_Champsaur/DATA_OBS/" , releves.PFG = "releves.PFG.abundance.csv" @@ -121,15 +137,19 @@ POST_FATE.validation(name.simulation = "FATE_Champsaur" , hab.obs = "simplified.cesbio.map.grd" , validation.mask = "certain.habitat.100m.restricted.grd" , studied.habitat = NULL + , list.strata.simulations = list.strata.simulations , doComposition = FALSE , doRichness = FALSE) ## PFG composition validation -------------------------------------------------------------------------- -list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) +list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") +strata.considered_PFG.compo = c("S", "M", "B") POST_FATE.validation(name.simulation = "FATE_Champsaur" , sim.version = "SIMUL_V4.1" , year = 2000 + , perStrata = TRUE , doHabitat = FALSE , obs.path = "FATE_Champsaur/DATA_OBS/" , releves.PFG = "releves.PFG.abundance.csv" @@ -137,16 +157,19 @@ POST_FATE.validation(name.simulation = "FATE_Champsaur" , hab.obs = "simplified.cesbio.map.grd" , validation.mask = "certain.habitat.100m.restricted.grd" , studied.habitat = NULL + , list.strata.simulations = list.strata.simulations , doComposition = TRUE , PFG.considered_PFG.compo = list.PFG , habitat.considered_PFG.compo = habitat.considered + , strata.considered_PFG.compo = strata.considered_PFG.compo , doRichness = FALSE) ## PFG richness validation ----------------------------------------------------------------------------- -list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) -POST_FATE.validation(name.simulation = "FATE_CHampsaur" +list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +POST_FATE.validation(name.simulation = "FATE_Champsaur" , sim.version = "SIMUL_V4.1" , year = 2000 + , perStrata = TRUE , doHabitat = FALSE , doComposition = FALSE , doRichness = TRUE diff --git a/man/do.PFG.composition.validation.Rd b/man/do.PFG.composition.validation.Rd index f4a934b..9d35451 100644 --- a/man/do.PFG.composition.validation.Rd +++ b/man/do.PFG.composition.validation.Rd @@ -4,7 +4,7 @@ \alias{do.PFG.composition.validation} \title{Compute distance between observed and simulated distribution} \usage{ -\method{do}{PFG.composition.validation}( +do.PFG.composition.validation( name.simulation, obs.path, sim.version, @@ -13,9 +13,11 @@ strata.considered_PFG.compo, habitat.considered_PFG.compo, observed.distribution, - perStrata.compo, + perStrata, validation.mask, - year + year, + list.strata.simulations, + list.strata.releves ) } \arguments{ @@ -40,12 +42,18 @@ considered in the validation.} \item{observed.distribution}{PFG observed distribution table.} -\item{perStrata.compo}{Logical. All strata together (FALSE) or per strata (TRUE).} - \item{validation.mask}{file which contain a raster mask that specified which pixels need validation.} \item{year}{year of simulation to validate.} + +\item{list.strata.simulations}{a character vector which contain \code{FATE} +strata definition and correspondence with observed strata definition.} + +\item{list.strata.releves}{a character vector which contain the observed strata +definition, extracted from observed PFG releves.} + +\item{perStrata.compo}{Logical. All strata together (FALSE) or per strata (TRUE).} } \value{ @@ -58,9 +66,10 @@ distribution for a precise \code{FATE} simulation. } \details{ After preliminary checks, this code extract observed habitat from the \code{hab.obs} -map and, then, merge it with the simulated PFG abundance file from results of a \code{FATE} -simulation. After filtration of the required PFG, strata and habitats, the function -transform the data into relative metrics and, then, compute distribution per PFG, strata +map and, then, merge it with the simulated PFG abundance file (with or without strata definition) +from results of the \code{FATE} simulation selected with \code{sim.version}. +After filtration of the required PFG, strata and habitats, the function transforms +the data into relative metrics and, then, compute distribution per PFG, strata and habitat (if necessary). Finally, the code computes proximity between observed and simulated data, per PFG, strata and habitat. } diff --git a/man/do.habitat.validation.Rd b/man/do.habitat.validation.Rd index 28c246f..2a294af 100644 --- a/man/do.habitat.validation.Rd +++ b/man/do.habitat.validation.Rd @@ -16,7 +16,9 @@ do.habitat.validation( name.simulation, perStrata, hab.obs, - year + year, + list.strata.releves, + list.strata.simulations ) } \arguments{ @@ -34,24 +36,30 @@ studied area.} \item{simulation.map}{a raster map of the whole studied area use to check the consistency between simulation map and the observed habitat map.} -\item{predict.all.map}{a TRUE/FALSE vector. If TRUE, the script will predict +\item{predict.all.map}{\code{Logical}. If TRUE, the script will predict habitat for the whole map.} \item{sim.version}{name of the simulation to validate.} \item{name.simulation}{simulation folder name.} -\item{perStrata}{a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +\item{perStrata}{\code{Logical}. If TRUE, the PFG abundance is defined by strata in each pixel. If FALSE, PFG abundance is defined for all strata.} \item{hab.obs}{a raster map of the observed habitat in the extended studied area.} -\item{year}{year of simulation for validation.} +\item{year}{simulation year selected for validation.} + +\item{list.strata.releves}{a character vector which contain the observed strata +definition, extracted from observed PFG releves.} + +\item{list.strata.simulations}{a character vector which contain \code{FATE} +strata definition and correspondence with observed strata definition.} } \value{ -Habitat performance file -If option selected, the function returns an habitat prediction file with +Habitat performance file. \cr +If option selected, the function also returns an habitat prediction file with observed and simulated habitat for each pixel of the whole map. } \description{ @@ -61,11 +69,11 @@ for each habitat. } \details{ After several preliminary checks, the function is going to prepare the observations -database by extracting the observed habitat from a raster map. Then, for each -simulations (sim.version), the script take the evolution abundance for each PFG -and all strata file and predict the habitat for the whole map (if option selected) -thanks to the RF model. Finally, the function computes habitat performance based on -TSS for each habitat. +database by extracting the observed habitat from a raster map. Then, for the +simulation \code{sim.version}, the script take the evolution abundance for each PFG +and all strata (or for each PFG & each strata if option selected) file and predict +the habitat for the whole map (if option selected) thanks to the RF model. +Finally, the function computes habitat performance based on TSS for each habitat. } \author{ Matthieu Combaud & Maxime Delprat diff --git a/man/get.observed.distribution.Rd b/man/get.observed.distribution.Rd index 6d0c1c8..51917af 100644 --- a/man/get.observed.distribution.Rd +++ b/man/get.observed.distribution.Rd @@ -13,7 +13,7 @@ get.observed.distribution( PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, - perStrata.compo, + perStrata, sim.version ) } @@ -40,7 +40,7 @@ strata considered in the validation.} \item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) considered in the validation.} -\item{perStrata.compo}{Logical. All strata together (FALSE) or per strata (TRUE).} +\item{perStrata}{\code{Logical}. All strata together (FALSE) or per strata (TRUE).} \item{sim.version}{name of the simulation we want to validate (it works with only one \code{sim.version}).} diff --git a/man/plot.predicted.habitat.Rd b/man/plot.predicted.habitat.Rd index 381bac5..14f4615 100644 --- a/man/plot.predicted.habitat.Rd +++ b/man/plot.predicted.habitat.Rd @@ -30,7 +30,7 @@ based on a habitat prediction file. For each pixel, the habitat failure or succe is associated to a color and then, the map is built. } \details{ -The function determine true/false prediction ('failure' if false, 'success' if true) +The function determines true/false prediction ('failure' if false, 'success' if true) and prepare a dataframe containing color and habitat code. Then, the script merge the prediction dataframe with the color and code habitat dataframe. Finally, the function draw a raster map and a plot of prediction habitat over it thanks diff --git a/man/train.RF.habitat.Rd b/man/train.RF.habitat.Rd index 1e7cd04..bfd286b 100644 --- a/man/train.RF.habitat.Rd +++ b/man/train.RF.habitat.Rd @@ -4,7 +4,7 @@ \alias{train.RF.habitat} \title{Create a random forest algorithm trained on CBNA data.} \usage{ -\method{train}{RF.habitat}( +train.RF.habitat( releves.PFG, releves.sites, hab.obs, @@ -42,7 +42,7 @@ the prediction error.} \item{output.path}{access path to the for the folder where output files will be created.} -\item{perStrata}{a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +\item{perStrata}{\code{Logical}. If TRUE, the PFG abundance is defined by strata in each site. If FALSE, PFG abundance is defined for all strata.} \item{sim.version}{name of the simulation we want to validate.} From 1277847522bdec7c4da31046147eb233c11b4e71 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 18 Mar 2022 14:23:36 +0100 Subject: [PATCH 060/176] Add files via upload Update of the validation functions and the temporal evolution function with strata definition --- .../POST_FATE.temporalEvolution.html | 318 +++++++----------- docs/reference/POST_FATE.validation.html | 75 +++-- .../do.PFG.composition.validation.html | 26 +- docs/reference/do.habitat.validation.html | 32 +- docs/reference/get.observed.distribution.html | 6 +- docs/reference/plot.predicted.habitat.html | 2 +- docs/reference/train.RF.habitat.html | 5 +- 7 files changed, 216 insertions(+), 248 deletions(-) diff --git a/docs/reference/POST_FATE.temporalEvolution.html b/docs/reference/POST_FATE.temporalEvolution.html index 861ec8c..df368ef 100644 --- a/docs/reference/POST_FATE.temporalEvolution.html +++ b/docs/reference/POST_FATE.temporalEvolution.html @@ -1,72 +1,17 @@ - - - - - - - -Create tables of pixel temporal evolution of PFG abundances (and -light and soil resources if activated) for a FATE simulation — POST_FATE.temporalEvolution • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create tables of pixel temporal evolution of PFG abundances (and +light and soil resources if activated) for a FATE simulation — POST_FATE.temporalEvolution • RFate - - - - - - - - - - - - - + + -
-
- -
- -
+
@@ -213,156 +147,154 @@

Create tables of pixel temporal evolution of PFG abundances (and simulation.

-
POST_FATE.temporalEvolution(
-  name.simulation,
-  file.simulParam = NULL,
-  no_years,
-  opt.ras_habitat = NULL,
-  opt.no_CPU = 1
-)
+
+
POST_FATE.temporalEvolution(
+  name.simulation,
+  file.simulParam = NULL,
+  no_years,
+  opt.ras_habitat = NULL,
+  opt.no_CPU = 1,
+  perStrata = FALSE
+)
+
-

Arguments

- - - - - - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

no_years

an integer corresponding to the number of simulation -years that will be used to extract PFG abundance / light / soil maps

opt.ras_habitat

(optional) default NULL.
+the PARAM_SIMUL folder of the FATE simulation

+
no_years
+

an integer corresponding to the number of simulation +years that will be used to extract PFG abundance / light / soil maps

+
opt.ras_habitat
+

(optional) default NULL.
A string corresponding to the file name of a raster mask, with an -integer value within each pixel, corresponding to a specific habitat

opt.no_CPU

(optional) default 1.
The number of +integer value within each pixel, corresponding to a specific habitat

+
opt.no_CPU
+

(optional) default 1.
The number of resources that can be used to parallelize the unzip/zip of raster -files, as well as the extraction of values from raster files

+files, as well as the extraction of values from raster files

+
perStrata
+

default FALSE.
If abundance per PFG & per Strata +activated in global parameters, the function saved a temporal evolution file +per PFG & per Strata.

+
+
+

Value

+

A list containing three data.frame objects with the +following columns :

PFG
+

concerned plant functional group (for abundance)

-

Value

+
STRATUM
+

concerned height stratum (for LIGHT & abundance if option selected)

+ +
ID.pixel
+

number of the concerned pixel

+ +
X, Y
+

coordinates of the concerned pixel

+ +
HAB
+

habitat of the concerned pixel

+ +
years
+

values of the corresponding object (abundance / LIGHT + / SOIL) for each selected simulation year(s)

-

A list containing three data.frame objects with the -following columns :

-
PFG

concerned plant functional group (for abundance)

-
STRATUM

concerned height stratum (for LIGHT)

-
ID.pixel

number of the concerned pixel

-
X, Y

coordinates of the concerned pixel

-
HAB

habitat of the concerned pixel

-
years

values of the corresponding object (abundance / LIGHT - / SOIL) for each selected simulation year(s)

-
+

One to three POST_FATE_TABLE_PIXEL_evolution_[...].csv files are created :

abundance
+

always

-

One to three POST_FATE_TABLE_PIXEL_evolution_[...].csv files are created :

-
abundance

always

-
light

if light module was activated

-
soil

if soil module was activated

+
light
+

if light module was activated

-
+
soil
+

if soil module was activated

-

Details

+
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, one to three -preanalytical tables that can then be used to create graphics.

+preanalytical tables that can then be used to create graphics.

For each PFG and each selected simulation year, raster maps are retrieved -from the results folder ABUND_perPFG_allStrata and unzipped. +from the results folder ABUND_perPFG_allStrata and unzipped. If +perStrata = TRUE, raster maps are retrieved from the folder +ABUND_perPFG_perStrata and unzipped. Informations extracted lead to the production of one table before the maps are compressed again :

-
    -
  • the value of abundance for each Plant Functional Group - for each selected simulation year(s) in every pixel in which the PFG is - present for at least one of the selected simulation year(s)

  • -
- -

If the light module was activated (see -PRE_FATE.params_globalParameters), for each height stratum +

  • the value of abundance for each Plant Functional Group + for each selected simulation year(s) and, if option selected, each height + stratum in every pixel in which the PFG is present for at least one of + the selected simulation year(s)

  • +

If the light module was activated (see +PRE_FATE.params_globalParameters), for each height stratum and each selected simulation year, raster maps are retrieved from the results folder LIGHT and unzipped. Informations extracted lead to the production of one table before the maps are compressed again :

-
    -
  • the value of light resources for each height stratum for - each selected simulation year(s) in every pixel

  • -
- -

If the soil module was activated (see -PRE_FATE.params_globalParameters), for each selected +

  • the value of light resources for each height stratum for + each selected simulation year(s) in every pixel

  • +

If the soil module was activated (see +PRE_FATE.params_globalParameters), for each selected simulation year, raster maps are retrieved from the results folder SOIL and unzipped. Informations extracted lead to the production of one table before the maps are compressed again :

-
    -
  • the value of soil resources for each selected simulation - year(s) in every pixel

  • -
- -

If a raster mask for habitat has been provided, the tables will -also contain information about the pixel habitat.

+
  • the value of soil resources for each selected simulation + year(s) in every pixel

  • +

If a raster mask for habitat has been provided, the tables will +also contain information about the pixel habitat.

These .csv files can then be used by other functions :

-
+ +
+

Author

Maya Guéguen

+
+
-
- - + + diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index e0ae816..3f549d0 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -159,6 +159,7 @@

Computes validation data for habitat, PFG richness and composition for a name.simulation, sim.version, year, + perStrata = TRUE, doHabitat = TRUE, obs.path, releves.PFG, @@ -166,9 +167,11 @@

Computes validation data for habitat, PFG richness and composition for a hab.obs, validation.mask, studied.habitat = NULL, + list.strata.simulations = NULL, doComposition = TRUE, PFG.considered_PFG.compo, habitat.considered_PFG.compo, + strata.considered_PFG.compo, doRichness = TRUE, list.PFG, exclude.PFG = NULL @@ -183,8 +186,11 @@

Arguments

name of the simulation to validate (it works with only one sim.version).

year

year of simulation for validation.

+
perStrata
+

Logical. Default TRUE. If TRUE, PFG abundance is defined by strata. +If FALSE, PFG abundance defined for all strata (habitat & PFG composition & PFG richness validation).

doHabitat
-

logical. Default TRUE. If TRUE, habitat validation module is activated, +

Logical. Default TRUE. If TRUE, habitat validation module is activated, if FALSE, habitat validation module is disabled.

obs.path

the function needs observed data, please create a folder for them in your @@ -201,8 +207,11 @@

Arguments

default NULL. If NULL, the function will take into account of all habitats in the hab.obs map. Otherwise, please specify in a vector habitats that will be take into account for the validation (habitat validation).

+
list.strata.simulations
+

default NULL. A character vector which contain FATE +strata definition and correspondence with observed strata definition.

doComposition
-

logical. Default TRUE. If TRUE, PFG composition validation module is activated, +

Logical. Default TRUE. If TRUE, PFG composition validation module is activated, if FALSE, PFG composition validation module is disabled.

PFG.considered_PFG.compo

a character vector of the list of PFG considered @@ -210,8 +219,12 @@

Arguments

habitat.considered_PFG.compo

a character vector of the list of habitat(s) considered in the validation (PFG composition validation).

+
strata.considered_PFG.compo
+

If perStrata = FALSE, a character vector with value "A" +(selection of one or several specific strata disabled). If perStrata = TRUE, a character +vector with at least one of the observed strata (PFG composition validation).

doRichness
-

logical. Default TRUE. If TRUE, PFG richness validation module is activated, +

Logical. Default TRUE. If TRUE, PFG richness validation module is activated, if FALSE, PFG richness validation module is disabled.

list.PFG

a character vector which contain all the PFGs taken account in @@ -232,30 +245,31 @@

Details

Habitat validation

The observed habitat is derived from a map of the area, the simulated habitat -is derived from FATE simulated relative abundance, based on a random forest -algorithm trained on observed data. To compare observations and simulations, the function -computes confusion matrix between observation and prediction and then computes the TSS -for each habitat h (number of prediction of habitat h/number of observation -of habitat h + number of non-prediction of habitat h/number of non-observation -of habitat h). The final metrics this script use is the mean of TSS per habitat over all -habitats, weighted by the share of each habitat in the observed habitat distribution.

- +is derived from FATE simulated relative abundance, based on a random forest +algorithm trained on observed releves data (see train.RF.habitat)
+To compare observations and simulations, the function computes confusion matrix between +observations and predictions and then compute the TSS for each habitat h +(number of prediction of habitat h/number of observation of habitat h + number of non-prediction +of habitat h/number of non-observation of habitat h). The final metrics this script use is the +mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed +habitat distribution. The habitat validation also provides a visual comparison of observed and +simulated habitat on the whole studied area (see do.habitat.validation & + plot.predicted.habitat).

+
PFG composition validation
-

This code firstly run the get.observed.distribution function in order to have -a obs.distri file which contain the observed distribution per PFG, strata and habitat. -This file is also an argument for the do.PFG.composition.validation function run next. -This second sub function provide the computation of distance between observed and simulated distribution.
-NB : The argument strata.considered_PFG.compo is by default "A" in the 2 sub functions because -it's easier for a FATE simulation to provide PFG abundances for all strata.
The argument -perStrata.compo is by default NULL for the same reasons.

+

This code firstly run the get.observed.distribution +function in order to have a obs.distri file which contain the observed distribution +per PFG, strata and habitat. This file is also an argument for the do.PFG.composition.validation +function run next. This second sub function provides the computation of distance between observed +and simulated distribution.

PFG richness validation

Firstly, the function updates the list.PFG with exclude.PFG vector. -Then, the script takes the abundance per PFG file from the results of the FATE -simulation and computes the difference between the list.PFG and all the PFG -which are presents in the abundance file, in order to obtain the PFG richness for a simulation. -The function also determine if an observed PFG is missing in the results of the simulation at -a specific year.

+Then, the script takes the abundance per PFG (and per strata if option selected) file from the +results of the FATE simulation and computes the difference between the list.PFG +and all the PFG which are presents in the abundance file, in order to obtain the PFG richness +for a simulation. The function also determine if an observed PFG is missing in the results of the +simulation at a specific year.

@@ -268,9 +282,11 @@

Author

Examples


 ## Habitat validation ---------------------------------------------------------------------------------
+list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7))
 POST_FATE.validation(name.simulation = "FATE_Champsaur"
                       , sim.version = "SIMUL_V4.1"
                       , year = 2000
+                      , perStrata = TRUE
                       , doHabitat = TRUE
                       , obs.path = "FATE_Champsaur/DATA_OBS/"
                       , releves.PFG = "releves.PFG.abundance.csv"
@@ -278,15 +294,19 @@ 

Examples

, hab.obs = "simplified.cesbio.map.grd" , validation.mask = "certain.habitat.100m.restricted.grd" , studied.habitat = NULL + , list.strata.simulations = list.strata.simulations , doComposition = FALSE , doRichness = FALSE) ## PFG composition validation -------------------------------------------------------------------------- -list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) +list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") +strata.considered_PFG.compo = c("S", "M", "B") POST_FATE.validation(name.simulation = "FATE_Champsaur" , sim.version = "SIMUL_V4.1" , year = 2000 + , perStrata = TRUE , doHabitat = FALSE , obs.path = "FATE_Champsaur/DATA_OBS/" , releves.PFG = "releves.PFG.abundance.csv" @@ -294,16 +314,19 @@

Examples

, hab.obs = "simplified.cesbio.map.grd" , validation.mask = "certain.habitat.100m.restricted.grd" , studied.habitat = NULL + , list.strata.simulations = list.strata.simulations , doComposition = TRUE , PFG.considered_PFG.compo = list.PFG , habitat.considered_PFG.compo = habitat.considered + , strata.considered_PFG.compo = strata.considered_PFG.compo , doRichness = FALSE) ## PFG richness validation ----------------------------------------------------------------------------- -list.PFG<-as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) -POST_FATE.validation(name.simulation = "FATE_CHampsaur" +list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +POST_FATE.validation(name.simulation = "FATE_Champsaur" , sim.version = "SIMUL_V4.1" , year = 2000 + , perStrata = TRUE , doHabitat = FALSE , doComposition = FALSE , doRichness = TRUE diff --git a/docs/reference/do.PFG.composition.validation.html b/docs/reference/do.PFG.composition.validation.html index b2a9b5c..14f0193 100644 --- a/docs/reference/do.PFG.composition.validation.html +++ b/docs/reference/do.PFG.composition.validation.html @@ -145,8 +145,7 @@

Compute distance between observed and simulated distribution

-
# S3 method for PFG.composition.validation
-do(
+    
do.PFG.composition.validation(
   name.simulation,
   obs.path,
   sim.version,
@@ -155,9 +154,11 @@ 

Compute distance between observed and simulated distribution

strata.considered_PFG.compo, habitat.considered_PFG.compo, observed.distribution, - perStrata.compo, + perStrata, validation.mask, - year + year, + list.strata.simulations, + list.strata.releves )
@@ -184,13 +185,19 @@

Arguments

considered in the validation.

observed.distribution

PFG observed distribution table.

-
perStrata.compo
-

Logical. All strata together (FALSE) or per strata (TRUE).

validation.mask

file which contain a raster mask that specified which pixels need validation.

year

year of simulation to validate.

+
list.strata.simulations
+

a character vector which contain FATE +strata definition and correspondence with observed strata definition.

+
list.strata.releves
+

a character vector which contain the observed strata +definition, extracted from observed PFG releves.

+
perStrata.compo
+

Logical. All strata together (FALSE) or per strata (TRUE).

Value

@@ -199,9 +206,10 @@

Value

Details

After preliminary checks, this code extract observed habitat from the hab.obs -map and, then, merge it with the simulated PFG abundance file from results of a FATE -simulation. After filtration of the required PFG, strata and habitats, the function -transform the data into relative metrics and, then, compute distribution per PFG, strata +map and, then, merge it with the simulated PFG abundance file (with or without strata definition) +from results of the FATE simulation selected with sim.version. +After filtration of the required PFG, strata and habitats, the function transforms +the data into relative metrics and, then, compute distribution per PFG, strata and habitat (if necessary). Finally, the code computes proximity between observed and simulated data, per PFG, strata and habitat.

diff --git a/docs/reference/do.habitat.validation.html b/docs/reference/do.habitat.validation.html index 5914180..d6da983 100644 --- a/docs/reference/do.habitat.validation.html +++ b/docs/reference/do.habitat.validation.html @@ -146,8 +146,7 @@

Compare observed and simulated habitat of a FATE simulation

-
# S3 method for habitat.validation
-do(
+    
do.habitat.validation(
   output.path,
   RF.model,
   habitat.FATE.map,
@@ -158,7 +157,9 @@ 

Compare observed and simulated habitat of a FATE simulation name.simulation, perStrata, hab.obs, - year + year, + list.strata.releves, + list.strata.simulations )

@@ -179,35 +180,40 @@

Arguments

a raster map of the whole studied area use to check the consistency between simulation map and the observed habitat map.

predict.all.map
-

a TRUE/FALSE vector. If TRUE, the script will predict +

Logical. If TRUE, the script will predict habitat for the whole map.

sim.version

name of the simulation to validate.

name.simulation

simulation folder name.

perStrata
-

a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +

Logical. If TRUE, the PFG abundance is defined by strata in each pixel. If FALSE, PFG abundance is defined for all strata.

hab.obs

a raster map of the observed habitat in the extended studied area.

year
-

year of simulation for validation.

+

simulation year selected for validation.

+
list.strata.releves
+

a character vector which contain the observed strata +definition, extracted from observed PFG releves.

+
list.strata.simulations
+

a character vector which contain FATE +strata definition and correspondence with observed strata definition.

Value

-

Habitat performance file -If option selected, the function returns an habitat prediction file with +

Habitat performance file.
If option selected, the function also returns an habitat prediction file with observed and simulated habitat for each pixel of the whole map.

Details

After several preliminary checks, the function is going to prepare the observations -database by extracting the observed habitat from a raster map. Then, for each -simulations (sim.version), the script take the evolution abundance for each PFG -and all strata file and predict the habitat for the whole map (if option selected) -thanks to the RF model.Finally, the function compute habitat performance based on -TSS for each habitat.

+database by extracting the observed habitat from a raster map. Then, for the +simulation sim.version, the script take the evolution abundance for each PFG +and all strata (or for each PFG & each strata if option selected) file and predict +the habitat for the whole map (if option selected) thanks to the RF model. +Finally, the function computes habitat performance based on TSS for each habitat.

Author

diff --git a/docs/reference/get.observed.distribution.html b/docs/reference/get.observed.distribution.html index 94f4e09..e6fa016 100644 --- a/docs/reference/get.observed.distribution.html +++ b/docs/reference/get.observed.distribution.html @@ -150,7 +150,7 @@

Compute distribution of relative abundance over observed relevés

PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, - perStrata.compo, + perStrata, sim.version )
@@ -179,8 +179,8 @@

Arguments

habitat.considered_PFG.compo

a character vector of the list of habitat(s) considered in the validation.

-
perStrata.compo
-

Logical. All strata together (FALSE) or per strata (TRUE).

+
perStrata
+

Logical. All strata together (FALSE) or per strata (TRUE).

sim.version

name of the simulation we want to validate (it works with only one sim.version).

diff --git a/docs/reference/plot.predicted.habitat.html b/docs/reference/plot.predicted.habitat.html index 678075a..ee0045a 100644 --- a/docs/reference/plot.predicted.habitat.html +++ b/docs/reference/plot.predicted.habitat.html @@ -172,7 +172,7 @@

Value

Details

-

The function determine true/false prediction ('failure' if false, 'success' if true) +

The function determines true/false prediction ('failure' if false, 'success' if true) and prepare a dataframe containing color and habitat code. Then, the script merge the prediction dataframe with the color and code habitat dataframe. Finally, the function draw a raster map and a plot of prediction habitat over it thanks diff --git a/docs/reference/train.RF.habitat.html b/docs/reference/train.RF.habitat.html index c2d6c39..471769c 100644 --- a/docs/reference/train.RF.habitat.html +++ b/docs/reference/train.RF.habitat.html @@ -143,8 +143,7 @@

Create a random forest algorithm trained on CBNA data.

-
# S3 method for RF.habitat
-train(
+    
train.RF.habitat(
   releves.PFG,
   releves.sites,
   hab.obs,
@@ -185,7 +184,7 @@ 

Arguments

access path to the for the folder where output files will be created.

perStrata
-

a TRUE/FALSE vector. If TRUE, the PFG abundance is defined +

Logical. If TRUE, the PFG abundance is defined by strata in each site. If FALSE, PFG abundance is defined for all strata.

sim.version

name of the simulation we want to validate.

From 94b74b8b257eafcaffd45758d75164da46a14d9c Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 18 Mar 2022 14:25:57 +0100 Subject: [PATCH 061/176] Add files via upload Update of the namespace file with the updating of the validation functions and the temporal evolution function --- NAMESPACE | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 71d3d62..6225478 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -99,6 +99,7 @@ importFrom(dplyr,mutate) importFrom(dplyr,rename) importFrom(dplyr,select) importFrom(forcats,fct_expand) +importFrom(foreach,"%:%") importFrom(foreach,"%do%") importFrom(foreach,"%dopar%") importFrom(foreach,foreach) @@ -230,6 +231,7 @@ importFrom(stats,var) importFrom(stats,weighted.mean) importFrom(stringr,str_split) importFrom(stringr,str_sub) +importFrom(tidyselect,all_of) importFrom(utils,combn) importFrom(utils,download.file) importFrom(utils,install.packages) From 40cb47cd258d1345bf3a29e0af9cdad98a7d14c8 Mon Sep 17 00:00:00 2001 From: MayaGueguen Date: Mon, 21 Mar 2022 14:16:49 +0100 Subject: [PATCH 062/176] First corrections and remarks on UTILS.train_RF_habitat --- R/UTILS.train_RF_habitat.R | 211 ++++++++++++++++++++++--------------- 1 file changed, 125 insertions(+), 86 deletions(-) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 34e159c..20ca82b 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -77,26 +77,60 @@ train.RF.habitat<-function(releves.PFG cat("\n ---------- TRAIN A RANDOM FOREST MODEL ON OBSERVED DATA \n") + ############################################################################# + + ## CHECK parameter releves.PFG + if (.testParam_notDf(releves.PFG)) + { + .stopMessage_beDataframe("releves.PFG") + } else + { + releves.PFG = as.data.frame(releves.PFG) + if (nrow(releves.PFG) == 0 || ncol(releves.PFG) != 4) + { + .stopMessage_numRowCol("releves.PFG", c("sites", "PFG", "strata", "BB")) ## TODO : change colnames ? + } + ## TODO : condition on sites + ## TODO : condition on strata + ## TODO : condition on PFG + .testParam_notInValues.m("releves.PFG$BB", releves.PFG$BB, c(NA, "NA", 0, "+", "r", 1:5)) + } + ## CHECK parameter releves.sites + if (.testParam_notDf(releves.sites)) + { + .stopMessage_beDataframe("releves.sites") + } else + { + releves.sites = as.data.frame(releves.sites) + if (nrow(releves.sites) == 0 || ncol(releves.sites) != 3) + { + .stopMessage_numRowCol("releves.sites", c("sites", "x", "y")) ## TODO : change colnames ? + } + ## TODO : condition on site + } + + #1. Compute relative abundance metric ######################################### - #identify sites with wrong BB values (ie values that cannot be converted by the PRE_FATE.abundBraunBlanquet function) - releves.PFG<-filter(releves.PFG,is.element(BB,c(NA, "NA", 0, "+", "r", 1:5))) - #transformation into coverage percentage + ## TODO : Transform in real proportion (per site) releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage - if(perStrata==T){ - aggregated.releves.PFG<-aggregate(coverage~site+PFG+strata,data=releves.PFG,FUN="sum") - }else if(perStrata==F){ - aggregated.releves.PFG<-aggregate(coverage~site+PFG,data=releves.PFG,FUN="sum") - aggregated.releves.PFG$strata<-"A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) + if (perStrata == TRUE) { + mat.PFG.agg <- aggregate(coverage ~ site + PFG + strata, data = releves.PFG, FUN = "sum") + } else if (perStrata == FALSE) { + mat.PFG.agg <- aggregate(coverage ~ site + PFG, data = releves.PFG, FUN = "sum") + mat.PFG.agg$strata <- "A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) } #transformation into a relative metric (here relative.metric is relative coverage) - aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG %>% group_by(site,strata) %>% mutate(relative.metric= round(prop.table(coverage),digits = 2))) #rel is proportion of total pct_cov, not percentage - aggregated.releves.PFG$relative.metric[is.na(aggregated.releves.PFG$relative.metric)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) - aggregated.releves.PFG$coverage<-NULL + mat.PFG.agg <- + as.data.frame( + mat.PFG.agg %>% group_by(site, strata) %>% mutate(relative.metric = round(prop.table(coverage), digits = 2)) + ) #rel is proportion of total pct_cov, not percentage + mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) + mat.PFG.agg$coverage<-NULL print("releve data have been transformed into a relative metric") @@ -104,38 +138,39 @@ train.RF.habitat<-function(releves.PFG ####################### #transfo into factor to be sure to create all the combination when doing "dcast" - aggregated.releves.PFG$PFG<-as.factor(aggregated.releves.PFG$PFG) - aggregated.releves.PFG$strata<-as.factor(aggregated.releves.PFG$strata) - - aggregated.releves.PFG<-dcast(setDT(aggregated.releves.PFG),site~PFG+strata,value.var=c("relative.metric"),fill=0,drop=F) + mat.PFG.agg$PFG <- as.factor(mat.PFG.agg$PFG) + mat.PFG.agg$strata <- as.factor(mat.PFG.agg$strata) + mat.PFG.agg <- dcast(mat.PFG.agg, site ~ PFG + strata, value.var = "relative.metric", fill = 0, drop = FALSE) #3. Get habitat information ################################### #get sites coordinates - aggregated.releves.PFG<-merge(dplyr::select(releves.sites,c(site)), aggregated.releves.PFG,by="site") - + mat.PFG.agg <- merge(releves.sites, mat.PFG.agg, by = "site") ## TODO : mettre tout directement dans releves.PFG ? + #get habitat code and name - if(compareCRS(aggregated.releves.PFG,hab.obs)){ - aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) - }else{ - aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(hab.obs)) - aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) + mat.PFG.agg$code.habitat <- raster::extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) + mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] + if (nrow(mat.PFG.agg) == 0) { + ## TODO : add stop message } #correspondance habitat code/habitat name - table.habitat.releve<-levels(hab.obs)[[1]] - - aggregated.releves.PFG<-merge(aggregated.releves.PFG,dplyr::select(table.habitat.releve,c(ID,habitat)),by.x="code.habitat",by.y="ID") + ## ATTENTION ! il faut que la couche de noms du raster existe, et qu'elle s'appelle habitat... + ## TODO : soit donner en paramètre un vecteur avec les noms d'habitat, soit les données dans releves.PFG... + table.habitat.releve <- levels(hab.obs)[[1]] + mat.PFG.agg<-merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") #(optional) keep only releves data in a specific area - if(!is.null(external.training.mask)){ + if (!is.null(external.training.mask)) { + # if (compareCRS(mat.PFG.agg, external.training.mask) == FALSE) { + # #as this stage it is not a problem to transform crs(mat.PFG.agg) since we have no more merge to do (we have already extracted habitat info from the map) + # mat.PFG.agg <- st_transform(x = mat.PFG.agg, crs = crs(external.training.mask)) + # } + # mat.PFG.agg <- st_crop(x = mat.PFG.agg, y = external.training.mask) - if(compareCRS(aggregated.releves.PFG,external.training.mask)==F){ #as this stage it is not a problem to transform crs(aggregated.releves.PFG) since we have no more merge to do (we have already extracted habitat info from the map) - aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(external.training.mask)) - } - - aggregated.releves.PFG<-st_crop(x=aggregated.releves.PFG,y=external.training.mask) + val.inMask = raster::extract(x = external.training.mask, y = mat.PFG.agg[, c("x", "y")]) + mat.PFG.agg = mat.PFG.agg[which(!is.na(val.inMask)), ] print("'releve' map has been cropped to match 'external.training.mask'.") } @@ -143,24 +178,26 @@ train.RF.habitat<-function(releves.PFG # 4. Keep only releve on interesting habitat ###################################################" - if (!is.null(studied.habitat)){ - aggregated.releves.PFG<-filter(aggregated.releves.PFG,is.element(habitat,studied.habitat)) #filter non interesting habitat + NA - print(cat("habitat classes used in the RF algo: ",unique(aggregated.releves.PFG$habitat),"\n",sep="\t")) - } else{ - print(cat("habitat classes used in the RF algo: ",unique(aggregated.releves.PFG$habitat),"\n",sep="\t")) + if (!is.null(studied.habitat)) { + mat.PFG.agg <- mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] #filter non interesting habitat + NA + if (nrow(mat.PFG.agg) == 0) { + ## TODO : add stop message + } } + print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) # 5. Save data ##################### - st_write(aggregated.releves.PFG,paste0(output.path,"/HABITAT/", sim.version, "/releve.PFG.habitat.shp"),overwrite=T,append=F) - write.csv(aggregated.releves.PFG,paste0(output.path,"/HABITAT/", sim.version, "/CBNA.releves.prepared.csv"),row.names = F) + # st_write(mat.PFG.agg,paste0(output.path,"/HABITAT/", sim.version, "/releve.PFG.habitat.shp"),overwrite=T,append=F) + write.csv(mat.PFG.agg,paste0(output.path,"/HABITAT/", sim.version, "/CBNA.releves.prepared.csv"),row.names = FALSE) + ## TODO : remove CBNA from file name # 6. Small adjustment in data structure ########################################## - aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG) #get rid of the spatial structure before entering the RF process - aggregated.releves.PFG$habitat<-as.factor(aggregated.releves.PFG$habitat) + mat.PFG.agg<-as.data.frame(mat.PFG.agg) #get rid of the spatial structure before entering the RF process + mat.PFG.agg$habitat<-as.factor(mat.PFG.agg$habitat) # 7.Random forest ###################################### @@ -168,72 +205,74 @@ train.RF.habitat<-function(releves.PFG #separate the database into a training and a test part set.seed(123) - training.site<-sample(aggregated.releves.PFG$site,size=RF.param$share.training*length(aggregated.releves.PFG$site),replace = F) - releves.training<-filter(aggregated.releves.PFG,is.element(site,training.site)) - releves.testing<-filter(aggregated.releves.PFG,!is.element(site,training.site)) + training.site <- sample(mat.PFG.agg$site, size = RF.param$share.training * length(mat.PFG.agg$site), replace = FALSE) + releves.training <- mat.PFG.agg[which(mat.PFG.agg$site %in% training.site), ] + releves.testing <- mat.PFG.agg[-which(mat.PFG.agg$site %in% training.site), ] #train the model (with correction for imbalances in sampling) #run optimization algo (careful : optimization over OOB...) - mtry.perf<-as.data.frame( - tuneRF( - x=dplyr::select(releves.training,-c(code.habitat,site,habitat,geometry)), - y=releves.training$habitat, - strata=releves.training$habitat, - sampsize=nrow(releves.training), - ntreeTry=RF.param$ntree, - stepFactor=2, improve=0.05,doBest=FALSE,plot=F,trace=F - ) - ) + mtry.perf <- tuneRF(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), + y = releves.training$habitat, + strata = releves.training$habitat, + sampsize = nrow(releves.training), + ntreeTry = RF.param$ntree, + stepFactor = 2, + improve = 0.05, + doBest = FALSE, + plot = FALSE, + trace = FALSE) + mtry.perf = as.data.frame(mtry.perf) #select mtry - mtry<-mtry.perf$mtry[mtry.perf$OOBError==min(mtry.perf$OOBError)][1] #the lowest n achieving minimum OOB + mtry <- mtry.perf$mtry[mtry.perf$OOBError == min(mtry.perf$OOBError)][1] #the lowest n achieving minimum OOB #run real model - model<- randomForest( - x=dplyr::select(releves.training,-c(code.habitat,site,habitat,geometry)), - y=releves.training$habitat, - xtest=dplyr::select(releves.testing,-c(code.habitat,site,habitat,geometry)), - ytest=releves.testing$habitat, - strata=releves.training$habitat, - sampsize=nrow(releves.training), - ntree=RF.param$ntree, - mtry=mtry, - norm.votes=TRUE, - keep.forest=TRUE - ) + model <- randomForest(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), + y = releves.training$habitat, + xtest = dplyr::select(releves.testing, -c(code.habitat, site, habitat, geometry)), + ytest = releves.testing$habitat, + strata = releves.training$habitat, + sampsize = nrow(releves.training), + ntree = RF.param$ntree, + mtry = mtry, + norm.votes = TRUE, + keep.forest = TRUE) #analyse model performance # Analysis on the training sample - - confusion.training<-confusionMatrix(data=model$predicted,reference=releves.training$habitat) - - synthesis.training<-data.frame(habitat=colnames(confusion.training$table),sensitivity=confusion.training$byClass[,1],specificity=confusion.training$byClass[,2],weight=colSums(confusion.training$table)/sum(colSums(confusion.training$table))) #warning: prevalence is the weight of predicted habitat, not of observed habitat - synthesis.training<-synthesis.training%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) - - aggregate.TSS.training<-round(sum(synthesis.training$weight*synthesis.training$TSS),digits=2) + confusion.training <- confusionMatrix(data = model$predicted, reference = releves.training$habitat) + synthesis.training <- data.frame(habitat = colnames(confusion.training$table) + , sensitivity = confusion.training$byClass[, 1] + , specificity = confusion.training$byClass[, 2] + , weight = colSums(confusion.training$table) / sum(colSums(confusion.training$table))) + #warning: prevalence is the weight of predicted habitat, not of observed habitat + synthesis.training <- synthesis.training %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) + aggregate.TSS.training <- round(sum(synthesis.training$weight * synthesis.training$TSS), digits = 2) # Analysis on the testing sample - - confusion.testing<-confusionMatrix(data=model$test$predicted,reference=releves.testing$habitat) - - synthesis.testing<-data.frame(habitat=colnames(confusion.testing$table),sensitivity=confusion.testing$byClass[,1],specificity=confusion.testing$byClass[,2],weight=colSums(confusion.testing$table)/sum(colSums(confusion.testing$table)))#warning: prevalence is the weight of predicted habitat, not of observed habitat - synthesis.testing<-synthesis.testing%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) - - aggregate.TSS.testing<-round(sum(synthesis.testing$weight*synthesis.testing$TSS),digits=2) + confusion.testing <- confusionMatrix(data = model$test$predicted, reference = releves.testing$habitat) + synthesis.testing<-data.frame(habitat = colnames(confusion.testing$table) + , sensitivity = confusion.testing$byClass[, 1] + , specificity = confusion.testing$byClass[, 2] + , weight = colSums(confusion.testing$table) / sum(colSums(confusion.testing$table))) + #warning: prevalence is the weight of predicted habitat, not of observed habitat + synthesis.testing <- synthesis.testing %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) + aggregate.TSS.testing <- round(sum(synthesis.testing$weight * synthesis.testing$TSS), digits = 2) # 8. Save and return output #######################################" - write_rds(model,paste0(output.path,"/HABITAT/", sim.version, "/RF.model.rds"),compress="none") - write.csv(synthesis.training,paste0(output.path,"/HABITAT/", sim.version, "/RF_perf.per.hab_training.csv"),row.names=F) - write.csv(aggregate.TSS.training,paste0(output.path,"/HABITAT/", sim.version, "/RF_aggregate.TSS_training.csv"),row.names=F) - write.csv(synthesis.testing,paste0(output.path,"/HABITAT/", sim.version, "/RF_perf.per.hab_testing.csv"),row.names=F) - write.csv(aggregate.TSS.testing,paste0(output.path,"/HABITAT/", sim.version, "/RF_aggregate.TSS_testing.csv"),row.names=F) + path.save = paste0(output.path, "/HABITAT/", sim.version) - return(model) + write_rds(model, paste0(path.save, "/RF.model.rds"), compress = "none") + write.csv(synthesis.training, paste0(path.save, "/RF_perf.per.hab_training.csv"), row.names = FALSE) + write.csv(aggregate.TSS.training, paste0(path.save, "/RF_aggregate.TSS_training.csv"), row.names = FALSE) + write.csv(synthesis.testing, paste0(path.save, "/RF_perf.per.hab_testing.csv"), row.names = FALSE) + write.csv(aggregate.TSS.testing, paste0(path.save, "/RF_aggregate.TSS_testing.csv"), row.names = FALSE) + return(model) } From ba5289ea5b576ab3c1d61752503d7c1e9438e4cd Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Tue, 22 Mar 2022 11:28:22 +0100 Subject: [PATCH 063/176] Corrections and adjustments --- R/UTILS.train_RF_habitat.R | 40 ++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 20ca82b..cc11a78 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -14,7 +14,7 @@ ##' and each PFG and strata. ##' @param releves.sites a data frame with coordinates and a description of ##' the habitat associated with the dominant species of each site in the -##' studied map. +##' studied map. Shapefile format. ##' @param hab.obs a raster map of the observed habitat in the ##' extended studied area. ##' @param external.training.mask default \code{NULL}. (optional) Keep only @@ -60,6 +60,7 @@ ##' @importFrom caret confusionMatrix ##' @importFrom readr write_rds ##' @importFrom utils read.csv write.csv +##' @importFrom stringr str_split ##' ### END OF HEADER ############################################################## @@ -91,8 +92,22 @@ train.RF.habitat<-function(releves.PFG .stopMessage_numRowCol("releves.PFG", c("sites", "PFG", "strata", "BB")) ## TODO : change colnames ? } ## TODO : condition on sites + if (!is.numeric(releves.PFG$site)) + { + stop("Sites in releves.PFG are not in the right format. Please make sure you have numeric values") + } ## TODO : condition on strata + if (!is.character(releves.PFG$strata) | !is.numeric(releves.PFG$strata)) + { + stop("strata definition in releves.PFG is not in the right format. Please make sure you have a character or numeric values") + } ## TODO : condition on PFG + fate_PFG = .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] ## prend le premier terme de output.path qui est le nom de simul (cf POST_FATE.validation) + , abs.simulParam = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) ## fichier de paramètre associé au nom de simulation + if (sort(as.factor(unique(releves.PFG$PFG))) != as.factor(fate_PFG$PFG)) + { + stop("PFG list in releves.PFG does not correspond to PFG list in FATE") + } .testParam_notInValues.m("releves.PFG$BB", releves.PFG$BB, c(NA, "NA", 0, "+", "r", 1:5)) } ## CHECK parameter releves.sites @@ -107,6 +122,10 @@ train.RF.habitat<-function(releves.PFG .stopMessage_numRowCol("releves.sites", c("sites", "x", "y")) ## TODO : change colnames ? } ## TODO : condition on site + if (!is.numeric(releves.sites$site)) + { + stop("Sites in releves.sites are not in the right format. Please make sure you have numeric values") + } } @@ -153,13 +172,25 @@ train.RF.habitat<-function(releves.PFG mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] if (nrow(mat.PFG.agg) == 0) { ## TODO : add stop message + stop("Code habitat vector is empty. Please verify values of your hab.obs map") } #correspondance habitat code/habitat name ## ATTENTION ! il faut que la couche de noms du raster existe, et qu'elle s'appelle habitat... ## TODO : soit donner en paramètre un vecteur avec les noms d'habitat, soit les données dans releves.PFG... - table.habitat.releve <- levels(hab.obs)[[1]] - mat.PFG.agg<-merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") + if (names(raster::levels(hab.obs)[[1]]) != c("ID", "habitat", "colour") | nrow(raster::levels(hab.obs)[[1]]) == 0 & is.data.frame(obs.habitat)) + { + colnames(obs.habitat) = c("ID", "habitat") + table.habitat.releve = obs.habitat + mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") + } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0) + { + table.habitat.releve = levels(hab.obs)[[1]] + mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") + } else + { + stop("Habitat definition in hab.obs map is not correct") + } #(optional) keep only releves data in a specific area if (!is.null(external.training.mask)) { @@ -182,6 +213,7 @@ train.RF.habitat<-function(releves.PFG mat.PFG.agg <- mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] #filter non interesting habitat + NA if (nrow(mat.PFG.agg) == 0) { ## TODO : add stop message + stop("Habitats in studied.habitat parameter are not presents in hab.obs map. Please select others habitats") } } print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) @@ -190,7 +222,7 @@ train.RF.habitat<-function(releves.PFG ##################### # st_write(mat.PFG.agg,paste0(output.path,"/HABITAT/", sim.version, "/releve.PFG.habitat.shp"),overwrite=T,append=F) - write.csv(mat.PFG.agg,paste0(output.path,"/HABITAT/", sim.version, "/CBNA.releves.prepared.csv"),row.names = FALSE) + write.csv(mat.PFG.agg,paste0(output.path,"/HABITAT/", sim.version, "/obs.releves.prepared.csv"),row.names = FALSE) ## TODO : remove CBNA from file name # 6. Small adjustment in data structure From abfb871b3e3211371b71ccb176d87e9d75b0a762 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Tue, 22 Mar 2022 13:24:39 +0100 Subject: [PATCH 064/176] correction in train.RF.habitat : preliminary checks, computing relative abundance & get habitat information --- R/UTILS.train_RF_habitat.R | 48 ++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index cc11a78..c48c33f 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -89,7 +89,7 @@ train.RF.habitat<-function(releves.PFG releves.PFG = as.data.frame(releves.PFG) if (nrow(releves.PFG) == 0 || ncol(releves.PFG) != 4) { - .stopMessage_numRowCol("releves.PFG", c("sites", "PFG", "strata", "BB")) ## TODO : change colnames ? + .stopMessage_numRowCol("releves.PFG", c("site", "PFG", "strata", "BB")) ## TODO : change colnames ? } ## TODO : condition on sites if (!is.numeric(releves.PFG$site)) @@ -119,7 +119,7 @@ train.RF.habitat<-function(releves.PFG releves.sites = as.data.frame(releves.sites) if (nrow(releves.sites) == 0 || ncol(releves.sites) != 3) { - .stopMessage_numRowCol("releves.sites", c("sites", "x", "y")) ## TODO : change colnames ? + .stopMessage_numRowCol("releves.sites", c("site", "x", "y")) ## TODO : change colnames ? } ## TODO : condition on site if (!is.numeric(releves.sites$site)) @@ -134,7 +134,17 @@ train.RF.habitat<-function(releves.PFG #transformation into coverage percentage ## TODO : Transform in real proportion (per site) - releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage + # releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage + if(!is.numeric(releves.PFG$abund)) + { + releves.PFG$coverage = PRE_FATE.abundBraunBlanquet(releves.PFG$abund)/100 + } else if (is.numeric(releves.PFG$abund) & max(releves.PFG$abund) == 1) + { + releves.PFG$coverage = releves.PFG$abund + } else if (is.numeric(releves.PFG$abund)) + { + releves.PFG$coverage = releves.PFG$abund + } if (perStrata == TRUE) { mat.PFG.agg <- aggregate(coverage ~ site + PFG + strata, data = releves.PFG, FUN = "sum") @@ -178,15 +188,19 @@ train.RF.habitat<-function(releves.PFG #correspondance habitat code/habitat name ## ATTENTION ! il faut que la couche de noms du raster existe, et qu'elle s'appelle habitat... ## TODO : soit donner en paramètre un vecteur avec les noms d'habitat, soit les données dans releves.PFG... - if (names(raster::levels(hab.obs)[[1]]) != c("ID", "habitat", "colour") | nrow(raster::levels(hab.obs)[[1]]) == 0 & is.data.frame(obs.habitat)) + if (names(raster::levels(hab.obs)[[1]]) != c("ID", "habitat", "colour") | nrow(raster::levels(hab.obs)[[1]]) == 0 & !is.null(studied.habitat)) { colnames(obs.habitat) = c("ID", "habitat") - table.habitat.releve = obs.habitat + table.habitat.releve = studied.habitat + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] #filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0) + print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) + } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) { table.habitat.releve = levels(hab.obs)[[1]] mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") + mat.PFG.agg <- mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] #filter non interesting habitat + NA + print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) } else { stop("Habitat definition in hab.obs map is not correct") @@ -206,17 +220,17 @@ train.RF.habitat<-function(releves.PFG } - # 4. Keep only releve on interesting habitat - ###################################################" - - if (!is.null(studied.habitat)) { - mat.PFG.agg <- mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] #filter non interesting habitat + NA - if (nrow(mat.PFG.agg) == 0) { - ## TODO : add stop message - stop("Habitats in studied.habitat parameter are not presents in hab.obs map. Please select others habitats") - } - } - print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) + # # 4. Keep only releve on interesting habitat + # ###################################################" + # + # if (!is.null(studied.habitat)) { + # mat.PFG.agg <- mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] #filter non interesting habitat + NA + # if (nrow(mat.PFG.agg) == 0) { + # ## TODO : add stop message + # stop("Habitats in studied.habitat parameter are not presents in hab.obs map. Please select others habitats") + # } + # } + # print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) # 5. Save data ##################### From 09a85c7690b1b4996a18a9251cbd2ad9a4330a31 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Tue, 22 Mar 2022 13:52:28 +0100 Subject: [PATCH 065/176] correction of train.RF.habitat --- R/UTILS.train_RF_habitat.R | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index c48c33f..83bed67 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -10,8 +10,8 @@ ##' trained on observed PFG abundance, sites releves and a map of observed ##' habitat. ##' -##' @param releves.PFG a data frame with Braund-Blanquet abundance at each site -##' and each PFG and strata. +##' @param releves.PFG a data frame with abundance (column named abund) at each site +##' and for each PFG and strata. ##' @param releves.sites a data frame with coordinates and a description of ##' the habitat associated with the dominant species of each site in the ##' studied map. Shapefile format. @@ -20,8 +20,9 @@ ##' @param external.training.mask default \code{NULL}. (optional) Keep only ##' releves data in a specific area. ##' @param studied.habitat If \code{NULL}, the function will -##' take into account of all habitats in the hab.obs map. Otherwise, please specify -##' in a vector the habitats that we take into account for the validation. +##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +##' in a 2 columns data frame the habitats and the ID for each of them which will be taken +##' into account for the validation. ##' @param RF.param a list of 2 parameters for random forest model : ##' share.training defines the size of the trainig part of the data base. ##' ntree is the number of trees build by the algorithm, it allows to reduce @@ -34,15 +35,15 @@ ##' ##' @details ##' -##' This function transform PFG Braund-Blanquet abundance in relative abundance, -##' get habitat information from the releves map, keep only relees on interesting -##' habitat and then builds de random forest model. Finally, the function analyzes -##' the model performance with computation of confusion matrix and TSS for -##' the traning and testing sample. +##' This function transform PFG abundance in relative abundance, +##' get habitat information from the releves map of from a vector previously defined, +##' keep releves on interesting habitat and then builds a random forest model. Finally, +##' the function analyzes the model performance with computation of confusion matrix and TSS between +##' the training and testing sample. ##' ##' @return ##' -##' 2 prepared CBNA releves files are created before the building of the random +##' 2 prepared observed releves files are created before the building of the random ##' forest model in a habitat validation folder. ##' 5 more files are created at the end of the script to save the RF model and ##' the performance analyzes (confusion matrix and TSS) for the training and @@ -134,7 +135,6 @@ train.RF.habitat<-function(releves.PFG #transformation into coverage percentage ## TODO : Transform in real proportion (per site) - # releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage if(!is.numeric(releves.PFG$abund)) { releves.PFG$coverage = PRE_FATE.abundBraunBlanquet(releves.PFG$abund)/100 @@ -189,14 +189,14 @@ train.RF.habitat<-function(releves.PFG ## ATTENTION ! il faut que la couche de noms du raster existe, et qu'elle s'appelle habitat... ## TODO : soit donner en paramètre un vecteur avec les noms d'habitat, soit les données dans releves.PFG... if (names(raster::levels(hab.obs)[[1]]) != c("ID", "habitat", "colour") | nrow(raster::levels(hab.obs)[[1]]) == 0 & !is.null(studied.habitat)) - { + { ## cas où pas de levels dans la carte d'habitat et utilisation d'un vecteur d'habitat colnames(obs.habitat) = c("ID", "habitat") table.habitat.releve = studied.habitat mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] #filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) - { + { ## cas où on utilise les levels définis dans la carte table.habitat.releve = levels(hab.obs)[[1]] mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") mat.PFG.agg <- mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] #filter non interesting habitat + NA From ba8772fbee0e3c8df936af257ed6dc39ce909816 Mon Sep 17 00:00:00 2001 From: MayaGueguen Date: Tue, 22 Mar 2022 15:40:43 +0100 Subject: [PATCH 066/176] Premier check sur UTILS.do_habitat_validation --- R/UTILS.do_habitat_validation.R | 281 ++++++++++++++++---------------- 1 file changed, 138 insertions(+), 143 deletions(-) diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 95612fa..d1a5a09 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -57,7 +57,6 @@ ##' @importFrom stats aggregate ##' @importFrom stringr str_sub ##' @importFrom foreach foreach %dopar% -##' @importFrom forcats fct_expand ##' @importFrom reshape2 dcast ##' @importFrom caret confusionMatrix ##' @importFrom utils write.csv @@ -68,7 +67,10 @@ ### END OF HEADER ############################################################## -do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs, year, list.strata.releves, list.strata.simulations) { +do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask + , simulation.map, predict.all.map, sim.version, name.simulation + , perStrata, hab.obs, year, list.strata.releves, list.strata.simulations) +{ cat("\n ---------- FATE OUTPUT ANALYSIS \n") @@ -80,16 +82,16 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat ########################### #check if strata definition used in the RF model is the same as the one used to analyze FATE output - if(perStrata==T){ - if(all(base::intersect(names(list.strata.simulations), list.strata.releves)==names(list.strata.simulations))){ + if (perStrata == TRUE) { + if (all(intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))) { list.strata = names(list.strata.simulations) print("strata definition OK") - }else { + } else { stop("wrong strata definition") } - }else if(perStrata==F){ - list.strata<-"all" - }else{ + } else if (perStrata == FALSE) { + list.strata <- "all" + } else { stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } @@ -99,10 +101,11 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } #consistency between habitat.FATE.map and simulation.map - if(!compareCRS(simulation.map,habitat.FATE.map)){ - print("reprojecting habitat.FATE.map to match simulation.map crs") - habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) - } + ## MUST BE DONE before + # if(!compareCRS(simulation.map,habitat.FATE.map)){ + # print("reprojecting habitat.FATE.map to match simulation.map crs") + # habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) + # } if(!all(res(habitat.FATE.map)==res(simulation.map))){ stop("provide habitat.FATE.map with same resolution as simulation.map") } @@ -110,10 +113,11 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat print("cropping habitat.FATE.map to match simulation.map") habitat.FATE.map<-crop(x=habitat.FATE.map,y=simulation.map) } - if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ - print("setting origin habitat.FATE.map to match simulation.map") - raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) - } + ## MUST BE DONE before + # if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ + # print("setting origin habitat.FATE.map to match simulation.map") + # raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) + # } if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates stop("habitat.FATE.map could not be coerced to match simulation.map") }else{ @@ -121,24 +125,25 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } #adjust validation.mask accordingly - if(!all(res(habitat.FATE.map)==res(validation.mask))){ - validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") - } + ## MUST BE DONE before ? + # if(!all(res(habitat.FATE.map)==res(validation.mask))){ + # validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") + # } if(extent(validation.mask)!=extent(habitat.FATE.map)){ validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) } if(!compareRaster(validation.mask,habitat.FATE.map)){ stop("error in correcting validation.mask to match habitat.FATE.map") }else{ - print("validation.mask is (now) consistent with (modified) habitat.FATE.map") + print("validation.mask is (now) consistent with (modified) habitat.FATE.map") ## TODO : change message } #check consistency for PFG & strata classes between FATE output vs the RF model - RF.predictors<-rownames(RF.model$importance) - RF.PFG<-unique(str_sub(RF.predictors,1,2)) + RF.predictors <- rownames(RF.model$importance) + RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) - FATE.PFG<-str_sub(list.files(paste0(name.simulation,"/DATA/PFGS/SUCC")),6,7) + FATE.PFG<-str_sub(list.files(paste0(name.simulation,"/DATA/PFGS/SUCC")),6,7) ## TODO : careful, will not match necessarily all PFG names if(length(setdiff(FATE.PFG,RF.PFG))>0|length(setdiff(RF.PFG,FATE.PFG))>0){ stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") @@ -149,23 +154,23 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #II. Prepare database for FATE habitat ######################################################################################### - #index of the pixels in the simulation area - in.region.pixels<-which(getValues(simulation.map)==1) - #habitat df for the whole simulation area - habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) - habitat.whole.area.df<-habitat.whole.area.df[in.region.pixels,] - habitat.whole.area.df<-subset(habitat.whole.area.df, for.validation!="NA") - habitat.whole.area.df<-merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x="code.habitat", by.y="ID") - habitat.whole.area.df<-filter(habitat.whole.area.df, is.element(habitat,RF.model$classes)) + habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) + , code.habitat = getValues(habitat.FATE.map) + , for.validation = getValues(validation.mask)) + habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] #index of the pixels in the simulation area + habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] + habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") + habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] - print(cat("Habitat considered in the prediction exercise: ",c(unique(habitat.whole.area.df$habitat)),"\n",sep="\t")) + print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) print("Habitat in the simulation area:") - table(habitat.whole.area.df$habitat,useNA="always") + table(habitat.whole.area.df$habitat, useNA = "always") print("Habitat in the subpart of the simulation area used for validation:") - table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation==1],useNA="always") + table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") + ############################## # III. Loop on simulations @@ -173,127 +178,117 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat print("processing simulations") - registerDoParallel(detectCores()-2) - results.simul <- foreach(i=1:length(all_of(sim.version))) %dopar%{ - - ########################" - # III.1. Data preparation - ######################### - - #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - if(perStrata==F){ + # registerDoParallel(detectCores()-2) ## TODO : put as optional (like in zip/unzip function) + results.simul <- foreach(i = 1:length(all_of(sim.version))) %dopar% + { - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns - #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function - colnames(simu_PFG) = c("PFG", "pixel", "abs") + ########################" + # III.1. Data preparation + ######################### - } else if(perStrata==T){ + #get simulated abundance per pixel*strata*PFG for pixels in the simulation area + if (perStrata == FALSE) { + ## TODO : add test if file exists + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns + #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function + colnames(simu_PFG) = c("PFG", "pixel", "abs") + simu_PFG$strata <- "A" + + } else if (perStrata == TRUE) { + ## TODO : add test if file exists + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + new.strata <- rep(NA, nrow(simu_PFG)) + for (i in 1:length(list.strata.simulations)) { + ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) + new.strata[ind] = names(list.strata.simulations)[i] + } + simu_PFG$strata = new.strata + } - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + ## SIMILAR to what was done in train_RF_habitat for the releves + #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) + simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") - } - - #aggregate per strata group with the correspondance provided in input - simu_PFG$new.strata<-NA - - #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) - if(perStrata==F){ - simu_PFG$new.strata<-"A" - }else if(perStrata==T){ - for(i in 1:length(list.strata.simulations)){ - simu_PFG$new.strata[is.element(simu_PFG$strata, list.strata.simulations[[i]])] = names(list.strata.simulations)[i] + #transform absolute abundance into relative abundance (no pb if all combination PFG*strata are not present, since then the value is 0!) + simu_PFG <- simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance = round(prop.table(abs), digits = 2)) #those are proportions, not percentages + simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) + simu_PFG <- as.data.frame(simu_PFG) + + #drop the absolute abundance + simu_PFG$abs<-NULL + + #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) + simu_PFG$PFG<-as.factor(simu_PFG$PFG) + simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$PFG)) RF.PFG)))) + simu_PFG$strata<-as.factor(simu_PFG$strata) + simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$strata), list.strata)))) + + #cast + simu_PFG<-reshape2::dcast(simu_PFG, pixel ~ PFG * strata, value.var = c("relative.abundance"), fill = 0, drop = FALSE) + + #merge PFG info and habitat + transform habitat into factor + + #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable + data.FATE.PFG.habitat <- merge(simu_PFG, habitat.whole.area.df, by = "pixel") #at this stage we have all the pixels in the simulation area + data.FATE.PFG.habitat$habitat <- factor(data.FATE.PFG.habitat$habitat, levels = RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs + + ############################ + # III.2. Prediction of habitat with the RF algorithm + ################################# + + data.validation <- data.FATE.PFG.habitat[which(data.FATE.PFG.habitat$for.validation == 1), ] + x.validation <- dplyr::select(data.validation,all_of(RF.predictors)) ## TODO : change for classic colnames selection but with error message if not fullfilling all names ? + y.validation <- data.validation$habitat + + y.validation.predicted <- predict(object = RF.model, newdata = x.validation, type = "response", norm.votes = TRUE) + + ############################## + # III.3. Analysis of the results + ################################ + + confusion.validation <- confusionMatrix(data = y.validation.predicted + , reference = factor(y.validation, sort(unique(c(levels(y.validation)) levels(y.validation.predicted))))) + + synthesis.validation <- data.frame(habitat = colnames(confusion.validation$table) + , sensitivity = confusion.validation$byClass[, 1] + , specificity = confusion.validation$byClass[, 2] + , weight = colSums(confusion.validation$table) / sum(colSums(confusion.validation$table))) + synthesis.validation <- synthesis.validation %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) + + aggregate.TSS.validation <- round(sum(synthesis.validation$weight * synthesis.validation$TSS, na.rm = TRUE), digits = 2) + + ######################## + # III.4. Predict habitat for the whole map if option selected (do it only for a small number of simulations) + ############################################ + + if (predict.all.map == TRUE) { + y.all.map.predicted = predict(object = RF.model, newdata = dplyr::select(data.FATE.PFG.habitat, all_of(RF.predictors)), type = "response", norm.votes = TRUE) + y.all.map.predicted = as.data.frame(y.all.map.predicted) + y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel + colnames(y.all.map.predicted) = c(sim.version, "pixel") + } else { + y.all.map.predicted <- NULL } - simu_PFG$strata = NULL - } - - simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") - - #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) - simu_PFG<-aggregate(abs~pixel+strata+PFG,data=simu_PFG,FUN="sum") - - #transform absolute abundance into relative abundance (no pb if all combination PFG*strata are not present, since then the value is 0!) - simu_PFG<-simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance= round(prop.table(abs),digits=2)) #those are proportions, not percentages - simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) - simu_PFG<-as.data.frame(simu_PFG) - - #drop the absolute abundance - simu_PFG$abs<-NULL - - #set a factor structure - simu_PFG$PFG<-as.factor(simu_PFG$PFG) - simu_PFG$strata<-as.factor(simu_PFG$strata) - - #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) - simu_PFG$PFG<-fct_expand(simu_PFG$PFG,RF.PFG) - simu_PFG$strata<-fct_expand(simu_PFG$strata,list.strata) - - #cast - simu_PFG<-reshape2::dcast(simu_PFG,pixel~PFG*strata,value.var=c("relative.abundance"),fill=0,drop=F) - - #merge PFG info and habitat + transform habitat into factor - - #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable - data.FATE.PFG.habitat<-merge(simu_PFG,habitat.whole.area.df,by="pixel") #at this stage we have all the pixels in the simulation area - data.FATE.PFG.habitat$habitat<-factor(data.FATE.PFG.habitat$habitat,levels=RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs - - ############################ - # III.2. Prediction of habitat with the RF algorithm - ################################# - - data.validation<-filter(data.FATE.PFG.habitat,for.validation==1) - x.validation<-dplyr::select(data.validation,all_of(RF.predictors)) - y.validation<-data.validation$habitat - - y.validation.predicted<-predict(object=RF.model,newdata=x.validation,type="response",norm.votes=T) - - ############################## - # III.3. Analysis of the results - ################################ - - confusion.validation<-confusionMatrix(data=y.validation.predicted,reference=fct_expand(y.validation,levels(y.validation.predicted))) - - synthesis.validation<-data.frame(habitat=colnames(confusion.validation$table),sensitivity=confusion.validation$byClass[,1],specificity=confusion.validation$byClass[,2],weight=colSums(confusion.validation$table)/sum(colSums(confusion.validation$table))) - synthesis.validation<-synthesis.validation%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) - - aggregate.TSS.validation<-round(sum(synthesis.validation$weight*synthesis.validation$TSS,na.rm=T),digits=2) - - ######################## - # III.4. Predict habitat for the whole map if option selected (do it only for a small number of simulations) - ############################################ - - if(predict.all.map==T){ - y.all.map.predicted = predict(object=RF.model,newdata=dplyr::select(data.FATE.PFG.habitat,all_of(RF.predictors)),type="response",norm.votes=T) - y.all.map.predicted = as.data.frame(y.all.map.predicted) - y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel - colnames(y.all.map.predicted) = c(sim.version, "pixel") + #prepare outputs + output.validation <- c(synthesis.validation$TSS, aggregate.TSS.validation) + names(output.validation) <- c(synthesis.validation$habitat, "aggregated") - }else{ - y.all.map.predicted<-NULL + return(list(output.validation = output.validation + , y.all.map.predicted = y.all.map.predicted)) } - - #prepare outputs - - output.validation<-c(synthesis.validation$TSS,aggregate.TSS.validation) - names(output.validation)<-c(synthesis.validation$habitat,"aggregated") - - output<-list(output.validation,y.all.map.predicted) - names(output)<-c("output.validation","y.all.map.predicted") - - return(output) - } #end of the loop on simulations #deal with the results regarding model performance - habitat.performance<-as.data.frame(matrix(unlist(lapply(results.simul,"[[",1)),ncol=length(RF.model$classes)+1,byrow=T)) - names(habitat.performance)<-c(RF.model$classes,"weighted") - habitat.performance$simulation<-sim.version + habitat.performance <- as.data.frame(matrix(unlist(lapply(results.simul, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) + names(habitat.performance) <- c(RF.model$classes, "weighted") + habitat.performance$simulation <- sim.version #save - write.csv(habitat.performance,paste0(output.path,"/HABITAT/", sim.version, "/performance.habitat.csv"),row.names=F) + write.csv(habitat.performance, paste0(output.path, "/HABITAT/", sim.version, "/performance.habitat.csv"), row.names = FALSE) print("habitat performance saved") @@ -303,7 +298,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat all.map.prediction = rename(all.map.prediction,"true.habitat"="habitat") #save - write.csv(all.map.prediction,paste0(output.path,"/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names=F) + write.csv(all.map.prediction,paste0(output.path,"/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names = FALSE) #return results return(all.map.prediction) From 5c282fcb6f58c5c70df1edc6f1f321a94d69324d Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Tue, 22 Mar 2022 15:59:57 +0100 Subject: [PATCH 067/176] small adjustments --- R/UTILS.do_habitat_validation.R | 161 ++++++++++++++++---------------- 1 file changed, 80 insertions(+), 81 deletions(-) diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 95612fa..9e604bc 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -68,7 +68,7 @@ ### END OF HEADER ############################################################## -do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs, year, list.strata.releves, list.strata.simulations) { +do.habitat.validation = function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs, year, list.strata.releves, list.strata.simulations) { cat("\n ---------- FATE OUTPUT ANALYSIS \n") @@ -80,39 +80,39 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat ########################### #check if strata definition used in the RF model is the same as the one used to analyze FATE output - if(perStrata==T){ - if(all(base::intersect(names(list.strata.simulations), list.strata.releves)==names(list.strata.simulations))){ + if(perStrata == T){ + if(all(base::intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))){ list.strata = names(list.strata.simulations) print("strata definition OK") }else { stop("wrong strata definition") } - }else if(perStrata==F){ - list.strata<-"all" + }else if(perStrata == F){ + list.strata = "all" }else{ stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) - if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map)==res(validation.mask))){ + if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map) == res(validation.mask))){ stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") } #consistency between habitat.FATE.map and simulation.map - if(!compareCRS(simulation.map,habitat.FATE.map)){ + if(!compareCRS(simulation.map, habitat.FATE.map)){ print("reprojecting habitat.FATE.map to match simulation.map crs") - habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) + habitat.FATE.map = projectRaster(habitat.FATE.map, crs = crs(simulation.map)) } - if(!all(res(habitat.FATE.map)==res(simulation.map))){ + if(!all(res(habitat.FATE.map) == res(simulation.map))){ stop("provide habitat.FATE.map with same resolution as simulation.map") } - if(extent(simulation.map)!=extent(habitat.FATE.map)){ + if(extent(simulation.map) != extent(habitat.FATE.map)){ print("cropping habitat.FATE.map to match simulation.map") - habitat.FATE.map<-crop(x=habitat.FATE.map,y=simulation.map) + habitat.FATE.map = crop(x = habitat.FATE.map, y = simulation.map) } - if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ + if(!all(origin(simulation.map) == origin(habitat.FATE.map))){ print("setting origin habitat.FATE.map to match simulation.map") - raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) + raster::origin(habitat.FATE.map) = raster::origin(simulation.map) } if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates stop("habitat.FATE.map could not be coerced to match simulation.map") @@ -121,13 +121,13 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } #adjust validation.mask accordingly - if(!all(res(habitat.FATE.map)==res(validation.mask))){ - validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") + if(!all(res(habitat.FATE.map) == res(validation.mask))){ + validation.mask = projectRaster(from = validation.mask, to = habitat.FATE.map, method = "ngb") } - if(extent(validation.mask)!=extent(habitat.FATE.map)){ - validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) + if(extent(validation.mask) != extent(habitat.FATE.map)){ + validation.mask = crop(x = validation.mask, y = habitat.FATE.map) } - if(!compareRaster(validation.mask,habitat.FATE.map)){ + if(!compareRaster(validation.mask, habitat.FATE.map)){ stop("error in correcting validation.mask to match habitat.FATE.map") }else{ print("validation.mask is (now) consistent with (modified) habitat.FATE.map") @@ -135,12 +135,12 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #check consistency for PFG & strata classes between FATE output vs the RF model - RF.predictors<-rownames(RF.model$importance) - RF.PFG<-unique(str_sub(RF.predictors,1,2)) + RF.predictors = rownames(RF.model$importance) + RF.PFG = unique(str_sub(RF.predictors,1,2)) - FATE.PFG<-str_sub(list.files(paste0(name.simulation,"/DATA/PFGS/SUCC")),6,7) + FATE.PFG = str_sub(list.files(paste0(name.simulation, "/DATA/PFGS/SUCC")), 6, 7) - if(length(setdiff(FATE.PFG,RF.PFG))>0|length(setdiff(RF.PFG,FATE.PFG))>0){ + if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0){ stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") } @@ -150,147 +150,146 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat ######################################################################################### #index of the pixels in the simulation area - in.region.pixels<-which(getValues(simulation.map)==1) + in.region.pixels = which(getValues(simulation.map) == 1) #habitat df for the whole simulation area - habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) - habitat.whole.area.df<-habitat.whole.area.df[in.region.pixels,] - habitat.whole.area.df<-subset(habitat.whole.area.df, for.validation!="NA") - habitat.whole.area.df<-merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x="code.habitat", by.y="ID") - habitat.whole.area.df<-filter(habitat.whole.area.df, is.element(habitat,RF.model$classes)) + habitat.whole.area.df = data.frame(pixel = seq(from = 1, to = ncell(habitat.FATE.map), by = 1), code.habitat = getValues(habitat.FATE.map), for.validation = getValues(validation.mask)) + habitat.whole.area.df = habitat.whole.area.df[in.region.pixels,] + habitat.whole.area.df = subset(habitat.whole.area.df, for.validation != "NA") + habitat.whole.area.df = merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]], c(ID, habitat)), by.x = "code.habitat", by.y = "ID") + habitat.whole.area.df = filter(habitat.whole.area.df, is.element(habitat, RF.model$classes)) - print(cat("Habitat considered in the prediction exercise: ",c(unique(habitat.whole.area.df$habitat)),"\n",sep="\t")) + print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) print("Habitat in the simulation area:") - table(habitat.whole.area.df$habitat,useNA="always") + table(habitat.whole.area.df$habitat, useNA = "always") print("Habitat in the subpart of the simulation area used for validation:") - table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation==1],useNA="always") + table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") ############################## # III. Loop on simulations - ######################### + ############################## print("processing simulations") registerDoParallel(detectCores()-2) - results.simul <- foreach(i=1:length(all_of(sim.version))) %dopar%{ + results.simul = foreach(i = 1:length(all_of(sim.version))) %dopar%{ - ########################" + ######################### # III.1. Data preparation ######################### #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - if(perStrata==F){ + if(perStrata == F){ simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns + simu_PFG = simu_PFG[,c("PFG", "ID.pixel", paste0("X", year))] #keep only the PFG, ID.pixel and abundance at any year columns #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function colnames(simu_PFG) = c("PFG", "pixel", "abs") - } else if(perStrata==T){ + } else if(perStrata == T){ simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] + simu_PFG = simu_PFG[,c("PFG", "ID.pixel", "strata", paste0("X", year))] colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") } #aggregate per strata group with the correspondance provided in input - simu_PFG$new.strata<-NA + simu_PFG$new.strata = NA #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) - if(perStrata==F){ - simu_PFG$new.strata<-"A" - }else if(perStrata==T){ + if(perStrata == F){ + simu_PFG$new.strata = "A" + }else if(perStrata == T){ for(i in 1:length(list.strata.simulations)){ simu_PFG$new.strata[is.element(simu_PFG$strata, list.strata.simulations[[i]])] = names(list.strata.simulations)[i] } simu_PFG$strata = NULL } - simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") + simu_PFG<-dplyr::rename(simu_PFG, "strata" = "new.strata") #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) - simu_PFG<-aggregate(abs~pixel+strata+PFG,data=simu_PFG,FUN="sum") + simu_PFG = aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") #transform absolute abundance into relative abundance (no pb if all combination PFG*strata are not present, since then the value is 0!) - simu_PFG<-simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance= round(prop.table(abs),digits=2)) #those are proportions, not percentages - simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) - simu_PFG<-as.data.frame(simu_PFG) + simu_PFG = simu_PFG %>% group_by(pixel, strata) %>% mutate(relative.abundance = round(prop.table(abs), digits = 2)) #those are proportions, not percentages + simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)] = 0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) + simu_PFG = as.data.frame(simu_PFG) #drop the absolute abundance - simu_PFG$abs<-NULL + simu_PFG$abs = NULL #set a factor structure - simu_PFG$PFG<-as.factor(simu_PFG$PFG) - simu_PFG$strata<-as.factor(simu_PFG$strata) + simu_PFG$PFG = as.factor(simu_PFG$PFG) + simu_PFG$strata = as.factor(simu_PFG$strata) #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) - simu_PFG$PFG<-fct_expand(simu_PFG$PFG,RF.PFG) - simu_PFG$strata<-fct_expand(simu_PFG$strata,list.strata) + simu_PFG$PFG = fct_expand(simu_PFG$PFG, RF.PFG) + simu_PFG$strata = fct_expand(simu_PFG$strata, list.strata) #cast - simu_PFG<-reshape2::dcast(simu_PFG,pixel~PFG*strata,value.var=c("relative.abundance"),fill=0,drop=F) + simu_PFG = reshape2::dcast(simu_PFG, pixel ~ PFG * strata, value.var = c("relative.abundance"), fill = 0, drop = F) #merge PFG info and habitat + transform habitat into factor #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable - data.FATE.PFG.habitat<-merge(simu_PFG,habitat.whole.area.df,by="pixel") #at this stage we have all the pixels in the simulation area - data.FATE.PFG.habitat$habitat<-factor(data.FATE.PFG.habitat$habitat,levels=RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs + data.FATE.PFG.habitat = merge(simu_PFG, habitat.whole.area.df, by = "pixel") #at this stage we have all the pixels in the simulation area + data.FATE.PFG.habitat$habitat = factor(data.FATE.PFG.habitat$habitat, levels = RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs - ############################ + ##################################################### # III.2. Prediction of habitat with the RF algorithm - ################################# + ##################################################### - data.validation<-filter(data.FATE.PFG.habitat,for.validation==1) - x.validation<-dplyr::select(data.validation,all_of(RF.predictors)) - y.validation<-data.validation$habitat + data.validation = filter(data.FATE.PFG.habitat, for.validation == 1) + x.validation = dplyr::select(data.validation, all_of(RF.predictors)) + y.validation = data.validation$habitat - y.validation.predicted<-predict(object=RF.model,newdata=x.validation,type="response",norm.votes=T) + y.validation.predicted = predict(object = RF.model, newdata = x.validation, type = "response", norm.votes = T) - ############################## + ################################ # III.3. Analysis of the results ################################ - confusion.validation<-confusionMatrix(data=y.validation.predicted,reference=fct_expand(y.validation,levels(y.validation.predicted))) + confusion.validation = confusionMatrix(data = y.validation.predicted, reference = fct_expand(y.validation, levels(y.validation.predicted))) - synthesis.validation<-data.frame(habitat=colnames(confusion.validation$table),sensitivity=confusion.validation$byClass[,1],specificity=confusion.validation$byClass[,2],weight=colSums(confusion.validation$table)/sum(colSums(confusion.validation$table))) - synthesis.validation<-synthesis.validation%>%mutate(TSS=round(sensitivity+specificity-1,digits=2)) + synthesis.validation = data.frame(habitat = colnames(confusion.validation$table), sensitivity = confusion.validation$byClass[,1], specificity = confusion.validation$byClass[,2], weight = colSums(confusion.validation$table)/sum(colSums(confusion.validation$table))) + synthesis.validation = synthesis.validation %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) - aggregate.TSS.validation<-round(sum(synthesis.validation$weight*synthesis.validation$TSS,na.rm=T),digits=2) + aggregate.TSS.validation = round(sum(synthesis.validation$weight * synthesis.validation$TSS, na.rm=T), digits = 2) - ######################## + ############################################################################################################# # III.4. Predict habitat for the whole map if option selected (do it only for a small number of simulations) - ############################################ + ############################################################################################################# - if(predict.all.map==T){ - - y.all.map.predicted = predict(object=RF.model,newdata=dplyr::select(data.FATE.PFG.habitat,all_of(RF.predictors)),type="response",norm.votes=T) + if(predict.all.map == T){ + y.all.map.predicted = predict(object = RF.model, newdata = dplyr::select(data.FATE.PFG.habitat, all_of(RF.predictors)), type = "response", norm.votes = T) y.all.map.predicted = as.data.frame(y.all.map.predicted) y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel colnames(y.all.map.predicted) = c(sim.version, "pixel") - }else{ y.all.map.predicted<-NULL } #prepare outputs - output.validation<-c(synthesis.validation$TSS,aggregate.TSS.validation) - names(output.validation)<-c(synthesis.validation$habitat,"aggregated") + output.validation = c(synthesis.validation$TSS, aggregate.TSS.validation) + names(output.validation) = c(synthesis.validation$habitat, "aggregated") - output<-list(output.validation,y.all.map.predicted) - names(output)<-c("output.validation","y.all.map.predicted") + output = list(output.validation, y.all.map.predicted) + names(output) = c("output.validation", "y.all.map.predicted") return(output) } + #end of the loop on simulations #deal with the results regarding model performance - habitat.performance<-as.data.frame(matrix(unlist(lapply(results.simul,"[[",1)),ncol=length(RF.model$classes)+1,byrow=T)) - names(habitat.performance)<-c(RF.model$classes,"weighted") - habitat.performance$simulation<-sim.version + habitat.performance = as.data.frame(matrix(unlist(lapply(results.simul, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = T)) + names(habitat.performance) = c(RF.model$classes, "weighted") + habitat.performance$simulation = sim.version #save write.csv(habitat.performance,paste0(output.path,"/HABITAT/", sim.version, "/performance.habitat.csv"),row.names=F) @@ -300,10 +299,10 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #deal with the results regarding habitat prediction over the whole map all.map.prediction = results.simul[[1]]$y.all.map.predicted all.map.prediction = merge(all.map.prediction, dplyr::select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") - all.map.prediction = rename(all.map.prediction,"true.habitat"="habitat") + all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") #save - write.csv(all.map.prediction,paste0(output.path,"/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names=F) + write.csv(all.map.prediction, paste0(output.path, "/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names = F) #return results return(all.map.prediction) From 70ded50178cded339d16e0e6200992781de3d8f7 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Tue, 22 Mar 2022 16:54:03 +0100 Subject: [PATCH 068/176] Correction and adjustments in do.habitat.validation (maxime) --- R/UTILS.do_habitat_validation.R | 242 ++++++-------------------------- 1 file changed, 40 insertions(+), 202 deletions(-) diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index d410e12..c7f76a1 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -67,14 +67,10 @@ ### END OF HEADER ############################################################## -<<<<<<< HEAD -do.habitat.validation = function(output.path, RF.model, habitat.FATE.map, validation.mask, simulation.map, predict.all.map, sim.version, name.simulation, perStrata, hab.obs, year, list.strata.releves, list.strata.simulations) { -======= do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask , simulation.map, predict.all.map, sim.version, name.simulation , perStrata, hab.obs, year, list.strata.releves, list.strata.simulations) { ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 cat("\n ---------- FATE OUTPUT ANALYSIS \n") @@ -86,27 +82,16 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat ########################### #check if strata definition used in the RF model is the same as the one used to analyze FATE output -<<<<<<< HEAD - if(perStrata == T){ - if(all(base::intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))){ -======= if (perStrata == TRUE) { if (all(intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))) { ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 list.strata = names(list.strata.simulations) print("strata definition OK") } else { stop("wrong strata definition") } -<<<<<<< HEAD - }else if(perStrata == F){ - list.strata = "all" - }else{ -======= } else if (perStrata == FALSE) { list.strata <- "all" } else { ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } @@ -116,38 +101,23 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } #consistency between habitat.FATE.map and simulation.map -<<<<<<< HEAD - if(!compareCRS(simulation.map, habitat.FATE.map)){ - print("reprojecting habitat.FATE.map to match simulation.map crs") - habitat.FATE.map = projectRaster(habitat.FATE.map, crs = crs(simulation.map)) - } - if(!all(res(habitat.FATE.map) == res(simulation.map))){ -======= ## MUST BE DONE before # if(!compareCRS(simulation.map,habitat.FATE.map)){ # print("reprojecting habitat.FATE.map to match simulation.map crs") # habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) # } if(!all(res(habitat.FATE.map)==res(simulation.map))){ ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 stop("provide habitat.FATE.map with same resolution as simulation.map") } if(extent(simulation.map) != extent(habitat.FATE.map)){ print("cropping habitat.FATE.map to match simulation.map") habitat.FATE.map = crop(x = habitat.FATE.map, y = simulation.map) } -<<<<<<< HEAD - if(!all(origin(simulation.map) == origin(habitat.FATE.map))){ - print("setting origin habitat.FATE.map to match simulation.map") - raster::origin(habitat.FATE.map) = raster::origin(simulation.map) - } -======= ## MUST BE DONE before # if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ # print("setting origin habitat.FATE.map to match simulation.map") # raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) # } ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates stop("habitat.FATE.map could not be coerced to match simulation.map") }else{ @@ -155,20 +125,12 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } #adjust validation.mask accordingly -<<<<<<< HEAD - if(!all(res(habitat.FATE.map) == res(validation.mask))){ - validation.mask = projectRaster(from = validation.mask, to = habitat.FATE.map, method = "ngb") - } - if(extent(validation.mask) != extent(habitat.FATE.map)){ - validation.mask = crop(x = validation.mask, y = habitat.FATE.map) -======= ## MUST BE DONE before ? # if(!all(res(habitat.FATE.map)==res(validation.mask))){ # validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") # } if(extent(validation.mask)!=extent(habitat.FATE.map)){ validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 } if(!compareRaster(validation.mask, habitat.FATE.map)){ stop("error in correcting validation.mask to match habitat.FATE.map") @@ -177,18 +139,11 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } #check consistency for PFG & strata classes between FATE output vs the RF model - -<<<<<<< HEAD - RF.predictors = rownames(RF.model$importance) - RF.PFG = unique(str_sub(RF.predictors,1,2)) - - FATE.PFG = str_sub(list.files(paste0(name.simulation, "/DATA/PFGS/SUCC")), 6, 7) -======= + RF.predictors <- rownames(RF.model$importance) RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) FATE.PFG<-str_sub(list.files(paste0(name.simulation,"/DATA/PFGS/SUCC")),6,7) ## TODO : careful, will not match necessarily all PFG names ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0){ stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") @@ -199,17 +154,6 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #II. Prepare database for FATE habitat ######################################################################################### -<<<<<<< HEAD - #index of the pixels in the simulation area - in.region.pixels = which(getValues(simulation.map) == 1) - - #habitat df for the whole simulation area - habitat.whole.area.df = data.frame(pixel = seq(from = 1, to = ncell(habitat.FATE.map), by = 1), code.habitat = getValues(habitat.FATE.map), for.validation = getValues(validation.mask)) - habitat.whole.area.df = habitat.whole.area.df[in.region.pixels,] - habitat.whole.area.df = subset(habitat.whole.area.df, for.validation != "NA") - habitat.whole.area.df = merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]], c(ID, habitat)), by.x = "code.habitat", by.y = "ID") - habitat.whole.area.df = filter(habitat.whole.area.df, is.element(habitat, RF.model$classes)) -======= #habitat df for the whole simulation area habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) , code.habitat = getValues(habitat.FATE.map) @@ -218,7 +162,6 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) @@ -227,10 +170,6 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat print("Habitat in the subpart of the simulation area used for validation:") table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") -<<<<<<< HEAD -======= - ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 ############################## # III. Loop on simulations @@ -238,126 +177,18 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat print("processing simulations") -<<<<<<< HEAD - registerDoParallel(detectCores()-2) - results.simul = foreach(i = 1:length(all_of(sim.version))) %dopar%{ - - ######################### - # III.1. Data preparation - ######################### - - #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - if(perStrata == F){ - - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG", "ID.pixel", paste0("X", year))] #keep only the PFG, ID.pixel and abundance at any year columns - #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function - colnames(simu_PFG) = c("PFG", "pixel", "abs") - - } else if(perStrata == T){ - - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG", "ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") - - } - - #aggregate per strata group with the correspondance provided in input - simu_PFG$new.strata = NA - - #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) - if(perStrata == F){ - simu_PFG$new.strata = "A" - }else if(perStrata == T){ - for(i in 1:length(list.strata.simulations)){ - simu_PFG$new.strata[is.element(simu_PFG$strata, list.strata.simulations[[i]])] = names(list.strata.simulations)[i] - } - simu_PFG$strata = NULL - } - - simu_PFG<-dplyr::rename(simu_PFG, "strata" = "new.strata") - - #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) - simu_PFG = aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") - - #transform absolute abundance into relative abundance (no pb if all combination PFG*strata are not present, since then the value is 0!) - simu_PFG = simu_PFG %>% group_by(pixel, strata) %>% mutate(relative.abundance = round(prop.table(abs), digits = 2)) #those are proportions, not percentages - simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)] = 0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) - simu_PFG = as.data.frame(simu_PFG) - - #drop the absolute abundance - simu_PFG$abs = NULL - - #set a factor structure - simu_PFG$PFG = as.factor(simu_PFG$PFG) - simu_PFG$strata = as.factor(simu_PFG$strata) - - #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) - simu_PFG$PFG = fct_expand(simu_PFG$PFG, RF.PFG) - simu_PFG$strata = fct_expand(simu_PFG$strata, list.strata) - - #cast - simu_PFG = reshape2::dcast(simu_PFG, pixel ~ PFG * strata, value.var = c("relative.abundance"), fill = 0, drop = F) - - #merge PFG info and habitat + transform habitat into factor - - #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable - data.FATE.PFG.habitat = merge(simu_PFG, habitat.whole.area.df, by = "pixel") #at this stage we have all the pixels in the simulation area - data.FATE.PFG.habitat$habitat = factor(data.FATE.PFG.habitat$habitat, levels = RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs - - ##################################################### - # III.2. Prediction of habitat with the RF algorithm - ##################################################### - - data.validation = filter(data.FATE.PFG.habitat, for.validation == 1) - x.validation = dplyr::select(data.validation, all_of(RF.predictors)) - y.validation = data.validation$habitat - - y.validation.predicted = predict(object = RF.model, newdata = x.validation, type = "response", norm.votes = T) - - ################################ - # III.3. Analysis of the results - ################################ - - confusion.validation = confusionMatrix(data = y.validation.predicted, reference = fct_expand(y.validation, levels(y.validation.predicted))) - - synthesis.validation = data.frame(habitat = colnames(confusion.validation$table), sensitivity = confusion.validation$byClass[,1], specificity = confusion.validation$byClass[,2], weight = colSums(confusion.validation$table)/sum(colSums(confusion.validation$table))) - synthesis.validation = synthesis.validation %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) - - aggregate.TSS.validation = round(sum(synthesis.validation$weight * synthesis.validation$TSS, na.rm=T), digits = 2) - - ############################################################################################################# - # III.4. Predict habitat for the whole map if option selected (do it only for a small number of simulations) - ############################################################################################################# - - if(predict.all.map == T){ - y.all.map.predicted = predict(object = RF.model, newdata = dplyr::select(data.FATE.PFG.habitat, all_of(RF.predictors)), type = "response", norm.votes = T) - y.all.map.predicted = as.data.frame(y.all.map.predicted) - y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel - colnames(y.all.map.predicted) = c(sim.version, "pixel") - }else{ - y.all.map.predicted<-NULL - } - - #prepare outputs - - output.validation = c(synthesis.validation$TSS, aggregate.TSS.validation) - names(output.validation) = c(synthesis.validation$habitat, "aggregated") - - output = list(output.validation, y.all.map.predicted) - names(output) = c("output.validation", "y.all.map.predicted") - - return(output) - } - - #end of the loop on simulations - #deal with the results regarding model performance - habitat.performance = as.data.frame(matrix(unlist(lapply(results.simul, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = T)) - names(habitat.performance) = c(RF.model$classes, "weighted") - habitat.performance$simulation = sim.version -======= # registerDoParallel(detectCores()-2) ## TODO : put as optional (like in zip/unzip function) + if (opt.no_CPU > 1) + { + if (.getOS() != "windows") + { + registerDoParallel(cores = opt.no_CPU) + } else + { + warning("Parallelisation with `foreach` is not available for Windows. Sorry.") + } + } results.simul <- foreach(i = 1:length(all_of(sim.version))) %dopar% { @@ -367,24 +198,36 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #get simulated abundance per pixel*strata*PFG for pixels in the simulation area if (perStrata == FALSE) { - ## TODO : add test if file exists - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns - #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function - colnames(simu_PFG) = c("PFG", "pixel", "abs") - simu_PFG$strata <- "A" + ## TODO : add test if file exists + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))) + { + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns + #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function + colnames(simu_PFG) = c("PFG", "pixel", "abs") + simu_PFG$strata <- "A" + }else + { + stop("Simulated abundance file does not exist") + } } else if (perStrata == TRUE) { - ## TODO : add test if file exists - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") - new.strata <- rep(NA, nrow(simu_PFG)) - for (i in 1:length(list.strata.simulations)) { - ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) - new.strata[ind] = names(list.strata.simulations)[i] + ## TODO : add test if file exists + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))) + { + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + new.strata <- rep(NA, nrow(simu_PFG)) + for (i in 1:length(list.strata.simulations)) { + ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) + new.strata[ind] = names(list.strata.simulations)[i] + } + simu_PFG$strata = new.strata + }else + { + stop("Simulated abundance file does not exist") } - simu_PFG$strata = new.strata } ## SIMILAR to what was done in train_RF_habitat for the releves @@ -401,7 +244,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) simu_PFG$PFG<-as.factor(simu_PFG$PFG) - simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$PFG)) RF.PFG)))) + simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$PFG), RF.PFG)))) simu_PFG$strata<-as.factor(simu_PFG$strata) simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$strata), list.strata)))) @@ -429,7 +272,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat ################################ confusion.validation <- confusionMatrix(data = y.validation.predicted - , reference = factor(y.validation, sort(unique(c(levels(y.validation)) levels(y.validation.predicted))))) + , reference = factor(y.validation, sort(unique(c(levels(y.validation), levels(y.validation.predicted)))))) synthesis.validation <- data.frame(habitat = colnames(confusion.validation$table) , sensitivity = confusion.validation$byClass[, 1] @@ -465,7 +308,6 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat habitat.performance <- as.data.frame(matrix(unlist(lapply(results.simul, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) names(habitat.performance) <- c(RF.model$classes, "weighted") habitat.performance$simulation <- sim.version ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 #save write.csv(habitat.performance, paste0(output.path, "/HABITAT/", sim.version, "/performance.habitat.csv"), row.names = FALSE) @@ -478,11 +320,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") #save -<<<<<<< HEAD - write.csv(all.map.prediction, paste0(output.path, "/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names = F) -======= write.csv(all.map.prediction,paste0(output.path,"/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names = FALSE) ->>>>>>> ba8772fbee0e3c8df936af257ed6dc39ce909816 #return results return(all.map.prediction) From 11cf905258bc691f4540a9fe1404ff128f0340d7 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Tue, 22 Mar 2022 16:54:56 +0100 Subject: [PATCH 069/176] small adjustments --- R/POST_FATE.validation.R | 71 ++++++++++++-------- R/UTILS.plot_predicted_habitat.R | 90 ++++++++++++------------- R/UTILS.train_RF_habitat.R | 111 ++++++++++++------------------- 3 files changed, 131 insertions(+), 141 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 86c9daa..7c940a0 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -227,9 +227,9 @@ POST_FATE.validation = function(name.simulation releves.sites = st_read(paste0(obs.path, releves.sites)) hab.obs = raster(paste0(obs.path, hab.obs)) # Habitat mask at FATE simu resolution - hab.obs.modif <- projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") - habitat.FATE.map <- crop(hab.obs.modif, simulation.map) #reprojection and croping of the extended habitat map in order to have a reduced observed habitat map - validation.mask<-raster(paste0(obs.path, validation.mask)) + hab.obs.modif = projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") + habitat.FATE.map = crop(hab.obs.modif, simulation.map) #reprojection and croping of the extended habitat map in order to have a reduced observed habitat map + validation.mask = raster(paste0(obs.path, validation.mask)) # Other if(is.null(studied.habitat)){ @@ -240,13 +240,13 @@ POST_FATE.validation = function(name.simulation stop("studied.habitat is not a vector of character") } RF.param = list( - share.training=0.7, - ntree=500) - predict.all.map<-T + share.training = 0.7, + ntree = 500) + predict.all.map = T ## TRAIN A RF ON OBSERVED DATA - RF.model <- train.RF.habitat(releves.PFG = releves.PFG + RF.model = train.RF.habitat(releves.PFG = releves.PFG , releves.sites = releves.sites , hab.obs = hab.obs , external.training.mask = NULL @@ -258,7 +258,7 @@ POST_FATE.validation = function(name.simulation ## USE THE RF MODEL TO VALIDATE FATE OUTPUT - habitats.results <- do.habitat.validation(output.path = output.path + habitats.results = do.habitat.validation(output.path = output.path , RF.model = RF.model , habitat.FATE.map = habitat.FATE.map , validation.mask = validation.mask @@ -275,12 +275,12 @@ POST_FATE.validation = function(name.simulation ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT # Provide a color df - col.df<-data.frame( + col.df = data.frame( habitat = RF.model$classes, failure = terrain.colors(length(RF.model$classes), alpha = 0.5), success = terrain.colors(length(RF.model$classes), alpha = 1)) - prediction.map <- plot.predicted.habitat(predicted.habitat = habitats.results + prediction.map = plot.predicted.habitat(predicted.habitat = habitats.results , col.df = col.df , simulation.map = simulation.map , output.path = output.path @@ -356,18 +356,18 @@ POST_FATE.validation = function(name.simulation perStrata = perStrata #list of PFG of interest - list.PFG<-setdiff(list.PFG,exclude.PFG) + list.PFG = setdiff(list.PFG,exclude.PFG) registerDoParallel(detectCores()-2) - dying.PFG.list<-foreach(i=1:length(sim.version)) %dopar% { + dying.PFG.list = foreach(i=1:length(sim.version)) %dopar% { - if(perStrata==F){ + if(perStrata == F){ simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] colnames(simu_PFG) = c("PFG", "pixel", "abs") - } else if(perStrata==T){ + } else if(perStrata == T){ simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] @@ -382,22 +382,22 @@ POST_FATE.validation = function(name.simulation names(dying.PFG.list) = sim.version #get table with PFG richness - PFG.richness.df<-data.frame(simulation=names(dying.PFG.list),richness=length(list.PFG)-unlist(lapply(dying.PFG.list,FUN="length"))) + PFG.richness.df = data.frame(simulation = names(dying.PFG.list), richness = length(list.PFG) - unlist(lapply(dying.PFG.list, FUN = "length"))) #get vector with one occurence per PFG*simulation with dying of the PFG, as factor with completed levels in order to have table with all PFG, including those which never die - dyingPFG.vector<-as.factor(unlist(dying.PFG.list)) - dyingPFG.vector<-fct_expand(dyingPFG.vector,list.PFG) - dying.distribution<-round(table(dyingPFG.vector)/length(sim.version),digits=2) + dyingPFG.vector = as.factor(unlist(dying.PFG.list)) + dyingPFG.vector = fct_expand(dyingPFG.vector, list.PFG) + dying.distribution = round(table(dyingPFG.vector)/length(sim.version), digits = 2) #output - output = list(PFG.richness.df, dying.distribution ,dying.PFG.list) - names(output)<-c("PFG.richness.df","dying.distribution","dying.PFG.list") + output = list(PFG.richness.df, dying.distribution , dying.PFG.list) + names(output) = c("PFG.richness.df", "dying.distribution", "dying.PFG.list") dir.create(output.path,recursive = TRUE, showWarnings = FALSE) - write.csv(PFG.richness.df,paste0(output.path,"/performance.richness.csv"),row.names=F) - write.csv(dying.distribution,paste0(output.path,"/PFG.extinction.frequency.csv"),row.names=F) - write_rds(dying.PFG.list,file=paste0(output.path,"/dying.PFG.list.rds"),compress="none") + write.csv(PFG.richness.df, paste0(output.path, "/performance.richness.csv"), row.names = F) + write.csv(dying.distribution, paste0(output.path, "/PFG.extinction.frequency.csv"), row.names = F) + write_rds(dying.PFG.list, file = paste0(output.path, "/dying.PFG.list.rds"), compress = "none") } @@ -406,24 +406,43 @@ POST_FATE.validation = function(name.simulation cat("\n #------------------------------------------------------------# \n") if(doRichness == TRUE){ + cat("\n ---------- PFG RICHNESS : \n") cat(paste0("\n Richness at year ", year, " : ", output[[1]][2], "\n")) cat(paste0("\n Number of PFG extinction at year ", year, " : ", sum(output[[2]]), "\n")) - } else{cat("\n ---------- PFG RICHNESS VALIDATION DISABLED \n") + + } else{ + + cat("\n ---------- PFG RICHNESS VALIDATION DISABLED \n") + } + if(doHabitat == TRUE){ + hab.pred = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/", sim.version, "/hab.pred.csv")) failure = as.numeric((table(hab.pred$prediction.code)[1]/sum(table(hab.pred$prediction.code)))*100) success = as.numeric((table(hab.pred$prediction.code)[2]/sum(table(hab.pred$prediction.code)))*100) + cat("\n ---------- HABITAT : \n") cat(paste0("\n", round(failure, digits = 2), "% of habitats are not correctly predicted by ", sim.version, " \n")) cat(paste0("\n", round(success, digits = 2), "% of habitats are correctly predicted by ", sim.version, " \n")) plot(prediction.map) - } else{cat("\n ---------- HABITAT VALIDATION DISABLED \n") + + } else{ + + cat("\n ---------- HABITAT VALIDATION DISABLED \n") + } + if(doComposition == TRUE){ + cat("\n ---------- PFG COMPOSITION : \n") return(performance.composition) - } else{cat("\n ---------- PFG COMPOSITION VALIDATION DISABLED \n") + + } else{ + + cat("\n ---------- PFG COMPOSITION VALIDATION DISABLED \n") + } + } diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 7e3e65e..10cb65b 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -46,7 +46,7 @@ ### END OF HEADER ############################################################## -plot.predicted.habitat<-function(predicted.habitat +plot.predicted.habitat = function(predicted.habitat , col.df , simulation.map , output.path @@ -56,94 +56,94 @@ plot.predicted.habitat<-function(predicted.habitat cat("\n ---------- AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT \n") #auxiliary function to compute the proportion of simulations lead to the modal prediction - count.habitat<-function(df){ - index<-which(names(df)=="modal.predicted.habitat") - prop.simu<-sum(df[-index]==as.character(df[index]))/(length(names(df))-1) + count.habitat = function(df){ + index = which(names(df) == "modal.predicted.habitat") + prop.simu = sum(df[-index] == as.character(df[index]))/(length(names(df))-1) return(prop.simu) } #compute modal predicted habitat and the proportion of simulations predicting this habitat (for each pixel) - predicted.habitat$modal.predicted.habitat<-apply(dplyr::select(predicted.habitat,sim.version),1,Mode) - predicted.habitat$modal.predicted.habitat[predicted.habitat$modal.predicted.habitat==">1 mode"]<-"ambiguous" - predicted.habitat$confidence<-apply(dplyr::select(predicted.habitat,c(all_of(sim.version),modal.predicted.habitat)),1,FUN=function(x) count.habitat(x)) + predicted.habitat$modal.predicted.habitat = apply(dplyr::select(predicted.habitat, sim.version), 1, Mode) + predicted.habitat$modal.predicted.habitat[predicted.habitat$modal.predicted.habitat == ">1 mode"] = "ambiguous" + predicted.habitat$confidence <- apply(dplyr::select(predicted.habitat, c(all_of(sim.version), modal.predicted.habitat)), 1 , FUN = function(x) count.habitat(x)) #true/false prediction - predicted.habitat$prediction.code<-"failure" - predicted.habitat$prediction.code[predicted.habitat$modal.predicted.habitat==predicted.habitat$true.habitat]<-"success" + predicted.habitat$prediction.code = "failure" + predicted.habitat$prediction.code[predicted.habitat$modal.predicted.habitat == predicted.habitat$true.habitat] = "success" #prepare a df containing color & habitat code (to facilitate conversion into raster) - col.df.long<-data.table::melt(data=setDT(col.df),id.vars="habitat",variable.name="prediction.code",value.name="color") + col.df.long = data.table::melt(data = setDT(col.df), id.vars = "habitat", variable.name = "prediction.code", value.name = "color") - habitat.code.df<-unique(dplyr::select(predicted.habitat,c(modal.predicted.habitat,prediction.code))) - habitat.code.df$habitat.code<-seq(from=1,to=dim(habitat.code.df)[1],by=1) - habitat.code.df<-rename(habitat.code.df,"habitat"="modal.predicted.habitat") + habitat.code.df = unique(dplyr::select(predicted.habitat, c(modal.predicted.habitat, prediction.code))) + habitat.code.df$habitat.code = seq(from = 1, to = dim(habitat.code.df)[1], by = 1) + habitat.code.df = rename(habitat.code.df, "habitat" = "modal.predicted.habitat") - habitat.code.df<-merge(habitat.code.df,col.df.long,by=c("habitat","prediction.code")) - habitat.code.df$label<-paste0(habitat.code.df$habitat," (",habitat.code.df$prediction.code,")") + habitat.code.df = merge(habitat.code.df, col.df.long, by = c("habitat", "prediction.code")) + habitat.code.df$label = paste0(habitat.code.df$habitat, " (", habitat.code.df$prediction.code, ")") #deal with out of scope habitat - out.of.scope<-data.frame(habitat="out.of.scope",prediction.code="",habitat.code=0,color="white",label="out of scope") - habitat.code.df<-rbind(habitat.code.df,out.of.scope) + out.of.scope = data.frame(habitat = "out.of.scope", prediction.code = "", habitat.code = 0, color = "white", label = "out of scope") + habitat.code.df = rbind(habitat.code.df, out.of.scope) - habitat.code.df$label<-as.factor(habitat.code.df$label) + habitat.code.df$label = as.factor(habitat.code.df$label) #order the df - habitat.code.df<-habitat.code.df[order(habitat.code.df$label),] #to be sure it's in te right order (useful to ensure the correspondance between label and color in the ggplot function) + habitat.code.df = habitat.code.df[order(habitat.code.df$label),] #to be sure it's in te right order (useful to ensure the correspondance between label and color in the ggplot function) #merge the prediction df with the df containing color and habitat code - predicted.habitat<-merge(predicted.habitat,habitat.code.df,by.x=c("modal.predicted.habitat","prediction.code"),by.y=c("habitat","prediction.code")) + predicted.habitat = merge(predicted.habitat, habitat.code.df, by.x = c("modal.predicted.habitat", "prediction.code"), by.y = c("habitat", "prediction.code")) write.csv(x = predicted.habitat, file = paste0(output.path, "/HABITAT/", sim.version, "/hab.pred.csv")) #plot #prepare raster - prediction.map<-raster(nrows=nrow(simulation.map),ncols=ncol(simulation.map),crs=crs(simulation.map),ext=extent(simulation.map), resolution=res(simulation.map)) + prediction.map = raster(nrows = nrow(simulation.map), ncols = ncol(simulation.map), crs = crs(simulation.map), ext = extent(simulation.map), resolution = res(simulation.map)) - prediction.map[]<-0 #initialization of the raster, corresponding to "out of scope habitats" - prediction.map[predicted.habitat$pixel]<-predicted.habitat$habitat.code + prediction.map[] = 0 #initialization of the raster, corresponding to "out of scope habitats" + prediction.map[predicted.habitat$pixel] = predicted.habitat$habitat.code #ratify - prediction.map<-ratify(prediction.map) - prediction.map.rat<-levels(prediction.map)[[1]] - prediction.map.rat<-merge(prediction.map.rat,habitat.code.df,by.x="ID",by.y="habitat.code") - levels(prediction.map)<-prediction.map.rat + prediction.map = ratify(prediction.map) + prediction.map.rat = levels(prediction.map)[[1]] + prediction.map.rat = merge(prediction.map.rat, habitat.code.df, by.x = "ID", by.y = "habitat.code") + levels(prediction.map) = prediction.map.rat #save the raster - writeRaster(prediction.map,filename = paste0(output.path,"/HABITAT/", sim.version, "/synthetic.prediction.grd"),overwrite=T) + writeRaster(prediction.map, filename = paste0(output.path, "/HABITAT/", sim.version, "/synthetic.prediction.grd"), overwrite = T) #plot on R #convert into xy - xy.prediction<-as.data.frame(prediction.map,xy=T) - names(xy.prediction)<-c("x","y","habitat","prediction.code","color","label") - xy.prediction<-xy.prediction[complete.cases(xy.prediction),] + xy.prediction = as.data.frame(prediction.map, xy = T) + names(xy.prediction) = c("x", "y", "habitat", "prediction.code", "color", "label") + xy.prediction = xy.prediction[complete.cases(xy.prediction),] #plot - prediction.plot<- - ggplot(xy.prediction, aes(x=x, y=y, fill=factor(label)))+ + prediction.plot = + ggplot(xy.prediction, aes(x = x, y = y, fill = factor(label))) + geom_raster(show.legend = T) + - coord_equal()+ - scale_fill_manual(values = as.character(habitat.code.df$color))+ #ok only if habitat.code.df has been ordered according to "label" - ggtitle(paste0("Modal prediction over ",length(sim.version)," simulations"))+ - guides(fill=guide_legend(nrow=4,byrow=F))+ + coord_equal() + + scale_fill_manual(values = as.character(habitat.code.df$color)) + #ok only if habitat.code.df has been ordered according to "label" + ggtitle(paste0("Modal prediction over ", length(sim.version), " simulations")) + + guides(fill = guide_legend(nrow = 4, byrow = F)) + theme( plot.title = element_text(size = 8), - legend.text = element_text(size = 8, colour ="black"), + legend.text = element_text(size = 8, colour = "black"), legend.title = element_blank(), legend.position = "bottom", - axis.title.x=element_blank(), - axis.text.x=element_blank(), - axis.ticks.x=element_blank(), - axis.title.y=element_blank(), - axis.text.y=element_blank(), - axis.ticks.y=element_blank() + axis.title.x = element_blank(), + axis.text.x = element_blank(), + axis.ticks.x = element_blank(), + axis.title.y = element_blank(), + axis.text.y = element_blank(), + axis.ticks.y = element_blank() ) #save the map - ggsave(filename="synthetic.prediction.png",plot = prediction.plot,path = paste0(output.path, "/HABITAT/", sim.version),scale = 1,dpi = 300,limitsize = F,width = 15,height = 15,units ="cm") + ggsave(filename = "synthetic.prediction.png", plot = prediction.plot, path = paste0(output.path, "/HABITAT/", sim.version), scale = 1, dpi = 300, limitsize = F, width = 15, height = 15, units ="cm") #return the map return(prediction.plot) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 83bed67..123ecb2 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -66,11 +66,11 @@ ### END OF HEADER ############################################################## -train.RF.habitat<-function(releves.PFG +train.RF.habitat = function(releves.PFG , releves.sites , hab.obs , external.training.mask = NULL - , studied.habitat + , studied.habitat = NULL , RF.param , output.path , perStrata @@ -90,21 +90,18 @@ train.RF.habitat<-function(releves.PFG releves.PFG = as.data.frame(releves.PFG) if (nrow(releves.PFG) == 0 || ncol(releves.PFG) != 4) { - .stopMessage_numRowCol("releves.PFG", c("site", "PFG", "strata", "BB")) ## TODO : change colnames ? + .stopMessage_numRowCol("releves.PFG", c("site", "PFG", "strata", "BB")) } - ## TODO : condition on sites if (!is.numeric(releves.PFG$site)) { stop("Sites in releves.PFG are not in the right format. Please make sure you have numeric values") } - ## TODO : condition on strata if (!is.character(releves.PFG$strata) | !is.numeric(releves.PFG$strata)) { stop("strata definition in releves.PFG is not in the right format. Please make sure you have a character or numeric values") } - ## TODO : condition on PFG - fate_PFG = .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] ## prend le premier terme de output.path qui est le nom de simul (cf POST_FATE.validation) - , abs.simulParam = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) ## fichier de paramètre associé au nom de simulation + fate_PFG = .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] + , abs.simulParam = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) if (sort(as.factor(unique(releves.PFG$PFG))) != as.factor(fate_PFG$PFG)) { stop("PFG list in releves.PFG does not correspond to PFG list in FATE") @@ -120,9 +117,8 @@ train.RF.habitat<-function(releves.PFG releves.sites = as.data.frame(releves.sites) if (nrow(releves.sites) == 0 || ncol(releves.sites) != 3) { - .stopMessage_numRowCol("releves.sites", c("site", "x", "y")) ## TODO : change colnames ? + .stopMessage_numRowCol("releves.sites", c("site", "x", "y")) } - ## TODO : condition on site if (!is.numeric(releves.sites$site)) { stop("Sites in releves.sites are not in the right format. Please make sure you have numeric values") @@ -134,32 +130,31 @@ train.RF.habitat<-function(releves.PFG ######################################### #transformation into coverage percentage - ## TODO : Transform in real proportion (per site) - if(!is.numeric(releves.PFG$abund)) + if(!is.numeric(releves.PFG$abund)) # Braun-Blanquet abundance { releves.PFG$coverage = PRE_FATE.abundBraunBlanquet(releves.PFG$abund)/100 - } else if (is.numeric(releves.PFG$abund) & max(releves.PFG$abund) == 1) + } else if (is.numeric(releves.PFG$abund) & max(releves.PFG$abund) == 1) # presence-absence data { releves.PFG$coverage = releves.PFG$abund - } else if (is.numeric(releves.PFG$abund)) + } else if (is.numeric(releves.PFG$abund)) # absolute abundance { releves.PFG$coverage = releves.PFG$abund } if (perStrata == TRUE) { - mat.PFG.agg <- aggregate(coverage ~ site + PFG + strata, data = releves.PFG, FUN = "sum") + mat.PFG.agg = aggregate(coverage ~ site + PFG + strata, data = releves.PFG, FUN = "sum") } else if (perStrata == FALSE) { - mat.PFG.agg <- aggregate(coverage ~ site + PFG, data = releves.PFG, FUN = "sum") - mat.PFG.agg$strata <- "A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) + mat.PFG.agg = aggregate(coverage ~ site + PFG, data = releves.PFG, FUN = "sum") + mat.PFG.agg$strata = "A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) } #transformation into a relative metric (here relative.metric is relative coverage) - mat.PFG.agg <- + mat.PFG.agg = as.data.frame( mat.PFG.agg %>% group_by(site, strata) %>% mutate(relative.metric = round(prop.table(coverage), digits = 2)) ) #rel is proportion of total pct_cov, not percentage mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) - mat.PFG.agg$coverage<-NULL + mat.PFG.agg$coverage = NULL print("releve data have been transformed into a relative metric") @@ -167,40 +162,37 @@ train.RF.habitat<-function(releves.PFG ####################### #transfo into factor to be sure to create all the combination when doing "dcast" - mat.PFG.agg$PFG <- as.factor(mat.PFG.agg$PFG) - mat.PFG.agg$strata <- as.factor(mat.PFG.agg$strata) - mat.PFG.agg <- dcast(mat.PFG.agg, site ~ PFG + strata, value.var = "relative.metric", fill = 0, drop = FALSE) + mat.PFG.agg$PFG = as.factor(mat.PFG.agg$PFG) + mat.PFG.agg$strata = as.factor(mat.PFG.agg$strata) + mat.PFG.agg = dcast(mat.PFG.agg, site ~ PFG + strata, value.var = "relative.metric", fill = 0, drop = FALSE) #3. Get habitat information ################################### #get sites coordinates - mat.PFG.agg <- merge(releves.sites, mat.PFG.agg, by = "site") ## TODO : mettre tout directement dans releves.PFG ? + mat.PFG.agg = merge(releves.sites, mat.PFG.agg, by = "site") #get habitat code and name - mat.PFG.agg$code.habitat <- raster::extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) + mat.PFG.agg$code.habitat = raster::extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] if (nrow(mat.PFG.agg) == 0) { - ## TODO : add stop message stop("Code habitat vector is empty. Please verify values of your hab.obs map") } - #correspondance habitat code/habitat name - ## ATTENTION ! il faut que la couche de noms du raster existe, et qu'elle s'appelle habitat... - ## TODO : soit donner en paramètre un vecteur avec les noms d'habitat, soit les données dans releves.PFG... + #correspondence habitat code/habitat name if (names(raster::levels(hab.obs)[[1]]) != c("ID", "habitat", "colour") | nrow(raster::levels(hab.obs)[[1]]) == 0 & !is.null(studied.habitat)) - { ## cas où pas de levels dans la carte d'habitat et utilisation d'un vecteur d'habitat + { # cas où pas de levels dans la carte d'habitat et utilisation d'un vecteur d'habitat colnames(obs.habitat) = c("ID", "habitat") table.habitat.releve = studied.habitat - mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] #filter non interesting habitat + NA + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) - { ## cas où on utilise les levels définis dans la carte + { # cas où on utilise les levels définis dans la carte table.habitat.releve = levels(hab.obs)[[1]] mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - mat.PFG.agg <- mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] #filter non interesting habitat + NA - print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] + print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) } else { stop("Habitat definition in hab.obs map is not correct") @@ -208,42 +200,21 @@ train.RF.habitat<-function(releves.PFG #(optional) keep only releves data in a specific area if (!is.null(external.training.mask)) { - # if (compareCRS(mat.PFG.agg, external.training.mask) == FALSE) { - # #as this stage it is not a problem to transform crs(mat.PFG.agg) since we have no more merge to do (we have already extracted habitat info from the map) - # mat.PFG.agg <- st_transform(x = mat.PFG.agg, crs = crs(external.training.mask)) - # } - # mat.PFG.agg <- st_crop(x = mat.PFG.agg, y = external.training.mask) - val.inMask = raster::extract(x = external.training.mask, y = mat.PFG.agg[, c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(val.inMask)), ] print("'releve' map has been cropped to match 'external.training.mask'.") } - - # # 4. Keep only releve on interesting habitat - # ###################################################" - # - # if (!is.null(studied.habitat)) { - # mat.PFG.agg <- mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] #filter non interesting habitat + NA - # if (nrow(mat.PFG.agg) == 0) { - # ## TODO : add stop message - # stop("Habitats in studied.habitat parameter are not presents in hab.obs map. Please select others habitats") - # } - # } - # print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) - # 5. Save data ##################### - # st_write(mat.PFG.agg,paste0(output.path,"/HABITAT/", sim.version, "/releve.PFG.habitat.shp"),overwrite=T,append=F) write.csv(mat.PFG.agg,paste0(output.path,"/HABITAT/", sim.version, "/obs.releves.prepared.csv"),row.names = FALSE) - ## TODO : remove CBNA from file name # 6. Small adjustment in data structure ########################################## - mat.PFG.agg<-as.data.frame(mat.PFG.agg) #get rid of the spatial structure before entering the RF process - mat.PFG.agg$habitat<-as.factor(mat.PFG.agg$habitat) + mat.PFG.agg = as.data.frame(mat.PFG.agg) #get rid of the spatial structure before entering the RF process + mat.PFG.agg$habitat = as.factor(mat.PFG.agg$habitat) # 7.Random forest ###################################### @@ -251,14 +222,14 @@ train.RF.habitat<-function(releves.PFG #separate the database into a training and a test part set.seed(123) - training.site <- sample(mat.PFG.agg$site, size = RF.param$share.training * length(mat.PFG.agg$site), replace = FALSE) - releves.training <- mat.PFG.agg[which(mat.PFG.agg$site %in% training.site), ] - releves.testing <- mat.PFG.agg[-which(mat.PFG.agg$site %in% training.site), ] + training.site = sample(mat.PFG.agg$site, size = RF.param$share.training * length(mat.PFG.agg$site), replace = FALSE) + releves.training = mat.PFG.agg[which(mat.PFG.agg$site %in% training.site), ] + releves.testing = mat.PFG.agg[-which(mat.PFG.agg$site %in% training.site), ] #train the model (with correction for imbalances in sampling) #run optimization algo (careful : optimization over OOB...) - mtry.perf <- tuneRF(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), + mtry.perf = tuneRF(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), y = releves.training$habitat, strata = releves.training$habitat, sampsize = nrow(releves.training), @@ -271,10 +242,10 @@ train.RF.habitat<-function(releves.PFG mtry.perf = as.data.frame(mtry.perf) #select mtry - mtry <- mtry.perf$mtry[mtry.perf$OOBError == min(mtry.perf$OOBError)][1] #the lowest n achieving minimum OOB + mtry = mtry.perf$mtry[mtry.perf$OOBError == min(mtry.perf$OOBError)][1] #the lowest n achieving minimum OOB #run real model - model <- randomForest(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), + model = randomForest(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), y = releves.training$habitat, xtest = dplyr::select(releves.testing, -c(code.habitat, site, habitat, geometry)), ytest = releves.testing$habitat, @@ -288,24 +259,24 @@ train.RF.habitat<-function(releves.PFG #analyse model performance # Analysis on the training sample - confusion.training <- confusionMatrix(data = model$predicted, reference = releves.training$habitat) - synthesis.training <- data.frame(habitat = colnames(confusion.training$table) + confusion.training = confusionMatrix(data = model$predicted, reference = releves.training$habitat) + synthesis.training = data.frame(habitat = colnames(confusion.training$table) , sensitivity = confusion.training$byClass[, 1] , specificity = confusion.training$byClass[, 2] , weight = colSums(confusion.training$table) / sum(colSums(confusion.training$table))) #warning: prevalence is the weight of predicted habitat, not of observed habitat - synthesis.training <- synthesis.training %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) - aggregate.TSS.training <- round(sum(synthesis.training$weight * synthesis.training$TSS), digits = 2) + synthesis.training = synthesis.training %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) + aggregate.TSS.training = round(sum(synthesis.training$weight * synthesis.training$TSS), digits = 2) # Analysis on the testing sample - confusion.testing <- confusionMatrix(data = model$test$predicted, reference = releves.testing$habitat) - synthesis.testing<-data.frame(habitat = colnames(confusion.testing$table) + confusion.testing = confusionMatrix(data = model$test$predicted, reference = releves.testing$habitat) + synthesis.testing = data.frame(habitat = colnames(confusion.testing$table) , sensitivity = confusion.testing$byClass[, 1] , specificity = confusion.testing$byClass[, 2] , weight = colSums(confusion.testing$table) / sum(colSums(confusion.testing$table))) #warning: prevalence is the weight of predicted habitat, not of observed habitat - synthesis.testing <- synthesis.testing %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) - aggregate.TSS.testing <- round(sum(synthesis.testing$weight * synthesis.testing$TSS), digits = 2) + synthesis.testing = synthesis.testing %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) + aggregate.TSS.testing = round(sum(synthesis.testing$weight * synthesis.testing$TSS), digits = 2) # 8. Save and return output From a3b2c06952d62a02900e0042a91dbacfe9d88b22 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Wed, 23 Mar 2022 15:53:28 +0100 Subject: [PATCH 070/176] Corrections of validation functions (23/03) --- R/POST_FATE.validation.R | 186 ++++++++++++----- R/UTILS.do_PFG_composition_validation.R | 264 +++++++++++------------- R/UTILS.do_habitat_validation.R | 83 +++----- R/UTILS.get_observed_distribution.R | 149 +++++++------ R/UTILS.train_RF_habitat.R | 14 +- 5 files changed, 382 insertions(+), 314 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 7c940a0..0ed228d 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -21,22 +21,26 @@ ##' @param year year of simulation for validation. ##' @param perStrata \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. ##' If \code{FALSE}, PFG abundance defined for all strata (habitat & PFG composition & PFG richness validation). +##' @param opt.no_CPU default \code{1}. \cr The number of resources that can be used to +##' parallelize the computation of prediction performance for habitat & richness validation. +##' ##' @param doHabitat \code{Logical}. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, ##' if \code{FALSE}, habitat validation module is disabled. -##' @param obs.path the function needs observed data, please create a folder for them in your -##' simulation folder and then indicate in this parameter the access path to this new folder (habitat & PFG composition validation). -##' @param releves.PFG name of file which contain the observed Braund-Blanquet abundance at each site -##' and each PFG and strata (habitat & PFG composition validation). -##' @param releves.site name of the file which contain coordinates and a description of -##' the habitat associated with the dominant species of each site in the studied map (habitat & PFG composition validation). -##' @param hab.obs name of the file which contain the extended studied map in the simulation (habitat & PFG composition validation). -##' @param validation.mask name of the file which contain a raster mask that specified which pixels need validation -##' (habitat & PFG composition validation). +##' @param releves.PFG a data frame with abundance (column named abund) at each site +##' and for each PFG and strata (habitat & PFG composition validation). +##' @param releves.sites a data frame with coordinates and a description of the habitat associated with +##' the dominant species of each site in the studied map (habitat & PFG composition validation). +##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection +##' & resolution than simulation mask (habitat & PFG composition validation). +##' @param validation.mask a raster mask that specified which pixels need validation, with same projection +##' & resolution than simulation mask (habitat & PFG composition validation). ##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will -##' take into account of all habitats in the \code{hab.obs} map. Otherwise, please specify -##' in a vector habitats that will be take into account for the validation (habitat validation). +##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +##' in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +##' into account for the validation (habitat validation). ##' @param list.strata.simulations default \code{NULL}. A character vector which contain \code{FATE} ##' strata definition and correspondence with observed strata definition. +##' ##' @param doComposition \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, ##' if \code{FALSE}, PFG composition validation module is disabled. ##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered @@ -46,6 +50,7 @@ ##' @param strata.considered_PFG.compo If \code{perStrata} = \code{FALSE}, a character vector with value "A" ##' (selection of one or several specific strata disabled). If \code{perStrata} = \code{TRUE}, a character ##' vector with at least one of the observed strata (PFG composition validation). +##' ##' @param doRichness \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG richness validation module is activated, ##' if \code{FALSE}, PFG richness validation module is disabled. ##' @param list.PFG a character vector which contain all the PFGs taken account in @@ -158,9 +163,8 @@ ##' @export ##' ##' @importFrom stringr str_split -##' @importFrom raster raster projectRaster res crs crop +##' @importFrom raster raster projectRaster res crs crop origin ##' @importFrom utils read.csv write.csv -##' @importFrom sf st_read ##' @importFrom foreach foreach %dopar% ##' @importFrom forcats fct_expand ##' @importFrom readr write_rds @@ -174,8 +178,8 @@ POST_FATE.validation = function(name.simulation , sim.version , year , perStrata = TRUE + , opt.no_CPU = 1 , doHabitat = TRUE - , obs.path , releves.PFG , releves.sites , hab.obs @@ -204,18 +208,11 @@ POST_FATE.validation = function(name.simulation output.path = paste0(name.simulation, "/VALIDATION") year = year # choice in the year for validation perStrata = perStrata - - # Useful elements to extract from the simulation - name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), - flag = "MASK", - flag.split = "^--.*--$", - is.num = FALSE) #isolate the access path to the simulation mask for any FATE simulation - simulation.map = raster(paste0(name)) + opt.no_CPU = opt.no_CPU # For habitat validation - # CBNA releves data habitat map - releves.PFG = read.csv(paste0(obs.path,releves.PFG),header=T,stringsAsFactors = T) - + # Observed releves data + releves.PFG = releves.PFG if(perStrata==TRUE){ list.strata.releves = as.character(unique(releves.PFG$strata)) list.strata.simulations = list.strata.simulations @@ -223,14 +220,45 @@ POST_FATE.validation = function(name.simulation list.strata.releves = NULL list.strata.simulations = NULL } + releves.sites = releves.sites + + # Habitat map + hab.obs = hab.obs + validation.mask = validation.mask - releves.sites = st_read(paste0(obs.path, releves.sites)) - hab.obs = raster(paste0(obs.path, hab.obs)) - # Habitat mask at FATE simu resolution - hab.obs.modif = projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") - habitat.FATE.map = crop(hab.obs.modif, simulation.map) #reprojection and croping of the extended habitat map in order to have a reduced observed habitat map - validation.mask = raster(paste0(obs.path, validation.mask)) + # Simulation mask + name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), + flag = "MASK", + flag.split = "^--.*--$", + is.num = FALSE) #isolate the access path to the simulation mask for any FATE simulation + simulation.map = raster(paste0(name)) + # Check hab.obs map + if(!compareCRS(simulation.map, hab.obs) | !all(res(habitat.FATE.map)==res(simulation.map))){ + stop(paste0("Projection & resolution of hab.obs map does not match with simulation mask. Please reproject hab.obs map with projection & resolution of ", names(simulation.map))) + }else if(extent(simulation.map) != extent(hab.obs)){ + habitat.FATE.map = crop(hab.obs, simulation.map) + }else { + habitat.FATE.map = hab.obs + } + if(!all(origin(simulation.map) == origin(habitat.FATE.map))){ + print("setting origin habitat.FATE.map to match simulation.map") + raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) + } + + # Check validation mask + if(!compareCRS(simulation.map, validation.mask) | !all(res(validation.mask)==res(simulation.map))){ + stop(paste0("Projection & resolution of validation mask does not match with simulation mask. Please reproject validation mask with projection & resolution of ", names(simulation.map))) + }else if(extent(validation.mask) != extent(simulation.map)){ + validation.mask = crop(validation.mask, simulation.map) + }else { + validation.mask = validation.mask + } + if(!all(origin(simulation.map) == origin(validation.mask))){ + print("setting origin validation mask to match simulation.map") + raster::origin(validation.mask) <- raster::origin(simulation.map) + } + # Other if(is.null(studied.habitat)){ studied.habitat = studied.habitat #if null, the function will study all the habitats in the map @@ -270,7 +298,9 @@ POST_FATE.validation = function(name.simulation , hab.obs = hab.obs , year = year , list.strata.releves = list.strata.releves - , list.strata.simulations = list.strata.simulations) + , list.strata.simulations = list.strata.simulations + , opt.no_CPU = opt.no_CPU + , studied.habitat = studied.habitat) ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT @@ -299,13 +329,50 @@ POST_FATE.validation = function(name.simulation if(doHabitat == FALSE){ perStrata = perStrata - + opt.no_CPU = opt.no_CPU + + # Habitat map + hab.obs = hab.obs + validation.mask = validation.mask + + # Simulation mask + name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), + flag = "MASK", + flag.split = "^--.*--$", + is.num = FALSE) #isolate the access path to the simulation mask for any FATE simulation + simulation.map = raster(paste0(name)) + + # Check hab.obs map + if(!compareCRS(simulation.map, hab.obs) | !all(res(habitat.FATE.map)==res(simulation.map))){ + stop(paste0("Projection & resolution of hab.obs map does not match with simulation mask. Please reproject hab.obs map with projection & resolution of ", names(simulation.map))) + }else if(extent(simulation.map) != extent(hab.obs)){ + habitat.FATE.map = crop(hab.obs, simulation.map) + }else { + habitat.FATE.map = hab.obs + } + if(!all(origin(simulation.map) == origin(habitat.FATE.map))){ + print("setting origin habitat.FATE.map to match simulation.map") + raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) + } + + # Check validation mask + if(!compareCRS(simulation.map, validation.mask) | !all(res(validation.mask)==res(simulation.map))){ + stop(paste0("Projection & resolution of validation mask does not match with simulation mask. Please reproject validation mask with projection & resolution of ", names(simulation.map))) + }else if(extent(validation.mask) != extent(simulation.map)){ + validation.mask = crop(validation.mask, simulation.map) + }else { + validation.mask = validation.mask + } + if(!all(origin(simulation.map) == origin(validation.mask))){ + print("setting origin validation mask to match simulation.map") + raster::origin(validation.mask) <- raster::origin(simulation.map) + } + # Get observed distribution - releves.PFG = read.csv(paste0(obs.path, releves.PFG),header=T,stringsAsFactors = T) - releves.sites = st_read(paste0(obs.path, releves.sites)) - hab.obs = raster(paste0(obs.path, hab.obs)) + releves.PFG = releves.PFG + releves.sites = releves.sites + # Do PFG composition validation - validation.mask = raster(paste0(obs.path, validation.mask)) if(perStrata==TRUE){ list.strata.releves = as.character(unique(releves.PFG$strata)) list.strata.simulations = list.strata.simulations @@ -313,15 +380,25 @@ POST_FATE.validation = function(name.simulation list.strata.releves = NULL list.strata.simulations = NULL } + + # Studied.habitat + if(is.null(studied.habitat)){ + studied.habitat = studied.habitat #if null, the function will study all the habitats in the map + } else if(is.character(studied.habitat)){ + studied.habitat = studied.habitat #if a character vector with habitat names, the function will study only the habitats in the vector + } else{ + stop("studied.habitat is not a vector of character") + } + } ## GET OBSERVED DISTRIBUTION obs.distri = get.observed.distribution(name.simulation = name.simulation - , obs.path = obs.path , releves.PFG = releves.PFG , releves.sites = releves.sites , hab.obs = hab.obs + , studied.habitat = studied.habitat , PFG.considered_PFG.compo = PFG.considered_PFG.compo , strata.considered_PFG.compo = strata.considered_PFG.compo , habitat.considered_PFG.compo = habitat.considered_PFG.compo @@ -331,7 +408,6 @@ POST_FATE.validation = function(name.simulation ## DO PFG COMPOSITION VALIDATION performance.composition = do.PFG.composition.validation(name.simulation = name.simulation - , obs.path = obs.path , sim.version = sim.version , hab.obs = hab.obs , PFG.considered_PFG.compo = PFG.considered_PFG.compo @@ -342,7 +418,8 @@ POST_FATE.validation = function(name.simulation , validation.mask = validation.mask , year = year , list.strata.simulations = list.strata.simulations - , list.strata.releves = list.strata.releves) + , list.strata.releves = list.strata.releves + , habitat.FATE.map = habitat.FATE.map) } @@ -358,20 +435,35 @@ POST_FATE.validation = function(name.simulation #list of PFG of interest list.PFG = setdiff(list.PFG,exclude.PFG) - registerDoParallel(detectCores()-2) + print("processing simulations") + + if (opt.no_CPU > 1) + { + if (.getOS() != "windows") + { + registerDoParallel(cores = opt.no_CPU) + } else + { + warning("Parallelisation with `foreach` is not available for Windows. Sorry.") + } + } dying.PFG.list = foreach(i=1:length(sim.version)) %dopar% { - if(perStrata == F){ + if(perStrata == FALSE){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] - colnames(simu_PFG) = c("PFG", "pixel", "abs") + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))){ + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] + colnames(simu_PFG) = c("PFG", "pixel", "abs") + } } else if(perStrata == T){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))){ + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + } } diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index 6cb0c20..01ab98f 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -12,26 +12,31 @@ ##' distribution for a precise \code{FATE} simulation. ##' ##' @param name.simulation simulation folder name. -##' @param obs.path the function needs observed data, please create a folder for them in your -##' simulation folder and then indicate in this parameter the access path to this new folder. ##' @param sim.version name of the simulation we want to validate (it works with ##' only one \code{sim.version}). -##' @param hab.obs file which contain the extended studied map in the simulation. +##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection +##' & resolution than simulation mask. ##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered ##' in the validation. ##' @param strata.considered_PFG.compo a character vector of the list of precise ##' strata considered in the validation. ##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) ##' considered in the validation. -##' @param observed.distribution PFG observed distribution table. -##' @param perStrata.compo Logical. All strata together (FALSE) or per strata (TRUE). -##' @param validation.mask file which contain a raster mask that specified -##' which pixels need validation. +##' @param observed.distribution PFG observed distribution table provides by \code{get.observed.distribution} function. +##' @param perStrata.compo \code{Logical}. All strata together (FALSE) or per strata (TRUE). +##' @param validation.mask a raster mask that specified +##' which pixels need validation, with same projection & resolution than simulation mask. ##' @param year year of simulation to validate. ##' @param list.strata.simulations a character vector which contain \code{FATE} ##' strata definition and correspondence with observed strata definition. ##' @param list.strata.releves a character vector which contain the observed strata ##' definition, extracted from observed PFG releves. +##' @param habitat.FATE.map a raster map of the observed habitat in the +##' studied area with same projection & resolution than validation mask and simulation mask. +##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will +##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +##' in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +##' into account for the validation. ##' ##' @details ##' @@ -64,18 +69,14 @@ ### END OF HEADER ############################################################## -do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, hab.obs, PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, observed.distribution, perStrata, validation.mask, year, list.strata.simulations, list.strata.releves){ +do.PFG.composition.validation<-function(name.simulation, sim.version, hab.obs, PFG.considered_PFG.compo + , strata.considered_PFG.compo, habitat.considered_PFG.compo, observed.distribution + , perStrata, validation.mask, year, list.strata.simulations, list.strata.releves + , habitat.FATE.map, studied.habitat){ cat("\n ---------- PFG COMPOSITION VALIDATION \n") output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) - name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), - flag = "MASK", - flag.split = "^--.*--$", - is.num = FALSE) #isolate the access path to the simulation mask for any FATE simulation - simulation.map = raster(paste0(name)) - hab.obs.modif = projectRaster(from = hab.obs, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") - habitat.FATE.map = crop(hab.obs.modif, simulation.map) #reprojection and croping of the extended habitat map in order to have a reduced observed habitat map #Auxiliary function to compute proximity (on a 0 to 1 scale, 1 means quantile equality) compute.proximity<-function(simulated.quantile,observed.quantile){ @@ -88,69 +89,48 @@ do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, ############################ #check if strata definition used in the RF model is the same as the one used to analyze FATE output - if(perStrata==T){ - if(all(base::intersect(names(list.strata.simulations), list.strata.releves)==names(list.strata.simulations))){ + if (perStrata == TRUE) { + if (all(intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))) { list.strata = names(list.strata.simulations) print("strata definition OK") - }else { + } else { stop("wrong strata definition") } - }else if(perStrata==F){ - list.strata = "all" - }else { - stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulations") + } else if (perStrata == FALSE) { + list.strata <- "all" + } else { + stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } - #consistency between habitat.FATE.map and simulation.map - if(!compareCRS(simulation.map,habitat.FATE.map)){ - print("reprojecting habitat.FATE.map to match simulation.map crs") - habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) + #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) + if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map) == res(validation.mask))){ + stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") } - if(!all(res(habitat.FATE.map)==res(simulation.map))){ - stop("provide habitat.FATE.map with same resolution as simulation.map") - } - if(extent(simulation.map)!=extent(habitat.FATE.map)){ - print("cropping habitat.FATE.map to match simulation.map") - habitat.FATE.map<-crop(x=habitat.FATE.map,y=simulation.map) - } - if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ - print("setting origin habitat.FATE.map to match simulation.map") - raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) - } - if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates - stop("habitat.FATE.map could not be coerced to match simulation.map") - }else{ - print("simulation.map & habitat.FATE.map are (now) consistent") - } - - #adjust validation.mask accordingly - if(!all(res(habitat.FATE.map)==res(validation.mask))){ - validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") - } - if(extent(validation.mask)!=extent(habitat.FATE.map)){ - validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) - } - if(!compareRaster(validation.mask,habitat.FATE.map)){ - stop("error in correcting validation.mask to match habitat.FATE.map") - }else{ - print("validation.mask is (now) consistent with (modified) habitat.FATE.map") - } - ######################################### # 2. Get observed habitat ######################################### - #index of the pixels in the simulation area - in.region.pixels<-which(getValues(simulation.map)==1) + habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) + , code.habitat = getValues(habitat.FATE.map) + , for.validation = getValues(validation.mask)) + habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] #index of the pixels in the simulation area + habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] + if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2){ + habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(studied.habitat,c(ID,habitat)), by.x = "code.habitat", by.y = "ID") + habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] + } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)){ + habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") + habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] + } - #habitat df for the whole simulation area - habitat.whole.area.df<-data.frame(pixel=seq(from=1,to=ncell(habitat.FATE.map),by=1),code.habitat=getValues(habitat.FATE.map),for.validation=getValues(validation.mask)) - habitat.whole.area.df<-filter(habitat.whole.area.df,is.element(pixel,in.region.pixels)&for.validation==1) - habitat.whole.area.df<-merge(habitat.whole.area.df,dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)),by.x="code.habitat",by.y="ID") + print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) print("Habitat in the simulation area:") - table(habitat.whole.area.df$habitat,useNA="always") + table(habitat.whole.area.df$habitat, useNA = "always") + + print("Habitat in the subpart of the simulation area used for validation:") + table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") ############################## @@ -166,42 +146,48 @@ do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, ######################### #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - if(perStrata==F){ + if(perStrata == FALSE){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] - colnames(simu_PFG) = c("PFG", "pixel", "abs") + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] + colnames(simu_PFG) = c("PFG", "pixel", "abs") + simu_PFG$strata <- "A" + + }else { + + stop("Simulated abundance file does not exist") + } - }else if(perStrata==T){ + }else if(perStrata == TRUE){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") - } - - #aggregate per strata group with the correspondence provided in input - simu_PFG$new.strata<-NA - - #attribute the "new.strata" value to group FATE strata used in the simulations into strata comparable with CBNA ones (all strata together or per strata) - if(perStrata==F){ - simu_PFG$new.strata<-"A" - }else if(perStrata==T){ - for(p in 1:length(list.strata.simulations)){ - simu_PFG$new.strata[is.element(simu_PFG$strata,list.strata.simulations[[p]])] = names(list.strata.simulations)[p] + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + new.strata <- rep(NA, nrow(simu_PFG)) + for (i in 1:length(list.strata.simulations)) { + ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) + new.strata[ind] = names(list.strata.simulations)[i] + } + simu_PFG$strata = new.strata + + }else { + + stop("Simulated abundance file does not exist") } - simu_PFG$strata = NULL } - simu_PFG<-dplyr::rename(simu_PFG,"strata"="new.strata") - - #agggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) - simu_PFG<-aggregate(abs~pixel+strata+PFG,data=simu_PFG,FUN="sum") #sum and not mean because for a given CBNA strata some PFG are present in 2 FATE strata (let's say 1 unit in each) and other are present in 3 FATE strata (let's say one unit in each), so taking the mean would suppress the info that the second PFG is more present! + #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) + simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") #sum and not mean because for a given CBNA strata some PFG are present in 2 FATE strata (let's say 1 unit in each) and other are present in 3 FATE strata (let's say one unit in each), so taking the mean would suppress the info that the second PFG is more present! # 3.2. Merge with habitat ########################### #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable - simu_PFG<-merge(simu_PFG,habitat.whole.area.df,by="pixel") #at this stage we have all the pixels in the simulation area + simu_PFG <- merge(simu_PFG, habitat.whole.area.df, by = "pixel") #at this stage we have all the pixels in the simulation area # 3.3. Filter the required PFG, strata and habitat ################################################### @@ -217,76 +203,74 @@ do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, ##################################################################################### #important to do it only here, because if we filter some PFG, it changes the value of the relative metric (no impact of filtering for habitat or for strata since we do it per strata, and habitat is constant across a given pixel) - - #careful: if several strata/habitat are selected, the computation is made for each strata separately - simu_PFG<-as.data.frame(simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.metric= round(prop.table(abs),digits = 2))) - simu_PFG$relative.metric[is.na(simu_PFG$relative.metric)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) - simu_PFG$abs<-NULL + simu_PFG <- as.data.frame(simu_PFG %>% group_by(pixel, strata) %>% mutate(relative.metric = round(prop.table(abs), digits = 2))) + simu_PFG$relative.metric[is.na(simu_PFG$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) + simu_PFG$abs <- NULL # 3.5. Compute distribution per PFG, and if require per strata/habitat (else all strata/habitat will be considered together) ############################################################################################################################## #prepare the df storing quantile values - simulated.distribution<-expand.grid( - PFG=PFG.considered_PFG.compo, - habitat=habitat.considered_PFG.compo, - strata=strata.considered_PFG.compo + simulated.distribution <- expand.grid( + PFG = PFG.considered_PFG.compo, + habitat = habitat.considered_PFG.compo, + strata = strata.considered_PFG.compo ) - null.quantile<-data.frame(rank=seq(0,4,1)) #to have 5 rows per PFG*strata*habitat - simulated.distribution<-merge(simulated.distribution,null.quantile,all=T) + null.quantile <- data.frame(rank = seq(0, 4, 1)) #to have 5 rows per PFG*strata*habitat + simulated.distribution <- merge(simulated.distribution, null.quantile, all = TRUE) - if(dim(simu_PFG)[1]>0){ + if(dim(simu_PFG)[1] > 0){ - distribution<-setDT(simu_PFG)[, quantile(relative.metric), by=c("PFG","habitat","strata")] - distribution<-rename(distribution,"quantile"="V1") - distribution<-data.frame(distribution,rank=seq(0,4,1)) #add the rank number + distribution <- setDT(simu_PFG)[, quantile(relative.metric), by = c("PFG", "habitat", "strata")] + distribution <- rename(distribution, "quantile" = "V1") + distribution <- data.frame(distribution, rank = seq(0, 4, 1)) #add the rank number - simulated.distribution<-merge(simulated.distribution,distribution,by=c("PFG","habitat","strata","rank"),all.x=T) # add the simulated quantiles, "all.x=T" to keep the unobserved combination (with quantile=NA then) + simulated.distribution <- merge(simulated.distribution, distribution, by = c("PFG", "habitat", "strata", "rank"), all.x = TRUE) # add the simulated quantiles, "all.x=T" to keep the unobserved combination (with quantile=NA then) - simulated.distribution$quantile[is.na(simulated.distribution$quantile)]<-0 # "NA" in the previous line means that the corresponding combination PFG*strata*habitat is not present, so as a null relative abundance ! + simulated.distribution$quantile[is.na(simulated.distribution$quantile)] <- 0 # "NA" in the previous line means that the corresponding combination PFG*strata*habitat is not present, so as a null relative abundance ! }else{ - simulated.distribution$quantile<-0 + simulated.distribution$quantile <- 0 } - simulated.distribution$habitat<-as.character(simulated.distribution$habitat) #else may generate problem in ordering the database - simulated.distribution$strata<-as.character(simulated.distribution$strata) #else may generate problem in ordering the database - simulated.distribution$PFG<-as.character(simulated.distribution$PFG) #else may generate problem in ordering the database - simulated.distribution$rank<-as.numeric(simulated.distribution$rank) #else may generate problem in ordering the database + simulated.distribution$habitat <- as.character(simulated.distribution$habitat) #else may generate problem in ordering the database + simulated.distribution$strata <- as.character(simulated.distribution$strata) #else may generate problem in ordering the database + simulated.distribution$PFG <- as.character(simulated.distribution$PFG) #else may generate problem in ordering the database + simulated.distribution$rank <- as.numeric(simulated.distribution$rank) #else may generate problem in ordering the database # 3.6. Order the table to be able to have output in the right format ##################################################################### - simulated.distribution<-setDT(simulated.distribution) - simulated.distribution<-simulated.distribution[order(habitat,strata,PFG,rank)] + simulated.distribution <- setDT(simulated.distribution) + simulated.distribution <- simulated.distribution[order(habitat, strata, PFG, rank)] # 3.7. Rename ############## - simulated.distribution<-rename(simulated.distribution,"simulated.quantile"="quantile") + simulated.distribution <- rename(simulated.distribution, "simulated.quantile" = "quantile") # 3.8 Rename and reorder the observed database ############################################### - observed.distribution$habitat<-as.character(observed.distribution$habitat) #else may generate problem in ordering the database - observed.distribution$strata<-as.character(observed.distribution$strata) #else may generate problem in ordering the database - observed.distribution$PFG<-as.character(observed.distribution$PFG) #else may generate problem in ordering the database - observed.distribution$rank<-as.numeric(observed.distribution$rank) #else may generate problem in ordering the database + observed.distribution$habitat <- as.character(observed.distribution$habitat) #else may generate problem in ordering the database + observed.distribution$strata <- as.character(observed.distribution$strata) #else may generate problem in ordering the database + observed.distribution$PFG <- as.character(observed.distribution$PFG) #else may generate problem in ordering the database + observed.distribution$rank <- as.numeric(observed.distribution$rank) #else may generate problem in ordering the database - observed.distribution<-setDT(observed.distribution) - observed.distribution<-observed.distribution[order(habitat,strata,PFG,rank)] + observed.distribution <- setDT(observed.distribution) + observed.distribution <- observed.distribution[order(habitat, strata, PFG, rank)] # "if" to check that observed and simulated databases are in the same order if( !( - all(simulated.distribution$PFG==observed.distribution$PFG)& - all(simulated.distribution$habitat==observed.distribution$habitat)& - all(simulated.distribution$strata==observed.distribution$strata)& - all(simulated.distribution$rank==observed.distribution$rank) + all(simulated.distribution$PFG == observed.distribution$PFG)& + all(simulated.distribution$habitat == observed.distribution$habitat)& + all(simulated.distribution$strata == observed.distribution$strata)& + all(simulated.distribution$rank == observed.distribution$rank) ) ){ stop("Problem in observed vs simulated database (problem in the PFG*strata*habitat considered or in the database order)") @@ -295,50 +279,50 @@ do.PFG.composition.validation<-function(name.simulation, obs.path, sim.version, # 3.9. Merge observed and simulated data ######################################### - simulated.distribution<-cbind(simulated.distribution,observed.quantile=observed.distribution$observed.quantile) #quicker than a merge, but we can do it only because we have worked on the order of the DT + simulated.distribution <- cbind(simulated.distribution, observed.quantile = observed.distribution$observed.quantile) #quicker than a merge, but we can do it only because we have worked on the order of the DT # 3.10 Compute proximity between observed and simulated data, per PFG*strata*habitat ##################################################################################### #we get rid off rank==0 because there is good chance that it is nearly always equal to zero both in observed and simulated data, and that would provide a favorable bias in the results - simulated.distribution<-filter(simulated.distribution,rank!=0) + simulated.distribution <- filter(simulated.distribution, rank != 0) - proximity<-simulated.distribution[,compute.proximity(simulated.quantile=simulated.quantile,observed.quantile=observed.quantile),by=c("PFG","habitat","strata")] + proximity <- simulated.distribution[,compute.proximity(simulated.quantile = simulated.quantile, observed.quantile = observed.quantile), by = c("PFG", "habitat", "strata")] - proximity<-rename(proximity,"proximity"="V1") + proximity <- rename(proximity, "proximity" = "V1") - proximity<-proximity[order(habitat,strata,PFG)] #to have output in the same order for all simulations + proximity <- proximity[order(habitat, strata, PFG)] #to have output in the same order for all simulations # 3.11. Aggregate results for the different PFG ################################################ - aggregated.proximity<-proximity[,mean(proximity),by=c("habitat","strata")] - aggregated.proximity<-rename(aggregated.proximity,"aggregated.proximity"="V1") - aggregated.proximity$aggregated.proximity<-round(aggregated.proximity$aggregated.proximity,digits=2) - aggregated.proximity$simul<-sim.version + aggregated.proximity <- proximity[,mean(proximity), by = c("habitat", "strata")] + aggregated.proximity <- rename(aggregated.proximity, "aggregated.proximity" = "V1") + aggregated.proximity$aggregated.proximity <- round(aggregated.proximity$aggregated.proximity, digits = 2) + aggregated.proximity$simul <- sim.version # return(aggregated.proximity) #line added because the foreach method does not work - results.simul[[i]]<-aggregated.proximity + results.simul[[i]] <- aggregated.proximity } # 4. Put in the output format ############################## - results<-sapply(results.simul,function(X){X$aggregated.proximity}) - rownames(results)<-paste0(results.simul[[1]]$habitat,"_",results.simul[[1]]$strata) - colnames(results)<-sim.version - results<-t(results) - results<-as.data.frame(results) - results$simulation<-rownames(results) + results <- sapply(results.simul, function(X){X$aggregated.proximity}) + rownames(results) <- paste0(results.simul[[1]]$habitat, "_", results.simul[[1]]$strata) + colnames(results) <- sim.version + results <- t(results) + results <- as.data.frame(results) + results$simulation <- rownames(results) #save and return - write.csv(results,paste0(output.path,"/performance.composition.csv"),row.names = F) + write.csv(results, paste0(output.path, "/performance.composition.csv"), row.names = FALSE) return(results) } diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index c7f76a1..ad4ae4f 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -16,23 +16,30 @@ ##' @param RF.model random forest model trained on CBNA data (train.RF.habitat ##' function) ##' @param habitat.FATE.map a raster map of the observed habitat in the -##' studied area. -##' @param validation.mask a raster mask that specified which pixels need validation. -##' @param simulation.map a raster map of the whole studied area use to check -##' the consistency between simulation map and the observed habitat map. +##' studied area with same projection & resolution than validation mask and simulation mask. +##' @param validation.mask a raster mask that specified +##' which pixels need validation, with same projection & resolution than simulation mask. +##' @param simulation.map a raster map of the whole studied area (provides by FATE parameters functions). ##' @param predict.all.map \code{Logical}. If TRUE, the script will predict ##' habitat for the whole map. ##' @param sim.version name of the simulation to validate. ##' @param name.simulation simulation folder name. ##' @param perStrata \code{Logical}. If TRUE, the PFG abundance is defined ##' by strata in each pixel. If FALSE, PFG abundance is defined for all strata. -##' @param hab.obs a raster map of the observed habitat in the -##' extended studied area. +##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection +##' & resolution than simulation mask. ##' @param year simulation year selected for validation. ##' @param list.strata.releves a character vector which contain the observed strata ##' definition, extracted from observed PFG releves. ##' @param list.strata.simulations a character vector which contain \code{FATE} ##' strata definition and correspondence with observed strata definition. +##' @param opt.no_CPU default \code{1}. \cr The number of +##' resources that can be used to parallelize the computation of performance of +##' habitat prediction. +##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will +##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +##' in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +##' into account for the validation. ##' ##' @details ##' @@ -55,7 +62,7 @@ ##' @importFrom raster compareCRS res projectRaster extent crop origin compareRaster ##' getValues predict levels ##' @importFrom stats aggregate -##' @importFrom stringr str_sub +##' @importFrom stringr str_sub str_split ##' @importFrom foreach foreach %dopar% ##' @importFrom reshape2 dcast ##' @importFrom caret confusionMatrix @@ -69,7 +76,8 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask , simulation.map, predict.all.map, sim.version, name.simulation - , perStrata, hab.obs, year, list.strata.releves, list.strata.simulations) + , perStrata, hab.obs, year, list.strata.releves, list.strata.simulations + , opt.no_CPU = 1, studied.habitat = NULL) { cat("\n ---------- FATE OUTPUT ANALYSIS \n") @@ -100,50 +108,12 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") } - #consistency between habitat.FATE.map and simulation.map - ## MUST BE DONE before - # if(!compareCRS(simulation.map,habitat.FATE.map)){ - # print("reprojecting habitat.FATE.map to match simulation.map crs") - # habitat.FATE.map<-projectRaster(habitat.FATE.map,crs=crs(simulation.map)) - # } - if(!all(res(habitat.FATE.map)==res(simulation.map))){ - stop("provide habitat.FATE.map with same resolution as simulation.map") - } - if(extent(simulation.map) != extent(habitat.FATE.map)){ - print("cropping habitat.FATE.map to match simulation.map") - habitat.FATE.map = crop(x = habitat.FATE.map, y = simulation.map) - } - ## MUST BE DONE before - # if(!all(origin(simulation.map)==origin(habitat.FATE.map))){ - # print("setting origin habitat.FATE.map to match simulation.map") - # raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) - # } - if(!compareRaster(simulation.map,habitat.FATE.map)){ #this is crucial to be able to identify pixel by their index and not their coordinates - stop("habitat.FATE.map could not be coerced to match simulation.map") - }else{ - print("simulation.map & habitat.FATE.map are (now) consistent") - } - - #adjust validation.mask accordingly - ## MUST BE DONE before ? - # if(!all(res(habitat.FATE.map)==res(validation.mask))){ - # validation.mask<-projectRaster(from=validation.mask,to=habitat.FATE.map,method = "ngb") - # } - if(extent(validation.mask)!=extent(habitat.FATE.map)){ - validation.mask<-crop(x=validation.mask,y=habitat.FATE.map) - } - if(!compareRaster(validation.mask, habitat.FATE.map)){ - stop("error in correcting validation.mask to match habitat.FATE.map") - }else{ - print("validation.mask is (now) consistent with (modified) habitat.FATE.map") ## TODO : change message - } - #check consistency for PFG & strata classes between FATE output vs the RF model - RF.predictors <- rownames(RF.model$importance) RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) - FATE.PFG<-str_sub(list.files(paste0(name.simulation,"/DATA/PFGS/SUCC")),6,7) ## TODO : careful, will not match necessarily all PFG names + FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] + , abs.simulParam = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0){ stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") @@ -159,9 +129,14 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat , code.habitat = getValues(habitat.FATE.map) , for.validation = getValues(validation.mask)) habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] #index of the pixels in the simulation area - habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] - habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") - habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] + habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] + if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2){ + habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(studied.habitat,c(ID,habitat)), by.x = "code.habitat", by.y = "ID") + habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] + } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)){ + habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") + habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] + } print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) @@ -177,8 +152,6 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat print("processing simulations") - - # registerDoParallel(detectCores()-2) ## TODO : put as optional (like in zip/unzip function) if (opt.no_CPU > 1) { if (.getOS() != "windows") @@ -198,7 +171,6 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #get simulated abundance per pixel*strata*PFG for pixels in the simulation area if (perStrata == FALSE) { - ## TODO : add test if file exists if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))) { simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) @@ -212,7 +184,6 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } } else if (perStrata == TRUE) { - ## TODO : add test if file exists if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))) { simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) @@ -262,7 +233,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat ################################# data.validation <- data.FATE.PFG.habitat[which(data.FATE.PFG.habitat$for.validation == 1), ] - x.validation <- dplyr::select(data.validation,all_of(RF.predictors)) ## TODO : change for classic colnames selection but with error message if not fullfilling all names ? + x.validation <- dplyr::select(data.validation,all_of(RF.predictors)) y.validation <- data.validation$habitat y.validation.predicted <- predict(object = RF.model, newdata = x.validation, type = "response", norm.votes = TRUE) diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index 6a64f03..856b9e6 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -10,13 +10,16 @@ ##' of relative abundance, from observed data. ##' ##' @param name.simulation simulation folder name. -##' @param obs.path the function needs observed data, please create a folder for them in your -##' simulation folder and then indicate in this parameter the access path to this new folder. -##' @param releves.PFG file which contain the observed Braund-Blanquet abundance at each site -##' and each PFG and strata. -##' @param releves.sites file which contain coordinates and a description of -##' the habitat associated with the dominant species of each site in the studied map. -##' @param hab.obs raster map of the extended studied area in the simulation. +##' @param releves.PFG a data frame with abundance (column named abund) at each site +##' and for each PFG and strata. +##' @param releves.sites a data frame with coordinates and a description of the habitat associated with +##' the dominant species of each site in the studied map. +##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection +##' & resolution than simulation mask. +##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will +##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +##' in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +##' into account for the validation. ##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered ##' in the validation. ##' @param strata.considered_PFG.compo a character vector of the list of precise @@ -56,10 +59,10 @@ get.observed.distribution<-function(name.simulation - , obs.path , releves.PFG , releves.sites , hab.obs + , studied.habitat = NULL , PFG.considered_PFG.compo , strata.considered_PFG.compo , habitat.considered_PFG.compo @@ -75,62 +78,80 @@ get.observed.distribution<-function(name.simulation #1. Aggregate coverage per PFG ######################################### - #identify sites with wrong BB values (ie values that cannot be converted by the PRE_FATE.abundBraunBlanquet function) - releves.PFG<-filter(releves.PFG,is.element(BB,c(NA, "NA", 0, "+", "r", 1:5))) - #transformation into coverage percentage - releves.PFG$coverage<-PRE_FATE.abundBraunBlanquet(releves.PFG$BB)/100 #as a proportion, not a percentage - - if(perStrata==T){ - aggregated.releves.PFG<-aggregate(coverage~site+PFG+strata,data=releves.PFG,FUN="sum") - }else if(perStrata==F){ - aggregated.releves.PFG<-aggregate(coverage~site+PFG,data=releves.PFG,FUN="sum") - aggregated.releves.PFG$strata<-"A" #"A" is for "all". Important to have a single-letter code here (useful to check consistency between relevés strata and model strata) + if(!is.numeric(releves.PFG$abund)) # Braun-Blanquet abundance + { + releves.PFG <- filter(releves.PFG,is.element(abund,c(NA, "NA", 0, "+", "r", 1:5))) + releves.PFG$coverage = PRE_FATE.abundBraunBlanquet(releves.PFG$abund)/100 + } else if (is.numeric(releves.PFG$abund) & max(releves.PFG$abund) == 1) # presence-absence data + { + releves.PFG$coverage = releves.PFG$abund + } else if (is.numeric(releves.PFG$abund)) # absolute abundance + { + releves.PFG$coverage = releves.PFG$abund } + if(perStrata == T){ + mat.PFG.agg <- aggregate(coverage ~ site + PFG + strata, data = releves.PFG, FUN = "sum") + }else if(perStrata == F){ + mat.PFG.agg <- aggregate(coverage ~ site + PFG, data = releves.PFG, FUN = "sum") + mat.PFG.agg$strata <- "A" #"A" is for "all". + } #2. Get habitat information ################################### #get sites coordinates - aggregated.releves.PFG<-merge(dplyr::select(releves.sites,c(site)),aggregated.releves.PFG,by="site") + mat.PFG.agg = merge(releves.sites, mat.PFG.agg, by = "site") #get habitat code and name - if(compareCRS(aggregated.releves.PFG,hab.obs)){ - aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) - }else{ - aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(hab.obs)) - aggregated.releves.PFG$code.habitat<-raster::extract(x=hab.obs,y=aggregated.releves.PFG) + mat.PFG.agg$code.habitat = raster::extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) + mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] + if (nrow(mat.PFG.agg) == 0) { + stop("Code habitat vector is empty. Please verify values of your hab.obs map") } #correspondance habitat code/habitat name - table.habitat.releve<-levels(hab.obs)[[1]] - - aggregated.releves.PFG<-merge(aggregated.releves.PFG,dplyr::select(table.habitat.releve,c(ID,habitat)),by.x="code.habitat",by.y="ID") - - #(optional) keep only releves data in a specific area - if(!is.null(composition.mask)){ - - if(compareCRS(aggregated.releves.PFG,composition.mask)==F){ #as this stage it is not a problem to transform crs(aggregated.releves.PFG) since we have no more merge to do (we have already extracted habitat info from the map) - aggregated.releves.PFG<-st_transform(x=aggregated.releves.PFG,crs=crs(composition.mask)) - } - - aggregated.releves.PFG<-st_crop(x=aggregated.releves.PFG,y=composition.mask) - print("'releve' map has been cropped to match 'external.training.mask'.") - } + if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2) + { # cas où pas de levels dans la carte d'habitat et utilisation d'un vecteur d'habitat + colnames(obs.habitat) = c("ID", "habitat") + table.habitat.releve = studied.habitat + mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") + print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) + } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) + { # cas où on utilise les levels définis dans la carte + table.habitat.releve = levels(hab.obs)[[1]] + mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] + print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) + } else + { + stop("Habitat definition in hab.obs map is not correct") + } + + # #(optional) keep only releves data in a specific area + # if(!is.null(composition.mask)){ + # + # if(compareCRS(mat.PFG.agg,composition.mask)==F){ #as this stage it is not a problem to transform crs(mat.PFG.agg) since we have no more merge to do (we have already extracted habitat info from the map) + # mat.PFG.agg<-st_transform(x=mat.PFG.agg,crs=crs(composition.mask)) + # } + # + # mat.PFG.agg<-st_crop(x=mat.PFG.agg,y=composition.mask) + # print("'releve' map has been cropped to match 'external.training.mask'.") + # } # 3. Keep only releve on interesting habitat, strata and PFG ##################################################################" - aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG) - aggregated.releves.PFG<-dplyr::select(aggregated.releves.PFG,c(site,PFG,strata,coverage,habitat)) + mat.PFG.agg <- as.data.frame(mat.PFG.agg) + mat.PFG.agg <- dplyr::select(mat.PFG.agg,c(site,PFG,strata,coverage,habitat)) - aggregated.releves.PFG<-filter( - aggregated.releves.PFG, - is.element(PFG,PFG.considered_PFG.compo)& - is.element(strata,strata.considered_PFG.compo)& - is.element(habitat,habitat.considered_PFG.compo) + mat.PFG.agg <- filter( + mat.PFG.agg, + is.element(PFG, PFG.considered_PFG.compo) & + is.element(strata, strata.considered_PFG.compo) & + is.element(habitat, habitat.considered_PFG.compo) ) @@ -139,51 +160,51 @@ get.observed.distribution<-function(name.simulation #important to do it only here, because if we filter some PFG, it changes the value of the relative metric #careful: if several strata are selected, the computation is made for each strata separately - aggregated.releves.PFG<-as.data.frame(aggregated.releves.PFG %>% group_by(site,strata) %>% mutate(relative.metric= round(prop.table(coverage),digits = 2))) - aggregated.releves.PFG$relative.metric[is.na(aggregated.releves.PFG$relative.metric)]<-0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) - aggregated.releves.PFG$coverage<-NULL + mat.PFG.agg <- as.data.frame(mat.PFG.agg %>% group_by(site, strata) %>% mutate(relative.metric = round(prop.table(coverage), digits = 2))) + mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) + mat.PFG.agg$coverage <- NULL print("releve data have been transformed into a relative metric") # 5. Save data ##################### - write.csv(aggregated.releves.PFG,paste0(output.path,"/CBNA.releves.prepared.csv"),row.names = F) + write.csv(mat.PFG.agg, paste0(output.path, "/obs.releves.prepared.csv"), row.names = F) # 6. Compute distribution per PFG, and if require per strata/habitat (else all strata/habitat will be considered together) #################################### - distribution<-setDT(aggregated.releves.PFG)[, quantile(relative.metric), by=c("PFG","habitat","strata")] - distribution<-rename(distribution,"quantile"="V1") - distribution<-data.frame(distribution,rank=seq(0,4,1)) #to be able to sort on quantile + distribution <- setDT(mat.PFG.agg)[, quantile(relative.metric), by = c("PFG", "habitat", "strata")] + distribution <- rename(distribution, "quantile" = "V1") + distribution <- data.frame(distribution, rank = seq(0, 4, 1)) #to be able to sort on quantile # 7. Add the missing PFG*habitat*strata #final distribution is the distribution once the missing combination have been added. For these combination, all quantiles are set to 0 - observed.distribution<-expand.grid( - PFG=PFG.considered_PFG.compo, - habitat=habitat.considered_PFG.compo, - strata=strata.considered_PFG.compo + observed.distribution <- expand.grid( + PFG = PFG.considered_PFG.compo, + habitat = habitat.considered_PFG.compo, + strata = strata.considered_PFG.compo ) - null.quantile<-data.frame(rank=seq(0,4,1)) #to have 5 rows per PFG*strata*habitat - observed.distribution<-merge(observed.distribution,null.quantile,all=T) + null.quantile <- data.frame(rank = seq(0, 4, 1)) #to have 5 rows per PFG*strata*habitat + observed.distribution <- merge(observed.distribution, null.quantile, all = TRUE) - observed.distribution<-merge(observed.distribution,distribution,by=c("PFG","habitat","strata","rank"),all.x=T) # "all.x=T" to keep the unobserved combination + observed.distribution <- merge(observed.distribution, distribution, by = c("PFG", "habitat", "strata", "rank"), all.x = TRUE) # "all.x=T" to keep the unobserved combination - observed.distribution$quantile[is.na(observed.distribution$quantile)]<-0 + observed.distribution$quantile[is.na(observed.distribution$quantile)] <- 0 # 8. Order the table to be able to have output in the right format - observed.distribution<-setDT(observed.distribution) - observed.distribution<-observed.distribution[order(habitat,strata,PFG,rank)] + observed.distribution <- setDT(observed.distribution) + observed.distribution <- observed.distribution[order(habitat, strata, PFG, rank)] - observed.distribution<-rename(observed.distribution,"observed.quantile"="quantile") + observed.distribution <- rename(observed.distribution, "observed.quantile" = "quantile") # 9. Save results ########################################## - write.csv(observed.distribution,paste0(output.path,"/observed.distribution.csv"),row.names = F) + write.csv(observed.distribution, paste0(output.path, "/observed.distribution.csv"), row.names = F) # 8. Return #################### diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 123ecb2..131d679 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -14,14 +14,14 @@ ##' and for each PFG and strata. ##' @param releves.sites a data frame with coordinates and a description of ##' the habitat associated with the dominant species of each site in the -##' studied map. Shapefile format. +##' studied map. ##' @param hab.obs a raster map of the observed habitat in the ##' extended studied area. ##' @param external.training.mask default \code{NULL}. (optional) Keep only ##' releves data in a specific area. -##' @param studied.habitat If \code{NULL}, the function will +##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will ##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -##' in a 2 columns data frame the habitats and the ID for each of them which will be taken +##' in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken ##' into account for the validation. ##' @param RF.param a list of 2 parameters for random forest model : ##' share.training defines the size of the trainig part of the data base. @@ -56,7 +56,7 @@ ##' @importFrom reshape2 dcast ##' @importFrom data.table setDT ##' @importFrom raster extract compareCRS levels -##' @importFrom sf st_transform st_crop st_write +##' @importFrom sf st_transform st_crop ##' @importFrom randomForest randomForest tuneRF ##' @importFrom caret confusionMatrix ##' @importFrom readr write_rds @@ -90,7 +90,7 @@ train.RF.habitat = function(releves.PFG releves.PFG = as.data.frame(releves.PFG) if (nrow(releves.PFG) == 0 || ncol(releves.PFG) != 4) { - .stopMessage_numRowCol("releves.PFG", c("site", "PFG", "strata", "BB")) + .stopMessage_numRowCol("releves.PFG", c("site", "PFG", "strata", "abund")) } if (!is.numeric(releves.PFG$site)) { @@ -106,7 +106,6 @@ train.RF.habitat = function(releves.PFG { stop("PFG list in releves.PFG does not correspond to PFG list in FATE") } - .testParam_notInValues.m("releves.PFG$BB", releves.PFG$BB, c(NA, "NA", 0, "+", "r", 1:5)) } ## CHECK parameter releves.sites if (.testParam_notDf(releves.sites)) @@ -132,6 +131,7 @@ train.RF.habitat = function(releves.PFG #transformation into coverage percentage if(!is.numeric(releves.PFG$abund)) # Braun-Blanquet abundance { + releves.PFG <- filter(releves.PFG,is.element(abund,c(NA, "NA", 0, "+", "r", 1:5))) releves.PFG$coverage = PRE_FATE.abundBraunBlanquet(releves.PFG$abund)/100 } else if (is.numeric(releves.PFG$abund) & max(releves.PFG$abund) == 1) # presence-absence data { @@ -180,7 +180,7 @@ train.RF.habitat = function(releves.PFG } #correspondence habitat code/habitat name - if (names(raster::levels(hab.obs)[[1]]) != c("ID", "habitat", "colour") | nrow(raster::levels(hab.obs)[[1]]) == 0 & !is.null(studied.habitat)) + if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2) { # cas où pas de levels dans la carte d'habitat et utilisation d'un vecteur d'habitat colnames(obs.habitat) = c("ID", "habitat") table.habitat.releve = studied.habitat From 827fe8af6f35f04d8dee6d0be74632b2f2894227 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Wed, 23 Mar 2022 16:07:22 +0100 Subject: [PATCH 071/176] Corrections in documentation of validation functions --- DESCRIPTION | 2 +- NAMESPACE | 2 - docs/reference/POST_FATE.validation.html | 30 +- .../do.PFG.composition.validation.html | 27 +- docs/reference/do.habitat.validation.html | 25 +- docs/reference/get.observed.distribution.html | 21 +- docs/reference/index.html | 447 +++++------------- docs/reference/train.RF.habitat.html | 25 +- man/POST_FATE.validation.Rd | 28 +- man/do.PFG.composition.validation.Rd | 27 +- man/do.habitat.validation.Rd | 25 +- man/get.observed.distribution.Rd | 19 +- man/train.RF.habitat.Rd | 25 +- src/libs/iostreams/src/zlib.o | Bin 243648 -> 244552 bytes 14 files changed, 268 insertions(+), 435 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0d4ee18..2c87e7a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -35,7 +35,7 @@ Description: Wrapper of the C++ model FATE. FATE is a vegetation model based on 1) gather, prepare and format data to be used with FATE ; 2) run FATE simulation(s) 3) process and analyze data produced by FATE. -RoxygenNote: 7.1.1 +RoxygenNote: 7.1.2 Encoding: UTF-8 NeedsCompilation: yes License: diff --git a/NAMESPACE b/NAMESPACE index 6225478..579d50c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -207,9 +207,7 @@ importFrom(readr,write_rds) importFrom(reshape2,dcast) importFrom(reshape2,melt) importFrom(sf,st_crop) -importFrom(sf,st_read) importFrom(sf,st_transform) -importFrom(sf,st_write) importFrom(shiny,runApp) importFrom(sp,SpatialPoints) importFrom(stats,aggregate) diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index 3f549d0..c230721 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -160,8 +160,8 @@

Computes validation data for habitat, PFG richness and composition for a sim.version, year, perStrata = TRUE, + opt.no_CPU = 1, doHabitat = TRUE, - obs.path, releves.PFG, releves.sites, hab.obs, @@ -189,24 +189,29 @@

Arguments

perStrata

Logical. Default TRUE. If TRUE, PFG abundance is defined by strata. If FALSE, PFG abundance defined for all strata (habitat & PFG composition & PFG richness validation).

+
opt.no_CPU
+

default 1.
The number of resources that can be used to +parallelize the computation of prediction performance for habitat & richness validation.

doHabitat

Logical. Default TRUE. If TRUE, habitat validation module is activated, if FALSE, habitat validation module is disabled.

-
obs.path
-

the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parameter the access path to this new folder (habitat & PFG composition validation).

releves.PFG
-

name of file which contain the observed Braund-Blanquet abundance at each site -and each PFG and strata (habitat & PFG composition validation).

+

a data frame with abundance (column named abund) at each site +and for each PFG and strata (habitat & PFG composition validation).

+
releves.sites
+

a data frame with coordinates and a description of the habitat associated with +the dominant species of each site in the studied map (habitat & PFG composition validation).

hab.obs
-

name of the file which contain the extended studied map in the simulation (habitat & PFG composition validation).

+

a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask (habitat & PFG composition validation).

validation.mask
-

name of the file which contain a raster mask that specified which pixels need validation -(habitat & PFG composition validation).

+

a raster mask that specified which pixels need validation, with same projection +& resolution than simulation mask (habitat & PFG composition validation).

studied.habitat

default NULL. If NULL, the function will -take into account of all habitats in the hab.obs map. Otherwise, please specify -in a vector habitats that will be take into account for the validation (habitat validation).

+take into account of habitats define in the hab.obs map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation (habitat validation).

list.strata.simulations

default NULL. A character vector which contain FATE strata definition and correspondence with observed strata definition.

@@ -232,9 +237,6 @@

Arguments

exclude.PFG

default NULL. A character vector containing the names of the PFG you want to exclude from the analysis (PFG richness validation).

-
releves.site
-

name of the file which contain coordinates and a description of -the habitat associated with the dominant species of each site in the studied map (habitat & PFG composition validation).

Value

diff --git a/docs/reference/do.PFG.composition.validation.html b/docs/reference/do.PFG.composition.validation.html index 14f0193..ba4facb 100644 --- a/docs/reference/do.PFG.composition.validation.html +++ b/docs/reference/do.PFG.composition.validation.html @@ -147,7 +147,6 @@

Compute distance between observed and simulated distribution

do.PFG.composition.validation(
   name.simulation,
-  obs.path,
   sim.version,
   hab.obs,
   PFG.considered_PFG.compo,
@@ -158,7 +157,9 @@ 

Compute distance between observed and simulated distribution

validation.mask, year, list.strata.simulations, - list.strata.releves + list.strata.releves, + habitat.FATE.map, + studied.habitat )
@@ -166,14 +167,12 @@

Compute distance between observed and simulated distribution

Arguments

name.simulation

simulation folder name.

-
obs.path
-

the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parameter the access path to this new folder.

sim.version

name of the simulation we want to validate (it works with only one sim.version).

hab.obs
-

file which contain the extended studied map in the simulation.

+

a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask.

PFG.considered_PFG.compo

a character vector of the list of PFG considered in the validation.

@@ -184,10 +183,10 @@

Arguments

a character vector of the list of habitat(s) considered in the validation.

observed.distribution
-

PFG observed distribution table.

+

PFG observed distribution table provides by get.observed.distribution function.

validation.mask
-

file which contain a raster mask that specified -which pixels need validation.

+

a raster mask that specified +which pixels need validation, with same projection & resolution than simulation mask.

year

year of simulation to validate.

list.strata.simulations
@@ -196,8 +195,16 @@

Arguments

list.strata.releves

a character vector which contain the observed strata definition, extracted from observed PFG releves.

+
habitat.FATE.map
+

a raster map of the observed habitat in the +studied area with same projection & resolution than validation mask and simulation mask.

+
studied.habitat
+

default NULL. If NULL, the function will +take into account of habitats define in the hab.obs map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.

perStrata.compo
-

Logical. All strata together (FALSE) or per strata (TRUE).

+

Logical. All strata together (FALSE) or per strata (TRUE).

Value

diff --git a/docs/reference/do.habitat.validation.html b/docs/reference/do.habitat.validation.html index d6da983..65c248a 100644 --- a/docs/reference/do.habitat.validation.html +++ b/docs/reference/do.habitat.validation.html @@ -159,7 +159,9 @@

Compare observed and simulated habitat of a FATE simulation hab.obs, year, list.strata.releves, - list.strata.simulations + list.strata.simulations, + opt.no_CPU = 1, + studied.habitat = NULL )

@@ -173,12 +175,12 @@

Arguments

function)

habitat.FATE.map

a raster map of the observed habitat in the -studied area.

+studied area with same projection & resolution than validation mask and simulation mask.

validation.mask
-

a raster mask that specified which pixels need validation.

+

a raster mask that specified +which pixels need validation, with same projection & resolution than simulation mask.

simulation.map
-

a raster map of the whole studied area use to check -the consistency between simulation map and the observed habitat map.

+

a raster map of the whole studied area (provides by FATE parameters functions).

predict.all.map

Logical. If TRUE, the script will predict habitat for the whole map.

@@ -190,8 +192,8 @@

Arguments

Logical. If TRUE, the PFG abundance is defined by strata in each pixel. If FALSE, PFG abundance is defined for all strata.

hab.obs
-

a raster map of the observed habitat in the -extended studied area.

+

a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask.

year

simulation year selected for validation.

list.strata.releves
@@ -200,6 +202,15 @@

Arguments

list.strata.simulations

a character vector which contain FATE strata definition and correspondence with observed strata definition.

+
opt.no_CPU
+

default 1.
The number of +resources that can be used to parallelize the computation of performance of +habitat prediction.

+
studied.habitat
+

default NULL. If NULL, the function will +take into account of habitats define in the hab.obs map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.

Value

diff --git a/docs/reference/get.observed.distribution.html b/docs/reference/get.observed.distribution.html index e6fa016..e318c82 100644 --- a/docs/reference/get.observed.distribution.html +++ b/docs/reference/get.observed.distribution.html @@ -143,10 +143,10 @@

Compute distribution of relative abundance over observed relevés

get.observed.distribution(
   name.simulation,
-  obs.path,
   releves.PFG,
   releves.sites,
   hab.obs,
+  studied.habitat = NULL,
   PFG.considered_PFG.compo,
   strata.considered_PFG.compo,
   habitat.considered_PFG.compo,
@@ -159,17 +159,20 @@ 

Compute distribution of relative abundance over observed relevés

Arguments

name.simulation

simulation folder name.

-
obs.path
-

the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parameter the access path to this new folder.

releves.PFG
-

file which contain the observed Braund-Blanquet abundance at each site -and each PFG and strata.

+

a data frame with abundance (column named abund) at each site +and for each PFG and strata.

releves.sites
-

file which contain coordinates and a description of -the habitat associated with the dominant species of each site in the studied map.

+

a data frame with coordinates and a description of the habitat associated with +the dominant species of each site in the studied map.

hab.obs
-

raster map of the extended studied area in the simulation.

+

a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask.

+
studied.habitat
+

default NULL. If NULL, the function will +take into account of habitats define in the hab.obs map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.

PFG.considered_PFG.compo

a character vector of the list of PFG considered in the validation.

diff --git a/docs/reference/index.html b/docs/reference/index.html index 0b00231..ed88d72 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -1,66 +1,12 @@ - - - - - - - -Function reference • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Function reference • RFate - - - - + + -
-
- -
- -
+
- - - - - - - - - - - + + + + + + + + + +
-

Datasets

-

Used in the examples.

+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+

Datasets

+

Used in the examples.

+

.loadData()

Load a dataset

-

Interface

-

Graphical User Interface (shiny application)

+
+

Interface

+

Graphical User Interface (shiny application)

+

RFATE()

Shiny application to apply RFate functions and run FATE simulation

-

Build Plant Functional Groups

-

Functions to select dominant species, compute functional distance between species and build clusters.

+
+

Build Plant Functional Groups

+

Functions to select dominant species, compute functional distance between species and build clusters.

+

PRE_FATE.abundBraunBlanquet()

Transform Braun-Blanquet values into relative abundances

+

PRE_FATE.selectDominant()

Selection of dominant species from abundance releves

+

PRE_FATE.speciesDistance()

Computation of distances between species based on traits and niche overlap

+

PRE_FATE.speciesDistanceOverlap()

Computation of niche overlap distances between species

+

PRE_FATE.speciesDistanceTraits()

Computation of traits distances between species

+

PRE_FATE.speciesDistanceCombine()

Combine several dissimilarity distance matrices

+

PRE_FATE.speciesClustering_step1()

Create clusters based on dissimilarity matrix

+

PRE_FATE.speciesClustering_step2()

Choose clusters and select determinant species

+

PRE_FATE.speciesClustering_step3()

Calculate PFG traits values based on determinant species traits values

-

Create FATE parameter files

-

Create user-friendly directory tree, parameter and simulation files for FATE simulation.

+
+

Create FATE parameter files

+

Create user-friendly directory tree, parameter and simulation files for FATE simulation.

+

PRE_FATE.skeletonDirectory()

Create the skeleton folder for a FATE simulation

+

PRE_FATE.params_PFGsuccession()

Create SUCCESSION parameter files for a FATE simulation

+

PRE_FATE.params_PFGlight()

Create LIGHT parameter files for a FATE simulation

+

PRE_FATE.params_PFGsoil()

Create SOIL parameter files for a FATE simulation

+

PRE_FATE.params_PFGdispersal()

Create DISPERSAL parameter files for a FATE simulation

+

PRE_FATE.params_PFGdisturbance()

Create DISTURBANCE parameter files for a FATE simulation

+

PRE_FATE.params_PFGdrought()

Create DROUGHT parameter files for a FATE simulation

+

PRE_FATE.params_changingYears()

Create SCENARIO parameter files for a FATE simulation

+

PRE_FATE.params_savingYears()

Create SAVE parameter files for a FATE simulation

+

PRE_FATE.params_globalParameters()

Create Global_parameters parameter file for a FATE simulation

+

PRE_FATE.params_simulParameters()

Create Simul_parameters parameter file for a FATE simulation

+

PRE_FATE.params_multipleSet()

Create multiple set(s) of parameter files for a FATE simulation

-

Run FATE simulation

+
+

Run FATE simulation

+

FATE()

FATE Wrapper

-

Analyze FATE outputs

-

Evalute predicted maps, produce summary and dynamic graphics.

+
+

Analyze FATE outputs

+

Evalute predicted maps, produce summary and dynamic graphics.

+

POST_FATE.graphics()

Create all possible graphical representations for a FATE simulation

+

POST_FATE.temporalEvolution()

Create tables of pixel temporal evolution of PFG abundances (and light and soil resources if activated) for a FATE simulation

+

POST_FATE.graphic_evolutionCoverage()

Create a graphical representation of the evolution of PFG coverage and abundance through time for a FATE simulation

+

POST_FATE.graphic_evolutionPixels()

Create a graphical representation of the evolution of PFG abundance through time for 5 (or more) pixels of a FATE simulation

+

POST_FATE.graphic_evolutionStability()

Create a graphical representation of the evolution of habitat composition through time for a FATE simulation

+

POST_FATE.relativeAbund()

Create relative abundance maps for each Plant Functional Group for one (or several) specific year of a FATE simulation

+

POST_FATE.graphic_validationStatistics()

Create a graphical representation of several statistics for each PFG to asses the quality of the model for one (or several) specific year of a FATE simulation

+

POST_FATE.binaryMaps()

Create binary maps for each Plant Functional Group for one (or several) specific year of a FATE simulation

+

POST_FATE.graphic_mapPFGvsHS()

Create maps of both habitat suitability and simulated occurrences of each Plant Functional Group for one (or several) specific year of a FATE simulation

+

POST_FATE.graphic_mapPFG()

Create a map related to plant functional group results (richness, relative cover, light or soil CWM) for one (or several) specific year of a FATE simulation

-

Save FATE simulation

+
+

POST_FATE.validation()

+

Computes validation data for habitat, PFG richness and composition for a FATE simulation.

+

Save FATE simulation

+

SAVE_FATE.step1_PFG()

Save data to reproduce building of Plant Functional Groups

+

SAVE_FATE.step2_parameters()

Save data to reproduce building of parameter files

-

Tool box

-

Utility functions.

+
+

Tool box

+

Utility functions.

+

.getOS()

Find operating system of your computer

+

.getParam()

Extract parameter value(s) from a parameter file

+

.setParam()

Replace parameter value(s) from a parameter file

+

.setPattern()

Replace a pattern with a new within all parameter files of a FATE simulation folder

+

.adaptMaps()

Adapt all raster maps of a FATE simulation folder (change NA to 0, and save as .tif)

+

.scaleMaps() .cropMaps()

Upscale / downscale / crop all raster maps of a FATE simulation folder

+

.getCutoff()

Find cutoff to transform abundance values into binary values

+

.unzip_ALL() .unzip() .zip_ALL() .zip()

Compress (.tif, .img) or decompress (.gz) files contained in results folder

- +
+

train.RF.habitat()

+

Create a random forest algorithm trained on CBNA data.

+

do.habitat.validation()

+

Compare observed and simulated habitat of a FATE simulation +at the last simulation year.

+

plot(<predicted.habitat>)

+

Create a raster map of habitat prediction for a specific FATE +simulation at the last simulation year.

+

get.observed.distribution()

+

Compute distribution of relative abundance over observed relevés

+

do.PFG.composition.validation()

+

Compute distance between observed and simulated distribution

+
-
- - + + diff --git a/docs/reference/train.RF.habitat.html b/docs/reference/train.RF.habitat.html index 471769c..a6a8f3c 100644 --- a/docs/reference/train.RF.habitat.html +++ b/docs/reference/train.RF.habitat.html @@ -148,7 +148,7 @@

Create a random forest algorithm trained on CBNA data.

releves.sites, hab.obs, external.training.mask = NULL, - studied.habitat, + studied.habitat = NULL, RF.param, output.path, perStrata, @@ -159,8 +159,8 @@

Create a random forest algorithm trained on CBNA data.

Arguments

releves.PFG
-

a data frame with Braund-Blanquet abundance at each site -and each PFG and strata.

+

a data frame with abundance (column named abund) at each site +and for each PFG and strata.

releves.sites

a data frame with coordinates and a description of the habitat associated with the dominant species of each site in the @@ -172,9 +172,10 @@

Arguments

default NULL. (optional) Keep only releves data in a specific area.

studied.habitat
-

If NULL, the function will -take into account of all habitats in the hab.obs map. Otherwise, please specify -in a vector the habitats that we take into account for the validation.

+

default NULL. If NULL, the function will +take into account of habitats define in the hab.obs map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.

RF.param

a list of 2 parameters for random forest model : share.training defines the size of the trainig part of the data base. @@ -191,7 +192,7 @@

Arguments

Value

-

2 prepared CBNA releves files are created before the building of the random +

2 prepared observed releves files are created before the building of the random forest model in a habitat validation folder. 5 more files are created at the end of the script to save the RF model and the performance analyzes (confusion matrix and TSS) for the training and @@ -199,11 +200,11 @@

Value

Details

-

This function transform PFG Braund-Blanquet abundance in relative abundance, -get habitat information from the releves map, keep only relees on interesting -habitat and then builds de random forest model. Finally, the function analyzes -the model performance with computation of confusion matrix and TSS for -the traning and testing sample.

+

This function transform PFG abundance in relative abundance, +get habitat information from the releves map of from a vector previously defined, +keep releves on interesting habitat and then builds a random forest model. Finally, +the function analyzes the model performance with computation of confusion matrix and TSS between +the training and testing sample.

Author

diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index ad92f2d..3415743 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -9,8 +9,8 @@ POST_FATE.validation( sim.version, year, perStrata = TRUE, + opt.no_CPU = 1, doHabitat = TRUE, - obs.path, releves.PFG, releves.sites, hab.obs, @@ -36,23 +36,28 @@ POST_FATE.validation( \item{perStrata}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. If \code{FALSE}, PFG abundance defined for all strata (habitat & PFG composition & PFG richness validation).} +\item{opt.no_CPU}{default \code{1}. \cr The number of resources that can be used to +parallelize the computation of prediction performance for habitat & richness validation.} + \item{doHabitat}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, if \code{FALSE}, habitat validation module is disabled.} -\item{obs.path}{the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parameter the access path to this new folder (habitat & PFG composition validation).} +\item{releves.PFG}{a data frame with abundance (column named abund) at each site +and for each PFG and strata (habitat & PFG composition validation).} -\item{releves.PFG}{name of file which contain the observed Braund-Blanquet abundance at each site -and each PFG and strata (habitat & PFG composition validation).} +\item{releves.sites}{a data frame with coordinates and a description of the habitat associated with +the dominant species of each site in the studied map (habitat & PFG composition validation).} -\item{hab.obs}{name of the file which contain the extended studied map in the simulation (habitat & PFG composition validation).} +\item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask (habitat & PFG composition validation).} -\item{validation.mask}{name of the file which contain a raster mask that specified which pixels need validation -(habitat & PFG composition validation).} +\item{validation.mask}{a raster mask that specified which pixels need validation, with same projection +& resolution than simulation mask (habitat & PFG composition validation).} \item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will -take into account of all habitats in the \code{hab.obs} map. Otherwise, please specify -in a vector habitats that will be take into account for the validation (habitat validation).} +take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation (habitat validation).} \item{list.strata.simulations}{default \code{NULL}. A character vector which contain \code{FATE} strata definition and correspondence with observed strata definition.} @@ -78,9 +83,6 @@ the simulation and observed in the simulation area (PFG richness validation).} \item{exclude.PFG}{default \code{NULL}. A character vector containing the names of the PFG you want to exclude from the analysis (PFG richness validation).} - -\item{releves.site}{name of the file which contain coordinates and a description of -the habitat associated with the dominant species of each site in the studied map (habitat & PFG composition validation).} } \value{ diff --git a/man/do.PFG.composition.validation.Rd b/man/do.PFG.composition.validation.Rd index 9d35451..46dcc15 100644 --- a/man/do.PFG.composition.validation.Rd +++ b/man/do.PFG.composition.validation.Rd @@ -6,7 +6,6 @@ \usage{ do.PFG.composition.validation( name.simulation, - obs.path, sim.version, hab.obs, PFG.considered_PFG.compo, @@ -17,19 +16,19 @@ do.PFG.composition.validation( validation.mask, year, list.strata.simulations, - list.strata.releves + list.strata.releves, + habitat.FATE.map, + studied.habitat ) } \arguments{ \item{name.simulation}{simulation folder name.} -\item{obs.path}{the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parameter the access path to this new folder.} - \item{sim.version}{name of the simulation we want to validate (it works with only one \code{sim.version}).} -\item{hab.obs}{file which contain the extended studied map in the simulation.} +\item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask.} \item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered in the validation.} @@ -40,10 +39,10 @@ strata considered in the validation.} \item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) considered in the validation.} -\item{observed.distribution}{PFG observed distribution table.} +\item{observed.distribution}{PFG observed distribution table provides by \code{get.observed.distribution} function.} -\item{validation.mask}{file which contain a raster mask that specified -which pixels need validation.} +\item{validation.mask}{a raster mask that specified +which pixels need validation, with same projection & resolution than simulation mask.} \item{year}{year of simulation to validate.} @@ -53,7 +52,15 @@ strata definition and correspondence with observed strata definition.} \item{list.strata.releves}{a character vector which contain the observed strata definition, extracted from observed PFG releves.} -\item{perStrata.compo}{Logical. All strata together (FALSE) or per strata (TRUE).} +\item{habitat.FATE.map}{a raster map of the observed habitat in the +studied area with same projection & resolution than validation mask and simulation mask.} + +\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will +take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.} + +\item{perStrata.compo}{\code{Logical}. All strata together (FALSE) or per strata (TRUE).} } \value{ diff --git a/man/do.habitat.validation.Rd b/man/do.habitat.validation.Rd index 2a294af..a3c82e5 100644 --- a/man/do.habitat.validation.Rd +++ b/man/do.habitat.validation.Rd @@ -18,7 +18,9 @@ do.habitat.validation( hab.obs, year, list.strata.releves, - list.strata.simulations + list.strata.simulations, + opt.no_CPU = 1, + studied.habitat = NULL ) } \arguments{ @@ -29,12 +31,12 @@ will be created.} function)} \item{habitat.FATE.map}{a raster map of the observed habitat in the -studied area.} +studied area with same projection & resolution than validation mask and simulation mask.} -\item{validation.mask}{a raster mask that specified which pixels need validation.} +\item{validation.mask}{a raster mask that specified +which pixels need validation, with same projection & resolution than simulation mask.} -\item{simulation.map}{a raster map of the whole studied area use to check -the consistency between simulation map and the observed habitat map.} +\item{simulation.map}{a raster map of the whole studied area (provides by FATE parameters functions).} \item{predict.all.map}{\code{Logical}. If TRUE, the script will predict habitat for the whole map.} @@ -46,8 +48,8 @@ habitat for the whole map.} \item{perStrata}{\code{Logical}. If TRUE, the PFG abundance is defined by strata in each pixel. If FALSE, PFG abundance is defined for all strata.} -\item{hab.obs}{a raster map of the observed habitat in the -extended studied area.} +\item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask.} \item{year}{simulation year selected for validation.} @@ -56,6 +58,15 @@ definition, extracted from observed PFG releves.} \item{list.strata.simulations}{a character vector which contain \code{FATE} strata definition and correspondence with observed strata definition.} + +\item{opt.no_CPU}{default \code{1}. \cr The number of +resources that can be used to parallelize the computation of performance of +habitat prediction.} + +\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will +take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.} } \value{ Habitat performance file. \cr diff --git a/man/get.observed.distribution.Rd b/man/get.observed.distribution.Rd index 51917af..a12ac12 100644 --- a/man/get.observed.distribution.Rd +++ b/man/get.observed.distribution.Rd @@ -6,10 +6,10 @@ \usage{ get.observed.distribution( name.simulation, - obs.path, releves.PFG, releves.sites, hab.obs, + studied.habitat = NULL, PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, @@ -20,16 +20,19 @@ get.observed.distribution( \arguments{ \item{name.simulation}{simulation folder name.} -\item{obs.path}{the function needs observed data, please create a folder for them in your -simulation folder and then indicate in this parameter the access path to this new folder.} +\item{releves.PFG}{a data frame with abundance (column named abund) at each site +and for each PFG and strata.} -\item{releves.PFG}{file which contain the observed Braund-Blanquet abundance at each site -and each PFG and strata.} +\item{releves.sites}{a data frame with coordinates and a description of the habitat associated with +the dominant species of each site in the studied map.} -\item{releves.sites}{file which contain coordinates and a description of -the habitat associated with the dominant species of each site in the studied map.} +\item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask.} -\item{hab.obs}{raster map of the extended studied area in the simulation.} +\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will +take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.} \item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered in the validation.} diff --git a/man/train.RF.habitat.Rd b/man/train.RF.habitat.Rd index bfd286b..97ca461 100644 --- a/man/train.RF.habitat.Rd +++ b/man/train.RF.habitat.Rd @@ -9,7 +9,7 @@ train.RF.habitat( releves.sites, hab.obs, external.training.mask = NULL, - studied.habitat, + studied.habitat = NULL, RF.param, output.path, perStrata, @@ -17,8 +17,8 @@ train.RF.habitat( ) } \arguments{ -\item{releves.PFG}{a data frame with Braund-Blanquet abundance at each site -and each PFG and strata.} +\item{releves.PFG}{a data frame with abundance (column named abund) at each site +and for each PFG and strata.} \item{releves.sites}{a data frame with coordinates and a description of the habitat associated with the dominant species of each site in the @@ -30,9 +30,10 @@ extended studied area.} \item{external.training.mask}{default \code{NULL}. (optional) Keep only releves data in a specific area.} -\item{studied.habitat}{If \code{NULL}, the function will -take into account of all habitats in the hab.obs map. Otherwise, please specify -in a vector the habitats that we take into account for the validation.} +\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will +take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.} \item{RF.param}{a list of 2 parameters for random forest model : share.training defines the size of the trainig part of the data base. @@ -48,7 +49,7 @@ by strata in each site. If FALSE, PFG abundance is defined for all strata.} \item{sim.version}{name of the simulation we want to validate.} } \value{ -2 prepared CBNA releves files are created before the building of the random +2 prepared observed releves files are created before the building of the random forest model in a habitat validation folder. 5 more files are created at the end of the script to save the RF model and the performance analyzes (confusion matrix and TSS) for the training and @@ -60,11 +61,11 @@ trained on observed PFG abundance, sites releves and a map of observed habitat. } \details{ -This function transform PFG Braund-Blanquet abundance in relative abundance, -get habitat information from the releves map, keep only relees on interesting -habitat and then builds de random forest model. Finally, the function analyzes -the model performance with computation of confusion matrix and TSS for -the traning and testing sample. +This function transform PFG abundance in relative abundance, +get habitat information from the releves map of from a vector previously defined, +keep releves on interesting habitat and then builds a random forest model. Finally, +the function analyzes the model performance with computation of confusion matrix and TSS between +the training and testing sample. } \author{ Matthieu Combaud, Maxime Delprat diff --git a/src/libs/iostreams/src/zlib.o b/src/libs/iostreams/src/zlib.o index a65f4ec33fbdc8a2a0357172418afe822a34780e..00d4b325c1a6284d54ca842fb77f2b9fd157cd73 100644 GIT binary patch literal 244552 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

|y`e?hL> z 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 QEp&8h+&+JCuD_7~1u(O>ng9R* From dbf8f5aa7c84532023485c856a31929a0ba6cb9f Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Wed, 23 Mar 2022 16:15:10 +0100 Subject: [PATCH 072/176] small adjustments --- R/POST_FATE.validation.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 0ed228d..76bde34 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -234,7 +234,7 @@ POST_FATE.validation = function(name.simulation simulation.map = raster(paste0(name)) # Check hab.obs map - if(!compareCRS(simulation.map, hab.obs) | !all(res(habitat.FATE.map)==res(simulation.map))){ + if(!compareCRS(simulation.map, hab.obs) | !all(res(hab.obs)==res(simulation.map))){ stop(paste0("Projection & resolution of hab.obs map does not match with simulation mask. Please reproject hab.obs map with projection & resolution of ", names(simulation.map))) }else if(extent(simulation.map) != extent(hab.obs)){ habitat.FATE.map = crop(hab.obs, simulation.map) From c2caa9ae298e338233d0323888f86d5d52c39f45 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Wed, 23 Mar 2022 16:27:30 +0100 Subject: [PATCH 073/176] small adjustments --- R/POST_FATE.validation.R | 6 +++--- R/UTILS.get_observed_distribution.R | 2 +- R/UTILS.train_RF_habitat.R | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 76bde34..851a01e 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -262,10 +262,10 @@ POST_FATE.validation = function(name.simulation # Other if(is.null(studied.habitat)){ studied.habitat = studied.habitat #if null, the function will study all the habitats in the map - } else if(is.character(studied.habitat)){ + } else if(is.data.frame(studied.habitat)){ studied.habitat = studied.habitat #if a character vector with habitat names, the function will study only the habitats in the vector } else{ - stop("studied.habitat is not a vector of character") + stop("studied.habitat is not a data frame") } RF.param = list( share.training = 0.7, @@ -384,7 +384,7 @@ POST_FATE.validation = function(name.simulation # Studied.habitat if(is.null(studied.habitat)){ studied.habitat = studied.habitat #if null, the function will study all the habitats in the map - } else if(is.character(studied.habitat)){ + } else if(is.data.frame(studied.habitat)){ studied.habitat = studied.habitat #if a character vector with habitat names, the function will study only the habitats in the vector } else{ stop("studied.habitat is not a vector of character") diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index 856b9e6..930407f 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -122,7 +122,7 @@ get.observed.distribution<-function(name.simulation { # cas où on utilise les levels définis dans la carte table.habitat.releve = levels(hab.obs)[[1]] mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat$habitat), ] print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) } else { diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 131d679..92bff96 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -191,7 +191,7 @@ train.RF.habitat = function(releves.PFG { # cas où on utilise les levels définis dans la carte table.habitat.releve = levels(hab.obs)[[1]] mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat), ] + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat$habitat), ] print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) } else { From 1db7b879ebc0d07576a8266dc1ccaa9c3309673d Mon Sep 17 00:00:00 2001 From: MayaGueguen Date: Thu, 24 Mar 2022 11:34:06 +0100 Subject: [PATCH 074/176] Small comment about what is redundant and could be merged --- R/PRE_FATE.skeletonDirectory.R | 11 ++++------- R/UTILS.do_PFG_composition_validation.R | 3 --- R/UTILS.do_habitat_validation.R | 6 +++++- R/UTILS.train_RF_habitat.R | 8 ++++---- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/R/PRE_FATE.skeletonDirectory.R b/R/PRE_FATE.skeletonDirectory.R index 857a4a1..9439f2e 100644 --- a/R/PRE_FATE.skeletonDirectory.R +++ b/R/PRE_FATE.skeletonDirectory.R @@ -71,14 +71,11 @@ ##' \item{\code{RESULTS}}{this folder will collect all the results produced by the ##' software with a folder for each simulation} ##' \item{\code{VALIDATION}}{this folder will collect all the validation files produced -##' by POST_FATE.validation function +##' by the \code{\link{POST_FATE.validation}} function ##' \describe{ -##' \item{\code{HABITAT}}{this folder will collect all the validation files produces -##' by the function POST_FATE.validation with habitat validation activated} -##' \item{\code{PFG_RICHNESS}}{this folder will collect all the validation files produces -##' by the function POST_FATE.validation with PFG richness validation activated} -##' \item{\code{PFG_COMPOSITION}}{this folder will collect all the validation files produces -##' by the function POST_FATE.validation with PFG composition validation activated} +##' \item{\code{HABITAT}}{files containing outputs from habitat validation} +##' \item{\code{PFG_RICHNESS}}{files containing outputs from PFG richness validation} +##' \item{\code{PFG_COMPOSITION}}{files containing outputs from PFG composition validation} ##' } ##' } ##' } diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index 01ab98f..eae0cd9 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -289,10 +289,7 @@ do.PFG.composition.validation<-function(name.simulation, sim.version, hab.obs, P simulated.distribution <- filter(simulated.distribution, rank != 0) proximity <- simulated.distribution[,compute.proximity(simulated.quantile = simulated.quantile, observed.quantile = observed.quantile), by = c("PFG", "habitat", "strata")] - - proximity <- rename(proximity, "proximity" = "V1") - proximity <- proximity[order(habitat, strata, PFG)] #to have output in the same order for all simulations diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index ad4ae4f..5c514d9 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -110,7 +110,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat #check consistency for PFG & strata classes between FATE output vs the RF model RF.predictors <- rownames(RF.model$importance) - RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) + RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) ## same problem here : PFG names are not necessary 2 letters long ==> strsplit(x, "_") instead ? FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] , abs.simulParam = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) @@ -202,6 +202,10 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat } ## SIMILAR to what was done in train_RF_habitat for the releves + ## If similar for training and validation, what about having one small function gathering those steps, that would be called for training and then for validation, but changing data + ## and then maybe, the rest of this function could be integrated within the main function (POST_FATE.validation) ? + ## OR to merge the similar steps between this part and the do_PFG_composition_validation ? + #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 92bff96..31d1012 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -129,7 +129,7 @@ train.RF.habitat = function(releves.PFG ######################################### #transformation into coverage percentage - if(!is.numeric(releves.PFG$abund)) # Braun-Blanquet abundance + if(!is.numeric(releves.PFG$abund)) # Braun-Blanquet abundance ## Not sure that this should be kept { releves.PFG <- filter(releves.PFG,is.element(abund,c(NA, "NA", 0, "+", "r", 1:5))) releves.PFG$coverage = PRE_FATE.abundBraunBlanquet(releves.PFG$abund)/100 @@ -173,7 +173,7 @@ train.RF.habitat = function(releves.PFG mat.PFG.agg = merge(releves.sites, mat.PFG.agg, by = "site") #get habitat code and name - mat.PFG.agg$code.habitat = raster::extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) + mat.PFG.agg$code.habitat = extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] if (nrow(mat.PFG.agg) == 0) { stop("Code habitat vector is empty. Please verify values of your hab.obs map") @@ -187,7 +187,7 @@ train.RF.habitat = function(releves.PFG mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) - } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) + } else if (names(levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) { # cas où on utilise les levels définis dans la carte table.habitat.releve = levels(hab.obs)[[1]] mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") @@ -200,7 +200,7 @@ train.RF.habitat = function(releves.PFG #(optional) keep only releves data in a specific area if (!is.null(external.training.mask)) { - val.inMask = raster::extract(x = external.training.mask, y = mat.PFG.agg[, c("x", "y")]) + val.inMask = extract(x = external.training.mask, y = mat.PFG.agg[, c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(val.inMask)), ] print("'releve' map has been cropped to match 'external.training.mask'.") } From e33e53fdcab3e7afa41c6e726ff06b47809e4653 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 25 Mar 2022 11:16:10 +0100 Subject: [PATCH 075/176] aggregation of habitat & PFG composition validation in POST_FATE.validation function & associated correction in UTILS compo & habitat validation functions --- R/POST_FATE.validation.R | 313 ++++++++++++---------- R/UTILS.do_PFG_composition_validation.R | 342 +++++++++--------------- R/UTILS.do_habitat_validation.R | 241 ++++------------- R/UTILS.get_observed_distribution.R | 3 +- R/UTILS.train_RF_habitat.R | 92 +++---- 5 files changed, 390 insertions(+), 601 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 851a01e..3ba1c9f 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -194,33 +194,34 @@ POST_FATE.validation = function(name.simulation , list.PFG , exclude.PFG = NULL){ - if(doHabitat == TRUE){ + if (doHabitat == TRUE | doComposition == TRUE){ cat("\n\n #------------------------------------------------------------#") - cat("\n # HABITAT VALIDATION") + cat("\n # CHECKS & DATA PREPARATION") cat("\n #------------------------------------------------------------# \n") - ## GLOBAL PARAMETERS - dir.create(file.path(name.simulation, "VALIDATION", "HABITAT", sim.version), showWarnings = FALSE) + dir.create(file.path(name.simulation, "VALIDATION", "PFG_COMPOSITION", sim.version), showWarnings = FALSE) + + ####################### + # 0. Global parameters + ####################### # General - output.path = paste0(name.simulation, "/VALIDATION") year = year # choice in the year for validation perStrata = perStrata opt.no_CPU = opt.no_CPU - # For habitat validation # Observed releves data releves.PFG = releves.PFG + releves.sites = releves.sites if(perStrata==TRUE){ - list.strata.releves = as.character(unique(releves.PFG$strata)) - list.strata.simulations = list.strata.simulations + list.strata.releves = as.character(unique(releves.PFG$strata)) + list.strata.simulations = list.strata.simulations }else { list.strata.releves = NULL list.strata.simulations = NULL } - releves.sites = releves.sites # Habitat map hab.obs = hab.obs @@ -258,8 +259,8 @@ POST_FATE.validation = function(name.simulation print("setting origin validation mask to match simulation.map") raster::origin(validation.mask) <- raster::origin(simulation.map) } - - # Other + + # Studied habitat if(is.null(studied.habitat)){ studied.habitat = studied.habitat #if null, the function will study all the habitats in the map } else if(is.data.frame(studied.habitat)){ @@ -267,159 +268,175 @@ POST_FATE.validation = function(name.simulation } else{ stop("studied.habitat is not a data frame") } - RF.param = list( - share.training = 0.7, - ntree = 500) - predict.all.map = T - - ## TRAIN A RF ON OBSERVED DATA - - RF.model = train.RF.habitat(releves.PFG = releves.PFG - , releves.sites = releves.sites - , hab.obs = hab.obs - , external.training.mask = NULL - , studied.habitat = studied.habitat - , RF.param = RF.param - , output.path = output.path - , perStrata = perStrata - , sim.version = sim.version) - - ## USE THE RF MODEL TO VALIDATE FATE OUTPUT - - habitats.results = do.habitat.validation(output.path = output.path - , RF.model = RF.model - , habitat.FATE.map = habitat.FATE.map - , validation.mask = validation.mask - , simulation.map = simulation.map - , predict.all.map = predict.all.map - , sim.version = sim.version - , name.simulation = name.simulation - , perStrata = perStrata - , hab.obs = hab.obs - , year = year - , list.strata.releves = list.strata.releves - , list.strata.simulations = list.strata.simulations - , opt.no_CPU = opt.no_CPU - , studied.habitat = studied.habitat) - - ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT - - # Provide a color df - col.df = data.frame( - habitat = RF.model$classes, - failure = terrain.colors(length(RF.model$classes), alpha = 0.5), - success = terrain.colors(length(RF.model$classes), alpha = 1)) - - prediction.map = plot.predicted.habitat(predicted.habitat = habitats.results - , col.df = col.df - , simulation.map = simulation.map - , output.path = output.path - , sim.version = sim.version) - } - - if(doComposition == TRUE){ + ####################### + # I. Preliminary checks + ####################### + + #check if strata definition used in the RF model is the same as the one used to analyze FATE output + if (perStrata == TRUE) { + if (all(intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))) { + list.strata = names(list.strata.simulations) + print("strata definition OK") + } else { + stop("wrong strata definition") + } + } else if (perStrata == FALSE) { + list.strata <- "all" + } else { + stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") + } - cat("\n\n #------------------------------------------------------------#") - cat("\n # PFG COMPOSITION VALIDATION") - cat("\n #------------------------------------------------------------# \n") + #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) + if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map) == res(validation.mask))){ + stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") + } + + ####################################### + # II. Prepare database for FATE habitat + ####################################### + + #habitat df for the whole simulation area + habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) + , code.habitat = getValues(habitat.FATE.map) + , for.validation = getValues(validation.mask)) + habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] #index of the pixels in the simulation area + habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] + if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2){ + habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(studied.habitat,c(ID,habitat)), by.x = "code.habitat", by.y = "ID") + habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] + } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)){ + habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") + habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] + } + + print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) + + print("Habitat in the simulation area:") + table(habitat.whole.area.df$habitat, useNA = "always") + + print("Habitat in the subpart of the simulation area used for validation:") + table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") + + + ####################### + # III. Data preparation + ####################### + + #get simulated abundance per pixel*strata*PFG for pixels in the simulation area + if (perStrata == FALSE) { + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))) + { + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns + #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function + colnames(simu_PFG) = c("PFG", "pixel", "abs") + simu_PFG$strata <- "A" + }else + { + stop("Simulated abundance file does not exist") + } + + } else if (perStrata == TRUE) { + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))) + { + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + new.strata <- rep(NA, nrow(simu_PFG)) + for (i in 1:length(list.strata.simulations)) { + ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) + new.strata[ind] = names(list.strata.simulations)[i] + } + simu_PFG$strata = new.strata + }else + { + stop("Simulated abundance file does not exist") + } + } - ## GLOBAL PARAMETERS + simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") - if(doHabitat == FALSE){ + if (doHabitat == TRUE){ - perStrata = perStrata - opt.no_CPU = opt.no_CPU + cat("\n\n #------------------------------------------------------------#") + cat("\n # HABITAT VALIDATION") + cat("\n #------------------------------------------------------------# \n") - # Habitat map - hab.obs = hab.obs - validation.mask = validation.mask + output.path = paste0(name.simulation, "/VALIDATION") - # Simulation mask - name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), - flag = "MASK", - flag.split = "^--.*--$", - is.num = FALSE) #isolate the access path to the simulation mask for any FATE simulation - simulation.map = raster(paste0(name)) + ## TRAIN A RF ON OBSERVED DATA - # Check hab.obs map - if(!compareCRS(simulation.map, hab.obs) | !all(res(habitat.FATE.map)==res(simulation.map))){ - stop(paste0("Projection & resolution of hab.obs map does not match with simulation mask. Please reproject hab.obs map with projection & resolution of ", names(simulation.map))) - }else if(extent(simulation.map) != extent(hab.obs)){ - habitat.FATE.map = crop(hab.obs, simulation.map) - }else { - habitat.FATE.map = hab.obs - } - if(!all(origin(simulation.map) == origin(habitat.FATE.map))){ - print("setting origin habitat.FATE.map to match simulation.map") - raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) - } + RF.param = list(share.training = 0.7, ntree = 500) - # Check validation mask - if(!compareCRS(simulation.map, validation.mask) | !all(res(validation.mask)==res(simulation.map))){ - stop(paste0("Projection & resolution of validation mask does not match with simulation mask. Please reproject validation mask with projection & resolution of ", names(simulation.map))) - }else if(extent(validation.mask) != extent(simulation.map)){ - validation.mask = crop(validation.mask, simulation.map) - }else { - validation.mask = validation.mask - } - if(!all(origin(simulation.map) == origin(validation.mask))){ - print("setting origin validation mask to match simulation.map") - raster::origin(validation.mask) <- raster::origin(simulation.map) - } + RF.model = train.RF.habitat(releves.PFG = releves.PFG + , releves.sites = releves.sites + , hab.obs = hab.obs + , external.training.mask = NULL + , studied.habitat = studied.habitat + , RF.param = RF.param + , output.path = output.path + , perStrata = perStrata + , sim.version = sim.version) - # Get observed distribution - releves.PFG = releves.PFG - releves.sites = releves.sites + ## USE THE RF MODEL TO VALIDATE FATE OUTPUT - # Do PFG composition validation - if(perStrata==TRUE){ - list.strata.releves = as.character(unique(releves.PFG$strata)) - list.strata.simulations = list.strata.simulations - }else { - list.strata.releves = NULL - list.strata.simulations = NULL + # check consistency for PFG & strata classes between FATE output vs the RF model + RF.predictors <- rownames(RF.model$importance) + # RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) + RF.PFG <- str_split(RF.predictors, "_")[[1]][1] # à vérifier + FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] + , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) + if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0) { + stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") } - # Studied.habitat - if(is.null(studied.habitat)){ - studied.habitat = studied.habitat #if null, the function will study all the habitats in the map - } else if(is.data.frame(studied.habitat)){ - studied.habitat = studied.habitat #if a character vector with habitat names, the function will study only the habitats in the vector - } else{ - stop("studied.habitat is not a vector of character") - } + predict.all.map = TRUE + + habitats.results = do.habitat.validation() + + ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT + + # Provide a color df + col.df = data.frame( + habitat = RF.model$classes, + failure = terrain.colors(length(RF.model$classes), alpha = 0.5), + success = terrain.colors(length(RF.model$classes), alpha = 1)) + + prediction.map = plot.predicted.habitat(predicted.habitat = habitats.results + , col.df = col.df + , simulation.map = simulation.map + , output.path = output.path + , sim.version = sim.version) } - ## GET OBSERVED DISTRIBUTION - - obs.distri = get.observed.distribution(name.simulation = name.simulation - , releves.PFG = releves.PFG - , releves.sites = releves.sites - , hab.obs = hab.obs - , studied.habitat = studied.habitat - , PFG.considered_PFG.compo = PFG.considered_PFG.compo - , strata.considered_PFG.compo = strata.considered_PFG.compo - , habitat.considered_PFG.compo = habitat.considered_PFG.compo - , perStrata = perStrata - , sim.version = sim.version) - - ## DO PFG COMPOSITION VALIDATION - - performance.composition = do.PFG.composition.validation(name.simulation = name.simulation - , sim.version = sim.version - , hab.obs = hab.obs - , PFG.considered_PFG.compo = PFG.considered_PFG.compo - , strata.considered_PFG.compo = strata.considered_PFG.compo - , habitat.considered_PFG.compo = habitat.considered_PFG.compo - , observed.distribution = obs.distri - , perStrata = perStrata - , validation.mask = validation.mask - , year = year - , list.strata.simulations = list.strata.simulations - , list.strata.releves = list.strata.releves - , habitat.FATE.map = habitat.FATE.map) + if (doComposition == TRUE){ + + cat("\n\n #------------------------------------------------------------#") + cat("\n # PFG COMPOSITION VALIDATION") + cat("\n #------------------------------------------------------------# \n") + + output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) + + ## GET OBSERVED DISTRIBUTION + + obs.distri = get.observed.distribution(name.simulation = name.simulation + , releves.PFG = releves.PFG + , releves.sites = releves.sites + , hab.obs = hab.obs + , studied.habitat = studied.habitat + , PFG.considered_PFG.compo = PFG.considered_PFG.compo + , strata.considered_PFG.compo = strata.considered_PFG.compo + , habitat.considered_PFG.compo = habitat.considered_PFG.compo + , perStrata = perStrata + , sim.version = sim.version) + + ## DO PFG COMPOSITION VALIDATION + + performance.composition = do.PFG.composition.validation() + + } } diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index 01ab98f..41fb596 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -68,254 +68,149 @@ ##' ### END OF HEADER ############################################################## +# params en trop : name.simulation, hab.obs, perStrata, validation.mask, year, list.strata.simulations, list.strata.releves, habitat.FATE.map +# manquant : simu_PFG, habitat.whole.area -do.PFG.composition.validation<-function(name.simulation, sim.version, hab.obs, PFG.considered_PFG.compo - , strata.considered_PFG.compo, habitat.considered_PFG.compo, observed.distribution - , perStrata, validation.mask, year, list.strata.simulations, list.strata.releves - , habitat.FATE.map, studied.habitat){ +do.PFG.composition.validation <- function(sim.version, PFG.considered_PFG.compo + , strata.considered_PFG.compo, habitat.considered_PFG.compo + , observed.distribution){ cat("\n ---------- PFG COMPOSITION VALIDATION \n") - output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) - #Auxiliary function to compute proximity (on a 0 to 1 scale, 1 means quantile equality) - compute.proximity<-function(simulated.quantile,observed.quantile){ + compute.proximity <- function(simulated.quantile,observed.quantile){ #for a given PFG*habitat*strata, return a "distance", computed as the sum of the absolute gap between observed and simulated quantile return(1-sum(abs(simulated.quantile-observed.quantile))/4) } - ############################ - # 1. Preliminary checks - ############################ - - #check if strata definition used in the RF model is the same as the one used to analyze FATE output - if (perStrata == TRUE) { - if (all(intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))) { - list.strata = names(list.strata.simulations) - print("strata definition OK") - } else { - stop("wrong strata definition") - } - } else if (perStrata == FALSE) { - list.strata <- "all" - } else { - stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") - } - - #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) - if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map) == res(validation.mask))){ - stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") - } + # 1. Merge with habitat + ######################## - ######################################### - # 2. Get observed habitat - ######################################### - - habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) - , code.habitat = getValues(habitat.FATE.map) - , for.validation = getValues(validation.mask)) - habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] #index of the pixels in the simulation area - habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] - if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2){ - habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(studied.habitat,c(ID,habitat)), by.x = "code.habitat", by.y = "ID") - habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] - } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)){ - habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") - habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] - } + #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable + simu_PFG <- merge(simu_PFG, habitat.whole.area.df, by = "pixel") #at this stage we have all the pixels in the simulation area - print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) + # 2. Filter the required PFG, strata and habitat + ################################################# - print("Habitat in the simulation area:") - table(habitat.whole.area.df$habitat, useNA = "always") + simu_PFG<-filter( + simu_PFG, + is.element(PFG,PFG.considered_PFG.compo)& + is.element(strata,strata.considered_PFG.compo)& + is.element(habitat,habitat.considered_PFG.compo) + ) - print("Habitat in the subpart of the simulation area used for validation:") - table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") + # 3. Transform into a relative metrics (here relative.metric is relative coverage) + ################################################################################### + #important to do it only here, because if we filter some PFG, it changes the value of the relative metric (no impact of filtering for habitat or for strata since we do it per strata, and habitat is constant across a given pixel) + #careful: if several strata/habitat are selected, the computation is made for each strata separately + simu_PFG <- as.data.frame(simu_PFG %>% group_by(pixel, strata) %>% mutate(relative.metric = round(prop.table(abs), digits = 2))) + simu_PFG$relative.metric[is.na(simu_PFG$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) + simu_PFG$abs <- NULL - ############################## - # 3. Loop on simulations - ############################## + # 4. Compute distribution per PFG, and if require per strata/habitat + ##################################################################### - print("processing simulations") - results.simul<-list() - for(i in 1:length(all_of(sim.version))) { - - # 3.1. Data preparation - ######################### - - #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - if(perStrata == FALSE){ - - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))){ - - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] - colnames(simu_PFG) = c("PFG", "pixel", "abs") - simu_PFG$strata <- "A" - - }else { - - stop("Simulated abundance file does not exist") - } - - }else if(perStrata == TRUE){ - - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))){ - - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") - new.strata <- rep(NA, nrow(simu_PFG)) - for (i in 1:length(list.strata.simulations)) { - ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) - new.strata[ind] = names(list.strata.simulations)[i] - } - simu_PFG$strata = new.strata - - }else { - - stop("Simulated abundance file does not exist") - } - } - - #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) - simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") #sum and not mean because for a given CBNA strata some PFG are present in 2 FATE strata (let's say 1 unit in each) and other are present in 3 FATE strata (let's say one unit in each), so taking the mean would suppress the info that the second PFG is more present! - - # 3.2. Merge with habitat - ########################### - - #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable - simu_PFG <- merge(simu_PFG, habitat.whole.area.df, by = "pixel") #at this stage we have all the pixels in the simulation area - - # 3.3. Filter the required PFG, strata and habitat - ################################################### - - simu_PFG<-filter( - simu_PFG, - is.element(PFG,PFG.considered_PFG.compo)& - is.element(strata,strata.considered_PFG.compo)& - is.element(habitat,habitat.considered_PFG.compo) - ) - - # 3.4.Transform into a relative metrics (here relative.metric is relative coverage) - ##################################################################################### + #prepare the df storing quantile values + simulated.distribution <- expand.grid( + PFG = PFG.considered_PFG.compo, + habitat = habitat.considered_PFG.compo, + strata = strata.considered_PFG.compo + ) + + null.quantile <- data.frame(rank = seq(0, 4, 1)) #to have 5 rows per PFG*strata*habitat + simulated.distribution <- merge(simulated.distribution, null.quantile, all = TRUE) + + if(dim(simu_PFG)[1] > 0){ - #important to do it only here, because if we filter some PFG, it changes the value of the relative metric (no impact of filtering for habitat or for strata since we do it per strata, and habitat is constant across a given pixel) - #careful: if several strata/habitat are selected, the computation is made for each strata separately - simu_PFG <- as.data.frame(simu_PFG %>% group_by(pixel, strata) %>% mutate(relative.metric = round(prop.table(abs), digits = 2))) - simu_PFG$relative.metric[is.na(simu_PFG$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) - simu_PFG$abs <- NULL + distribution <- setDT(simu_PFG)[, quantile(relative.metric), by = c("PFG", "habitat", "strata")] + distribution <- rename(distribution, "quantile" = "V1") + distribution <- data.frame(distribution, rank = seq(0, 4, 1)) #add the rank number - # 3.5. Compute distribution per PFG, and if require per strata/habitat (else all strata/habitat will be considered together) - ############################################################################################################################## + simulated.distribution <- merge(simulated.distribution, distribution, by = c("PFG", "habitat", "strata", "rank"), all.x = TRUE) # add the simulated quantiles, "all.x=T" to keep the unobserved combination (with quantile=NA then) + simulated.distribution$quantile[is.na(simulated.distribution$quantile)] <- 0 # "NA" in the previous line means that the corresponding combination PFG*strata*habitat is not present, so as a null relative abundance ! - #prepare the df storing quantile values - simulated.distribution <- expand.grid( - PFG = PFG.considered_PFG.compo, - habitat = habitat.considered_PFG.compo, - strata = strata.considered_PFG.compo + }else{ + simulated.distribution$quantile <- 0 + } + + simulated.distribution$habitat <- as.character(simulated.distribution$habitat) #else may generate problem in ordering the database + simulated.distribution$strata <- as.character(simulated.distribution$strata) #else may generate problem in ordering the database + simulated.distribution$PFG <- as.character(simulated.distribution$PFG) #else may generate problem in ordering the database + simulated.distribution$rank <- as.numeric(simulated.distribution$rank) #else may generate problem in ordering the database + + + # 5. Order the table to be able to have output in the right format + ################################################################### + + simulated.distribution <- setDT(simulated.distribution) + simulated.distribution <- simulated.distribution[order(habitat, strata, PFG, rank)] + + + # 6. Rename + ############ + + simulated.distribution <- rename(simulated.distribution, "simulated.quantile" = "quantile") + + + # 7. Rename and reorder the observed database + ############################################# + + observed.distribution$habitat <- as.character(observed.distribution$habitat) #else may generate problem in ordering the database + observed.distribution$strata <- as.character(observed.distribution$strata) #else may generate problem in ordering the database + observed.distribution$PFG <- as.character(observed.distribution$PFG) #else may generate problem in ordering the database + observed.distribution$rank <- as.numeric(observed.distribution$rank) #else may generate problem in ordering the database + + observed.distribution <- setDT(observed.distribution) + observed.distribution <- observed.distribution[order(habitat, strata, PFG, rank)] + + # "if" to check that observed and simulated databases are in the same order + if( + !( + all(simulated.distribution$PFG == observed.distribution$PFG)& + all(simulated.distribution$habitat == observed.distribution$habitat)& + all(simulated.distribution$strata == observed.distribution$strata)& + all(simulated.distribution$rank == observed.distribution$rank) ) - - null.quantile <- data.frame(rank = seq(0, 4, 1)) #to have 5 rows per PFG*strata*habitat - simulated.distribution <- merge(simulated.distribution, null.quantile, all = TRUE) - - if(dim(simu_PFG)[1] > 0){ - - distribution <- setDT(simu_PFG)[, quantile(relative.metric), by = c("PFG", "habitat", "strata")] - distribution <- rename(distribution, "quantile" = "V1") - distribution <- data.frame(distribution, rank = seq(0, 4, 1)) #add the rank number - - simulated.distribution <- merge(simulated.distribution, distribution, by = c("PFG", "habitat", "strata", "rank"), all.x = TRUE) # add the simulated quantiles, "all.x=T" to keep the unobserved combination (with quantile=NA then) - - simulated.distribution$quantile[is.na(simulated.distribution$quantile)] <- 0 # "NA" in the previous line means that the corresponding combination PFG*strata*habitat is not present, so as a null relative abundance ! - - }else{ - simulated.distribution$quantile <- 0 - } - - simulated.distribution$habitat <- as.character(simulated.distribution$habitat) #else may generate problem in ordering the database - simulated.distribution$strata <- as.character(simulated.distribution$strata) #else may generate problem in ordering the database - simulated.distribution$PFG <- as.character(simulated.distribution$PFG) #else may generate problem in ordering the database - simulated.distribution$rank <- as.numeric(simulated.distribution$rank) #else may generate problem in ordering the database - - - # 3.6. Order the table to be able to have output in the right format - ##################################################################### - simulated.distribution <- setDT(simulated.distribution) - simulated.distribution <- simulated.distribution[order(habitat, strata, PFG, rank)] - - - # 3.7. Rename - ############## - simulated.distribution <- rename(simulated.distribution, "simulated.quantile" = "quantile") - - - # 3.8 Rename and reorder the observed database - ############################################### - - observed.distribution$habitat <- as.character(observed.distribution$habitat) #else may generate problem in ordering the database - observed.distribution$strata <- as.character(observed.distribution$strata) #else may generate problem in ordering the database - observed.distribution$PFG <- as.character(observed.distribution$PFG) #else may generate problem in ordering the database - observed.distribution$rank <- as.numeric(observed.distribution$rank) #else may generate problem in ordering the database - - observed.distribution <- setDT(observed.distribution) - observed.distribution <- observed.distribution[order(habitat, strata, PFG, rank)] - - # "if" to check that observed and simulated databases are in the same order - if( - !( - all(simulated.distribution$PFG == observed.distribution$PFG)& - all(simulated.distribution$habitat == observed.distribution$habitat)& - all(simulated.distribution$strata == observed.distribution$strata)& - all(simulated.distribution$rank == observed.distribution$rank) - ) - ){ - stop("Problem in observed vs simulated database (problem in the PFG*strata*habitat considered or in the database order)") - } - - # 3.9. Merge observed and simulated data - ######################################### - - simulated.distribution <- cbind(simulated.distribution, observed.quantile = observed.distribution$observed.quantile) #quicker than a merge, but we can do it only because we have worked on the order of the DT - - # 3.10 Compute proximity between observed and simulated data, per PFG*strata*habitat - ##################################################################################### - - #we get rid off rank==0 because there is good chance that it is nearly always equal to zero both in observed and simulated data, and that would provide a favorable bias in the results - - simulated.distribution <- filter(simulated.distribution, rank != 0) - - proximity <- simulated.distribution[,compute.proximity(simulated.quantile = simulated.quantile, observed.quantile = observed.quantile), by = c("PFG", "habitat", "strata")] - - - proximity <- rename(proximity, "proximity" = "V1") - - proximity <- proximity[order(habitat, strata, PFG)] #to have output in the same order for all simulations - - - # 3.11. Aggregate results for the different PFG - ################################################ - - aggregated.proximity <- proximity[,mean(proximity), by = c("habitat", "strata")] - aggregated.proximity <- rename(aggregated.proximity, "aggregated.proximity" = "V1") - aggregated.proximity$aggregated.proximity <- round(aggregated.proximity$aggregated.proximity, digits = 2) - aggregated.proximity$simul <- sim.version - - # return(aggregated.proximity) - - #line added because the foreach method does not work - results.simul[[i]] <- aggregated.proximity - + ){ + stop("Problem in observed vs simulated database (problem in the PFG*strata*habitat considered or in the database order)") } - # 4. Put in the output format - ############################## + # 8. Merge observed and simulated data + ####################################### + + simulated.distribution <- cbind(simulated.distribution, observed.quantile = observed.distribution$observed.quantile) #quicker than a merge, but we can do it only because we have worked on the order of the DT + + # 9. Compute proximity between observed and simulated data, per PFG*strata*habitat + ################################################################################### + + #we get rid off rank==0 because there is good chance that it is nearly always equal to zero both in observed and simulated data, and that would provide a favorable bias in the results - results <- sapply(results.simul, function(X){X$aggregated.proximity}) - rownames(results) <- paste0(results.simul[[1]]$habitat, "_", results.simul[[1]]$strata) + simulated.distribution <- filter(simulated.distribution, rank != 0) + + proximity <- simulated.distribution[,compute.proximity(simulated.quantile = simulated.quantile, observed.quantile = observed.quantile), by = c("PFG", "habitat", "strata")] + proximity <- rename(proximity, "proximity" = "V1") + proximity <- proximity[order(habitat, strata, PFG)] #to have output in the same order for all simulations + + + # 10. Aggregate results for the different PFG + ############################################## + + aggregated.proximity <- proximity[,mean(proximity), by = c("habitat", "strata")] + aggregated.proximity <- rename(aggregated.proximity, "aggregated.proximity" = "V1") + aggregated.proximity$aggregated.proximity <- round(aggregated.proximity$aggregated.proximity, digits = 2) + aggregated.proximity$simul <- sim.version + + results.PFG.compo <- list() + results.PFG.compo[[1]] <- aggregated.proximity + + # 11. Put in the output format + ############################### + + results <- sapply(results.PFG.compo, function(X){X$aggregated.proximity}) + rownames(results) <- paste0(results.PFG.compo[[1]]$habitat, "_", results.PFG.compo[[1]]$strata) colnames(results) <- sim.version results <- t(results) results <- as.data.frame(results) @@ -325,5 +220,6 @@ do.PFG.composition.validation<-function(name.simulation, sim.version, hab.obs, P write.csv(results, paste0(output.path, "/performance.composition.csv"), row.names = FALSE) return(results) + } diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index ad4ae4f..7ee0c3e 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -73,210 +73,87 @@ ##' ### END OF HEADER ############################################################## +# params en trop : habitat.FATE.map, validation.mask, simulation.map, name.simulation, perStrata, hab.obs, year, list.strata.releves, opt.no_CPU, studied.habitat +# manquant : simu_PFG, habitat.whole.area -do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validation.mask - , simulation.map, predict.all.map, sim.version, name.simulation - , perStrata, hab.obs, year, list.strata.releves, list.strata.simulations - , opt.no_CPU = 1, studied.habitat = NULL) +do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim.version) { cat("\n ---------- FATE OUTPUT ANALYSIS \n") - #notes - # we prepare the relevé data in this function, but in fact we could provide them directly if we adjust the code + ####################### + # I. Data preparation + ####################### - ########################### - #I. Preliminary checks - ########################### + #transform absolute abundance into relative abundance + simu_PFG <- simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance = round(prop.table(abs), digits = 2)) #those are proportions, not percentages + simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) + simu_PFG <- as.data.frame(simu_PFG) - #check if strata definition used in the RF model is the same as the one used to analyze FATE output - if (perStrata == TRUE) { - if (all(intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))) { - list.strata = names(list.strata.simulations) - print("strata definition OK") - } else { - stop("wrong strata definition") - } - } else if (perStrata == FALSE) { - list.strata <- "all" - } else { - stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") - } + #drop the absolute abundance + simu_PFG$abs<-NULL - #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) - if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map) == res(validation.mask))){ - stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") - } + #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) + simu_PFG$PFG <- as.factor(simu_PFG$PFG) + simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$PFG), RF.PFG)))) + simu_PFG$strata <- as.factor(simu_PFG$strata) + simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$strata), list.strata)))) - #check consistency for PFG & strata classes between FATE output vs the RF model - RF.predictors <- rownames(RF.model$importance) - RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) + #cast + simu_PFG <- reshape2::dcast(simu_PFG, pixel ~ PFG * strata, value.var = c("relative.abundance"), fill = 0, drop = FALSE) - FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] - , abs.simulParam = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) + #merge PFG info and habitat + transform habitat into factor - if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0){ - stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") - } + #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable + data.FATE.PFG.habitat <- merge(simu_PFG, habitat.whole.area.df, by = "pixel") #at this stage we have all the pixels in the simulation area + data.FATE.PFG.habitat$habitat <- factor(data.FATE.PFG.habitat$habitat, levels = RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs + ############################ + # II. Prediction of habitat with the RF algorithm + ################################# - ######################################################################################### - #II. Prepare database for FATE habitat - ######################################################################################### + data.validation <- data.FATE.PFG.habitat[which(data.FATE.PFG.habitat$for.validation == 1), ] + x.validation <- dplyr::select(data.validation,all_of(RF.predictors)) + y.validation <- data.validation$habitat - #habitat df for the whole simulation area - habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) - , code.habitat = getValues(habitat.FATE.map) - , for.validation = getValues(validation.mask)) - habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] #index of the pixels in the simulation area - habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] - if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2){ - habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(studied.habitat,c(ID,habitat)), by.x = "code.habitat", by.y = "ID") - habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] - } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)){ - habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") - habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] - } + y.validation.predicted <- predict(object = RF.model, newdata = x.validation, type = "response", norm.votes = TRUE) - print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) + ############################## + # III. Analysis of the results + ################################ - print("Habitat in the simulation area:") - table(habitat.whole.area.df$habitat, useNA = "always") + confusion.validation <- confusionMatrix(data = y.validation.predicted + , reference = factor(y.validation, sort(unique(c(levels(y.validation), levels(y.validation.predicted)))))) - print("Habitat in the subpart of the simulation area used for validation:") - table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") + synthesis.validation <- data.frame(habitat = colnames(confusion.validation$table) + , sensitivity = confusion.validation$byClass[, 1] + , specificity = confusion.validation$byClass[, 2] + , weight = colSums(confusion.validation$table) / sum(colSums(confusion.validation$table))) + synthesis.validation <- synthesis.validation %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) - ############################## - # III. Loop on simulations - ############################## + aggregate.TSS.validation <- round(sum(synthesis.validation$weight * synthesis.validation$TSS, na.rm = TRUE), digits = 2) - print("processing simulations") + ######################## + # IV. Predict habitat for the whole map if option selected (do it only for a small number of simulations) + ############################################ - if (opt.no_CPU > 1) - { - if (.getOS() != "windows") - { - registerDoParallel(cores = opt.no_CPU) - } else - { - warning("Parallelisation with `foreach` is not available for Windows. Sorry.") - } + if (predict.all.map == TRUE) { + y.all.map.predicted = predict(object = RF.model, newdata = dplyr::select(data.FATE.PFG.habitat, all_of(RF.predictors)), type = "response", norm.votes = TRUE) + y.all.map.predicted = as.data.frame(y.all.map.predicted) + y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel + colnames(y.all.map.predicted) = c(sim.version, "pixel") + } else { + y.all.map.predicted <- NULL } - results.simul <- foreach(i = 1:length(all_of(sim.version))) %dopar% - { - - ########################" - # III.1. Data preparation - ######################### - - #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - if (perStrata == FALSE) { - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))) - { - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns - #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function - colnames(simu_PFG) = c("PFG", "pixel", "abs") - simu_PFG$strata <- "A" - }else - { - stop("Simulated abundance file does not exist") - } - - } else if (perStrata == TRUE) { - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))) - { - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") - new.strata <- rep(NA, nrow(simu_PFG)) - for (i in 1:length(list.strata.simulations)) { - ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) - new.strata[ind] = names(list.strata.simulations)[i] - } - simu_PFG$strata = new.strata - }else - { - stop("Simulated abundance file does not exist") - } - } - - ## SIMILAR to what was done in train_RF_habitat for the releves - #aggregate all the rows with same pixel, (new) strata and PFG (necessary since possibly several line with the same pixel+strata+PFG after strata grouping) - simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") - - #transform absolute abundance into relative abundance (no pb if all combination PFG*strata are not present, since then the value is 0!) - simu_PFG <- simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance = round(prop.table(abs), digits = 2)) #those are proportions, not percentages - simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) - simu_PFG <- as.data.frame(simu_PFG) - - #drop the absolute abundance - simu_PFG$abs<-NULL - - #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) - simu_PFG$PFG<-as.factor(simu_PFG$PFG) - simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$PFG), RF.PFG)))) - simu_PFG$strata<-as.factor(simu_PFG$strata) - simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$strata), list.strata)))) - - #cast - simu_PFG<-reshape2::dcast(simu_PFG, pixel ~ PFG * strata, value.var = c("relative.abundance"), fill = 0, drop = FALSE) - - #merge PFG info and habitat + transform habitat into factor - - #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable - data.FATE.PFG.habitat <- merge(simu_PFG, habitat.whole.area.df, by = "pixel") #at this stage we have all the pixels in the simulation area - data.FATE.PFG.habitat$habitat <- factor(data.FATE.PFG.habitat$habitat, levels = RF.model$classes) #thanks to the "levels" argument, we have the same order for the habitat factor in the RF model and in the FATE outputs - - ############################ - # III.2. Prediction of habitat with the RF algorithm - ################################# - - data.validation <- data.FATE.PFG.habitat[which(data.FATE.PFG.habitat$for.validation == 1), ] - x.validation <- dplyr::select(data.validation,all_of(RF.predictors)) - y.validation <- data.validation$habitat - - y.validation.predicted <- predict(object = RF.model, newdata = x.validation, type = "response", norm.votes = TRUE) - - ############################## - # III.3. Analysis of the results - ################################ - - confusion.validation <- confusionMatrix(data = y.validation.predicted - , reference = factor(y.validation, sort(unique(c(levels(y.validation), levels(y.validation.predicted)))))) - - synthesis.validation <- data.frame(habitat = colnames(confusion.validation$table) - , sensitivity = confusion.validation$byClass[, 1] - , specificity = confusion.validation$byClass[, 2] - , weight = colSums(confusion.validation$table) / sum(colSums(confusion.validation$table))) - synthesis.validation <- synthesis.validation %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) - - aggregate.TSS.validation <- round(sum(synthesis.validation$weight * synthesis.validation$TSS, na.rm = TRUE), digits = 2) - - ######################## - # III.4. Predict habitat for the whole map if option selected (do it only for a small number of simulations) - ############################################ - - if (predict.all.map == TRUE) { - y.all.map.predicted = predict(object = RF.model, newdata = dplyr::select(data.FATE.PFG.habitat, all_of(RF.predictors)), type = "response", norm.votes = TRUE) - y.all.map.predicted = as.data.frame(y.all.map.predicted) - y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel - colnames(y.all.map.predicted) = c(sim.version, "pixel") - } else { - y.all.map.predicted <- NULL - } - - #prepare outputs - output.validation <- c(synthesis.validation$TSS, aggregate.TSS.validation) - names(output.validation) <- c(synthesis.validation$habitat, "aggregated") - - return(list(output.validation = output.validation - , y.all.map.predicted = y.all.map.predicted)) - } - #end of the loop on simulations + + #prepare outputs + output.validation <- c(synthesis.validation$TSS, aggregate.TSS.validation) + names(output.validation) <- c(synthesis.validation$habitat, "aggregated") + + results.habitat <- list(output.validation = output.validation, y.all.map.predicted = y.all.map.predicted) #deal with the results regarding model performance - habitat.performance <- as.data.frame(matrix(unlist(lapply(results.simul, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) + habitat.performance <- as.data.frame(matrix(unlist(lapply(results.habitat, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) names(habitat.performance) <- c(RF.model$classes, "weighted") habitat.performance$simulation <- sim.version @@ -286,7 +163,7 @@ do.habitat.validation<-function(output.path, RF.model, habitat.FATE.map, validat print("habitat performance saved") #deal with the results regarding habitat prediction over the whole map - all.map.prediction = results.simul[[1]]$y.all.map.predicted + all.map.prediction = results.habitat[[1]]$y.all.map.predicted all.map.prediction = merge(all.map.prediction, dplyr::select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index 930407f..c720e13 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -57,7 +57,6 @@ ##' ### END OF HEADER ############################################################## - get.observed.distribution<-function(name.simulation , releves.PFG , releves.sites @@ -211,4 +210,4 @@ get.observed.distribution<-function(name.simulation return(observed.distribution) -} +} \ No newline at end of file diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 92bff96..cad017f 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -67,14 +67,14 @@ train.RF.habitat = function(releves.PFG - , releves.sites - , hab.obs - , external.training.mask = NULL - , studied.habitat = NULL - , RF.param - , output.path - , perStrata - , sim.version) + , releves.sites + , hab.obs + , external.training.mask = NULL + , studied.habitat = NULL + , RF.param + , output.path + , perStrata + , sim.version) { cat("\n ---------- TRAIN A RANDOM FOREST MODEL ON OBSERVED DATA \n") @@ -101,7 +101,7 @@ train.RF.habitat = function(releves.PFG stop("strata definition in releves.PFG is not in the right format. Please make sure you have a character or numeric values") } fate_PFG = .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] - , abs.simulParam = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) + , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) if (sort(as.factor(unique(releves.PFG$PFG))) != as.factor(fate_PFG$PFG)) { stop("PFG list in releves.PFG does not correspond to PFG list in FATE") @@ -129,7 +129,7 @@ train.RF.habitat = function(releves.PFG ######################################### #transformation into coverage percentage - if(!is.numeric(releves.PFG$abund)) # Braun-Blanquet abundance + if(!is.numeric(releves.PFG$abund)) # Braun-Blanquet abundance ## Not sure that this should be kept { releves.PFG <- filter(releves.PFG,is.element(abund,c(NA, "NA", 0, "+", "r", 1:5))) releves.PFG$coverage = PRE_FATE.abundBraunBlanquet(releves.PFG$abund)/100 @@ -173,7 +173,7 @@ train.RF.habitat = function(releves.PFG mat.PFG.agg = merge(releves.sites, mat.PFG.agg, by = "site") #get habitat code and name - mat.PFG.agg$code.habitat = raster::extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) + mat.PFG.agg$code.habitat = extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] if (nrow(mat.PFG.agg) == 0) { stop("Code habitat vector is empty. Please verify values of your hab.obs map") @@ -187,20 +187,20 @@ train.RF.habitat = function(releves.PFG mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) - } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) - { # cas où on utilise les levels définis dans la carte - table.habitat.releve = levels(hab.obs)[[1]] - mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat$habitat), ] - print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) - } else - { - stop("Habitat definition in hab.obs map is not correct") - } + } else if (names(levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) + { # cas où on utilise les levels définis dans la carte + table.habitat.releve = levels(hab.obs)[[1]] + mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat$habitat), ] + print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) + } else + { + stop("Habitat definition in hab.obs map is not correct") + } #(optional) keep only releves data in a specific area if (!is.null(external.training.mask)) { - val.inMask = raster::extract(x = external.training.mask, y = mat.PFG.agg[, c("x", "y")]) + val.inMask = extract(x = external.training.mask, y = mat.PFG.agg[, c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(val.inMask)), ] print("'releve' map has been cropped to match 'external.training.mask'.") } @@ -230,15 +230,15 @@ train.RF.habitat = function(releves.PFG #run optimization algo (careful : optimization over OOB...) mtry.perf = tuneRF(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), - y = releves.training$habitat, - strata = releves.training$habitat, - sampsize = nrow(releves.training), - ntreeTry = RF.param$ntree, - stepFactor = 2, - improve = 0.05, - doBest = FALSE, - plot = FALSE, - trace = FALSE) + y = releves.training$habitat, + strata = releves.training$habitat, + sampsize = nrow(releves.training), + ntreeTry = RF.param$ntree, + stepFactor = 2, + improve = 0.05, + doBest = FALSE, + plot = FALSE, + trace = FALSE) mtry.perf = as.data.frame(mtry.perf) #select mtry @@ -246,24 +246,24 @@ train.RF.habitat = function(releves.PFG #run real model model = randomForest(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), - y = releves.training$habitat, - xtest = dplyr::select(releves.testing, -c(code.habitat, site, habitat, geometry)), - ytest = releves.testing$habitat, - strata = releves.training$habitat, - sampsize = nrow(releves.training), - ntree = RF.param$ntree, - mtry = mtry, - norm.votes = TRUE, - keep.forest = TRUE) + y = releves.training$habitat, + xtest = dplyr::select(releves.testing, -c(code.habitat, site, habitat, geometry)), + ytest = releves.testing$habitat, + strata = releves.training$habitat, + sampsize = nrow(releves.training), + ntree = RF.param$ntree, + mtry = mtry, + norm.votes = TRUE, + keep.forest = TRUE) #analyse model performance # Analysis on the training sample confusion.training = confusionMatrix(data = model$predicted, reference = releves.training$habitat) synthesis.training = data.frame(habitat = colnames(confusion.training$table) - , sensitivity = confusion.training$byClass[, 1] - , specificity = confusion.training$byClass[, 2] - , weight = colSums(confusion.training$table) / sum(colSums(confusion.training$table))) + , sensitivity = confusion.training$byClass[, 1] + , specificity = confusion.training$byClass[, 2] + , weight = colSums(confusion.training$table) / sum(colSums(confusion.training$table))) #warning: prevalence is the weight of predicted habitat, not of observed habitat synthesis.training = synthesis.training %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) aggregate.TSS.training = round(sum(synthesis.training$weight * synthesis.training$TSS), digits = 2) @@ -271,9 +271,9 @@ train.RF.habitat = function(releves.PFG # Analysis on the testing sample confusion.testing = confusionMatrix(data = model$test$predicted, reference = releves.testing$habitat) synthesis.testing = data.frame(habitat = colnames(confusion.testing$table) - , sensitivity = confusion.testing$byClass[, 1] - , specificity = confusion.testing$byClass[, 2] - , weight = colSums(confusion.testing$table) / sum(colSums(confusion.testing$table))) + , sensitivity = confusion.testing$byClass[, 1] + , specificity = confusion.testing$byClass[, 2] + , weight = colSums(confusion.testing$table) / sum(colSums(confusion.testing$table))) #warning: prevalence is the weight of predicted habitat, not of observed habitat synthesis.testing = synthesis.testing %>% mutate(TSS = round(sensitivity + specificity - 1, digits = 2)) aggregate.TSS.testing = round(sum(synthesis.testing$weight * synthesis.testing$TSS), digits = 2) From 44678d0b88b0a4280b1aa06c239b7524f838228e Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 25 Mar 2022 11:50:48 +0100 Subject: [PATCH 076/176] small correction --- R/UTILS.get_observed_distribution.R | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index c720e13..6715090 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -70,9 +70,7 @@ get.observed.distribution<-function(name.simulation cat("\n ---------- GET OBSERVED DISTRIBUTION \n") - composition.mask = NULL - output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) - dir.create(file.path(output.path), recursive = TRUE, showWarnings = FALSE) + # composition.mask = NULL #1. Aggregate coverage per PFG ######################################### From a5dd3ef285a0fbe04a33676bea67c07581de275d Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 25 Mar 2022 16:57:39 +0100 Subject: [PATCH 077/176] Corrections with foreach loops in order to take into account a set of simulations and not just one in the validation --- R/POST_FATE.validation.R | 294 +++++++++++++++--------- R/UTILS.do_PFG_composition_validation.R | 31 +-- R/UTILS.do_habitat_validation.R | 39 +--- R/UTILS.get_observed_distribution.R | 6 +- R/UTILS.plot_predicted_habitat.R | 8 +- R/UTILS.train_RF_habitat.R | 14 +- 6 files changed, 212 insertions(+), 180 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 3ba1c9f..f1b40b5 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -200,9 +200,6 @@ POST_FATE.validation = function(name.simulation cat("\n # CHECKS & DATA PREPARATION") cat("\n #------------------------------------------------------------# \n") - dir.create(file.path(name.simulation, "VALIDATION", "HABITAT", sim.version), showWarnings = FALSE) - dir.create(file.path(name.simulation, "VALIDATION", "PFG_COMPOSITION", sim.version), showWarnings = FALSE) - ####################### # 0. Global parameters ####################### @@ -319,125 +316,204 @@ POST_FATE.validation = function(name.simulation table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") - ####################### - # III. Data preparation - ####################### + print("processing simulations") - #get simulated abundance per pixel*strata*PFG for pixels in the simulation area - if (perStrata == FALSE) { - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))) + if (opt.no_CPU > 1) + { + if (.getOS() != "windows") { - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns - #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function - colnames(simu_PFG) = c("PFG", "pixel", "abs") - simu_PFG$strata <- "A" - }else + registerDoParallel(cores = opt.no_CPU) + } else { - stop("Simulated abundance file does not exist") + warning("Parallelisation with `foreach` is not available for Windows. Sorry.") } - - } else if (perStrata == TRUE) { - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))) + } + results.simul = foreach(i = 1:length(all_of(sim.version))) %dopar% { - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") - new.strata <- rep(NA, nrow(simu_PFG)) - for (i in 1:length(list.strata.simulations)) { - ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) - new.strata[ind] = names(list.strata.simulations)[i] + + sim <- sim.version[i] + + #get simulated abundance per pixel*strata*PFG for pixels in the simulation area + if (perStrata == FALSE) { + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv"))) + { + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns + #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function + colnames(simu_PFG) = c("PFG", "pixel", "abs") + simu_PFG$strata <- "A" + }else + { + stop("Simulated abundance file does not exist") + } + + } else if (perStrata == TRUE) { + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv"))) + { + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv")) + simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + new.strata <- rep(NA, nrow(simu_PFG)) + for (i in 1:length(list.strata.simulations)) { + ind = which(simu_PFG$strata %in% list.strata.simulations[[i]]) + new.strata[ind] = names(list.strata.simulations)[i] + } + simu_PFG$strata = new.strata + }else + { + stop("Simulated abundance file does not exist") + } } - simu_PFG$strata = new.strata - }else - { - stop("Simulated abundance file does not exist") - } - } - - simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") + + simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") + + if (doHabitat == TRUE){ + + cat("\n\n #------------------------------------------------------------#") + cat("\n # HABITAT VALIDATION") + cat("\n #------------------------------------------------------------# \n") + + output.path = paste0(name.simulation, "/VALIDATION") + + ## TRAIN A RF ON OBSERVED DATA + + RF.param = list(share.training = 0.7, ntree = 500) + + RF.model = train.RF.habitat(releves.PFG = releves.PFG + , releves.sites = releves.sites + , hab.obs = hab.obs + , external.training.mask = NULL + , studied.habitat = studied.habitat + , RF.param = RF.param + , output.path = output.path + , perStrata = perStrata + , sim.version = sim.version) + + ## USE THE RF MODEL TO VALIDATE FATE OUTPUT + + # check consistency for PFG & strata classes between FATE output vs the RF model + RF.predictors <- rownames(RF.model$importance) + # RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) + RF.PFG <- str_split(RF.predictors, "_")[[1]][1] # à vérifier + FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] + , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) + if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0) { + stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") + } + + predict.all.map = TRUE + + results.habitat = do.habitat.validation(output.path = output.path + , RF.model = RF.model + , predict.all.map = predict.all.map + , sim.version = sim.version + , simu_PFG = simu_PFG + , habitat.whole.area.df = habitat.whole.area.df) + + #deal with the results regarding model performance + habitat.performance <- as.data.frame(matrix(unlist(lapply(results.habitat, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) + names(habitat.performance) <- c(RF.model$classes, "weighted") + habitat.performance$simulation <- sim.version + + #save + write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) + + print("habitat performance saved") + + #deal with the results regarding habitat prediction over the whole map + all.map.prediction = results.habitat$y.all.map.predicted + all.map.prediction = merge(all.map.prediction, dplyr::select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") + all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") + + #save + write.csv(all.map.prediction,paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) + + } + + if (doComposition == TRUE){ + + cat("\n\n #------------------------------------------------------------#") + cat("\n # PFG COMPOSITION VALIDATION") + cat("\n #------------------------------------------------------------# \n") + + output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION") + + ## GET OBSERVED DISTRIBUTION + + obs.distri = get.observed.distribution(name.simulation = name.simulation + , releves.PFG = releves.PFG + , releves.sites = releves.sites + , hab.obs = hab.obs + , studied.habitat = studied.habitat + , PFG.considered_PFG.compo = PFG.considered_PFG.compo + , strata.considered_PFG.compo = strata.considered_PFG.compo + , habitat.considered_PFG.compo = habitat.considered_PFG.compo + , perStrata = perStrata) + + ## DO PFG COMPOSITION VALIDATION + + performance.composition = do.PFG.composition.validation(sim = sim + , PFG.considered_PFG.compo = PFG.considered_PFG.compo + , strata.considered_PFG.compo = strata.considered_PFG.compo + , habitat.considered_PFG.compo = habitat.considered_PFG.compo + , observed.distribution = obs.distri + , simu_PFG = simu_PFG + , habitat.whole.area.df = habitat.whole.area.df) + + } + + if(doHabitat == TRUE & doComposition == TRUE){ + results = list(habitat.prediction = all.map.prediction, RF.model = RF.model, performance.compo = performance.composition) + return(results) + } + if(doHabitat == TRUE & doComposition == FALSE){ + results = list(habitat.prediction = all.map.prediction, RF.model = RF.model) + return(results) + } + if(doHabitat == FALSE & doComposition == TRUE){ + results = list(performance.compo = performance.composition) + return(results) + } + + } # end of loop - if (doHabitat == TRUE){ - - cat("\n\n #------------------------------------------------------------#") - cat("\n # HABITAT VALIDATION") - cat("\n #------------------------------------------------------------# \n") + if(doHabitat == TRUE){ output.path = paste0(name.simulation, "/VALIDATION") - ## TRAIN A RF ON OBSERVED DATA - - RF.param = list(share.training = 0.7, ntree = 500) - - RF.model = train.RF.habitat(releves.PFG = releves.PFG - , releves.sites = releves.sites - , hab.obs = hab.obs - , external.training.mask = NULL - , studied.habitat = studied.habitat - , RF.param = RF.param - , output.path = output.path - , perStrata = perStrata - , sim.version = sim.version) - - ## USE THE RF MODEL TO VALIDATE FATE OUTPUT - - # check consistency for PFG & strata classes between FATE output vs the RF model - RF.predictors <- rownames(RF.model$importance) - # RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) - RF.PFG <- str_split(RF.predictors, "_")[[1]][1] # à vérifier - FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] - , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) - if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0) { - stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") - } - - predict.all.map = TRUE - - habitats.results = do.habitat.validation() - ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT + RF.model = results.simul$RF.model + # Provide a color df col.df = data.frame( habitat = RF.model$classes, failure = terrain.colors(length(RF.model$classes), alpha = 0.5), success = terrain.colors(length(RF.model$classes), alpha = 1)) - prediction.map = plot.predicted.habitat(predicted.habitat = habitats.results + prediction.map = plot.predicted.habitat(predicted.habitat = results.simul$habitat.prediction , col.df = col.df , simulation.map = simulation.map , output.path = output.path , sim.version = sim.version) - } - if (doComposition == TRUE){ - - cat("\n\n #------------------------------------------------------------#") - cat("\n # PFG COMPOSITION VALIDATION") - cat("\n #------------------------------------------------------------# \n") + if(doComposition == TRUE){ - output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION/", sim.version) + output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION") - ## GET OBSERVED DISTRIBUTION + results.composition = results.simul$performance.compo + results.compo <- sapply(results.composition, function(X){X$aggregated.proximity}) + rownames(results.compo) <- paste0(results.composition[[1]]$habitat, "_", results.composition[[1]]$strata) + colnames(results.compo) <- sim.version + results.compo <- t(results.compo) + results.compo <- as.data.frame(results.compo) + results.compo$simulation <- rownames(results.compo) - obs.distri = get.observed.distribution(name.simulation = name.simulation - , releves.PFG = releves.PFG - , releves.sites = releves.sites - , hab.obs = hab.obs - , studied.habitat = studied.habitat - , PFG.considered_PFG.compo = PFG.considered_PFG.compo - , strata.considered_PFG.compo = strata.considered_PFG.compo - , habitat.considered_PFG.compo = habitat.considered_PFG.compo - , perStrata = perStrata - , sim.version = sim.version) - - ## DO PFG COMPOSITION VALIDATION - - performance.composition = do.PFG.composition.validation() + #save and return + write.csv(results.compo, paste0(output.path, "/performance.composition.csv"), row.names = FALSE) } - } if(doRichness == TRUE){ @@ -446,7 +522,7 @@ POST_FATE.validation = function(name.simulation cat("\n # PFG RICHNESS VALIDATION") cat("\n #------------------------------------------------------------# \n") - output.path = paste0(name.simulation, "/VALIDATION/PFG_RICHNESS/", sim.version) + output.path = paste0(name.simulation, "/VALIDATION/PFG_RICHNESS") perStrata = perStrata #list of PFG of interest @@ -464,7 +540,9 @@ POST_FATE.validation = function(name.simulation warning("Parallelisation with `foreach` is not available for Windows. Sorry.") } } - dying.PFG.list = foreach(i=1:length(sim.version)) %dopar% { + dying.PFG.list <- foreach(i = 1:length(all_of(sim.version))) %dopar% { + + sim <- sim.version[i] if(perStrata == FALSE){ @@ -474,17 +552,19 @@ POST_FATE.validation = function(name.simulation colnames(simu_PFG) = c("PFG", "pixel", "abs") } - } else if(perStrata == T){ - - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] - colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") - } + } else if(perStrata == TRUE){ + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] + colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") + + } } return(setdiff(list.PFG,unique(simu_PFG$PFG))) + } #names the results @@ -502,7 +582,7 @@ POST_FATE.validation = function(name.simulation output = list(PFG.richness.df, dying.distribution , dying.PFG.list) names(output) = c("PFG.richness.df", "dying.distribution", "dying.PFG.list") - dir.create(output.path,recursive = TRUE, showWarnings = FALSE) + dir.create(output.path, recursive = TRUE, showWarnings = FALSE) write.csv(PFG.richness.df, paste0(output.path, "/performance.richness.csv"), row.names = F) write.csv(dying.distribution, paste0(output.path, "/PFG.extinction.frequency.csv"), row.names = F) @@ -528,13 +608,13 @@ POST_FATE.validation = function(name.simulation if(doHabitat == TRUE){ - hab.pred = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/", sim.version, "/hab.pred.csv")) + hab.pred = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/hab.pred.csv")) failure = as.numeric((table(hab.pred$prediction.code)[1]/sum(table(hab.pred$prediction.code)))*100) success = as.numeric((table(hab.pred$prediction.code)[2]/sum(table(hab.pred$prediction.code)))*100) cat("\n ---------- HABITAT : \n") - cat(paste0("\n", round(failure, digits = 2), "% of habitats are not correctly predicted by ", sim.version, " \n")) - cat(paste0("\n", round(success, digits = 2), "% of habitats are correctly predicted by ", sim.version, " \n")) + cat(paste0("\n", round(failure, digits = 2), "% of habitats are not correctly predicted by the simulations \n")) + cat(paste0("\n", round(success, digits = 2), "% of habitats are correctly predicted by the simulations \n")) plot(prediction.map) } else{ @@ -546,7 +626,7 @@ POST_FATE.validation = function(name.simulation if(doComposition == TRUE){ cat("\n ---------- PFG COMPOSITION : \n") - return(performance.composition) + return(results.simul$performance.compo) } else{ diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index 41fb596..e88f672 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -68,12 +68,9 @@ ##' ### END OF HEADER ############################################################## -# params en trop : name.simulation, hab.obs, perStrata, validation.mask, year, list.strata.simulations, list.strata.releves, habitat.FATE.map -# manquant : simu_PFG, habitat.whole.area - -do.PFG.composition.validation <- function(sim.version, PFG.considered_PFG.compo +do.PFG.composition.validation <- function(sim, PFG.considered_PFG.compo , strata.considered_PFG.compo, habitat.considered_PFG.compo - , observed.distribution){ + , observed.distribution, simu_PFG, habitat.whole.area.df){ cat("\n ---------- PFG COMPOSITION VALIDATION \n") @@ -141,20 +138,17 @@ do.PFG.composition.validation <- function(sim.version, PFG.considered_PFG.compo simulated.distribution$PFG <- as.character(simulated.distribution$PFG) #else may generate problem in ordering the database simulated.distribution$rank <- as.numeric(simulated.distribution$rank) #else may generate problem in ordering the database - # 5. Order the table to be able to have output in the right format ################################################################### simulated.distribution <- setDT(simulated.distribution) simulated.distribution <- simulated.distribution[order(habitat, strata, PFG, rank)] - # 6. Rename ############ simulated.distribution <- rename(simulated.distribution, "simulated.quantile" = "quantile") - # 7. Rename and reorder the observed database ############################################# @@ -201,25 +195,10 @@ do.PFG.composition.validation <- function(sim.version, PFG.considered_PFG.compo aggregated.proximity <- proximity[,mean(proximity), by = c("habitat", "strata")] aggregated.proximity <- rename(aggregated.proximity, "aggregated.proximity" = "V1") aggregated.proximity$aggregated.proximity <- round(aggregated.proximity$aggregated.proximity, digits = 2) - aggregated.proximity$simul <- sim.version - - results.PFG.compo <- list() - results.PFG.compo[[1]] <- aggregated.proximity - - # 11. Put in the output format - ############################### - - results <- sapply(results.PFG.compo, function(X){X$aggregated.proximity}) - rownames(results) <- paste0(results.PFG.compo[[1]]$habitat, "_", results.PFG.compo[[1]]$strata) - colnames(results) <- sim.version - results <- t(results) - results <- as.data.frame(results) - results$simulation <- rownames(results) - - #save and return - write.csv(results, paste0(output.path, "/performance.composition.csv"), row.names = FALSE) + aggregated.proximity$simul <- sim - return(results) + results.PFG.compo <- list(aggregated.proximity = aggregated.proximity) + return(results.PFG.compo) } diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 7ee0c3e..54eacb5 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -58,25 +58,16 @@ ##' ##' @export ##' -##' @importFrom dplyr filter rename group_by %>% mutate rename select -##' @importFrom raster compareCRS res projectRaster extent crop origin compareRaster -##' getValues predict levels -##' @importFrom stats aggregate -##' @importFrom stringr str_sub str_split -##' @importFrom foreach foreach %dopar% +##' @importFrom dplyr group_by %>% mutate rename select +##' @importFrom raster predict ##' @importFrom reshape2 dcast ##' @importFrom caret confusionMatrix ##' @importFrom utils write.csv -##' @importFrom doParallel registerDoParallel -##' @importFrom parallel detectCores ##' @importFrom tidyselect all_of ##' ### END OF HEADER ############################################################## -# params en trop : habitat.FATE.map, validation.mask, simulation.map, name.simulation, perStrata, hab.obs, year, list.strata.releves, opt.no_CPU, studied.habitat -# manquant : simu_PFG, habitat.whole.area - -do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim.version) +do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim, simu_PFG, habitat.whole.area.df) { cat("\n ---------- FATE OUTPUT ANALYSIS \n") @@ -141,7 +132,7 @@ do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim.ve y.all.map.predicted = predict(object = RF.model, newdata = dplyr::select(data.FATE.PFG.habitat, all_of(RF.predictors)), type = "response", norm.votes = TRUE) y.all.map.predicted = as.data.frame(y.all.map.predicted) y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel - colnames(y.all.map.predicted) = c(sim.version, "pixel") + colnames(y.all.map.predicted) = c(sim, "pixel") } else { y.all.map.predicted <- NULL } @@ -151,27 +142,7 @@ do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim.ve names(output.validation) <- c(synthesis.validation$habitat, "aggregated") results.habitat <- list(output.validation = output.validation, y.all.map.predicted = y.all.map.predicted) - - #deal with the results regarding model performance - habitat.performance <- as.data.frame(matrix(unlist(lapply(results.habitat, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) - names(habitat.performance) <- c(RF.model$classes, "weighted") - habitat.performance$simulation <- sim.version - - #save - write.csv(habitat.performance, paste0(output.path, "/HABITAT/", sim.version, "/performance.habitat.csv"), row.names = FALSE) - - print("habitat performance saved") - - #deal with the results regarding habitat prediction over the whole map - all.map.prediction = results.habitat[[1]]$y.all.map.predicted - all.map.prediction = merge(all.map.prediction, dplyr::select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") - all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") - - #save - write.csv(all.map.prediction,paste0(output.path,"/HABITAT/", sim.version, "/habitat.prediction.csv"), row.names = FALSE) - - #return results - return(all.map.prediction) + return(results.habitat) } diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index 6715090..e4e8a1e 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -57,16 +57,14 @@ ##' ### END OF HEADER ############################################################## -get.observed.distribution<-function(name.simulation - , releves.PFG +get.observed.distribution<-function(releves.PFG , releves.sites , hab.obs , studied.habitat = NULL , PFG.considered_PFG.compo , strata.considered_PFG.compo , habitat.considered_PFG.compo - , perStrata - , sim.version){ + , perStrata){ cat("\n ---------- GET OBSERVED DISTRIBUTION \n") diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 10cb65b..2fd5899 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -63,7 +63,7 @@ plot.predicted.habitat = function(predicted.habitat } #compute modal predicted habitat and the proportion of simulations predicting this habitat (for each pixel) - predicted.habitat$modal.predicted.habitat = apply(dplyr::select(predicted.habitat, sim.version), 1, Mode) + predicted.habitat$modal.predicted.habitat = apply(dplyr::select(predicted.habitat, c(all_of(sim.version))), 1, Mode) predicted.habitat$modal.predicted.habitat[predicted.habitat$modal.predicted.habitat == ">1 mode"] = "ambiguous" predicted.habitat$confidence <- apply(dplyr::select(predicted.habitat, c(all_of(sim.version), modal.predicted.habitat)), 1 , FUN = function(x) count.habitat(x)) @@ -94,7 +94,7 @@ plot.predicted.habitat = function(predicted.habitat #merge the prediction df with the df containing color and habitat code predicted.habitat = merge(predicted.habitat, habitat.code.df, by.x = c("modal.predicted.habitat", "prediction.code"), by.y = c("habitat", "prediction.code")) - write.csv(x = predicted.habitat, file = paste0(output.path, "/HABITAT/", sim.version, "/hab.pred.csv")) + write.csv(x = predicted.habitat, file = paste0(output.path, "/HABITAT/hab.pred.csv")) #plot @@ -112,7 +112,7 @@ plot.predicted.habitat = function(predicted.habitat levels(prediction.map) = prediction.map.rat #save the raster - writeRaster(prediction.map, filename = paste0(output.path, "/HABITAT/", sim.version, "/synthetic.prediction.grd"), overwrite = T) + writeRaster(prediction.map, filename = paste0(output.path, "/HABITAT/synthetic.prediction.grd"), overwrite = T) #plot on R @@ -143,7 +143,7 @@ plot.predicted.habitat = function(predicted.habitat ) #save the map - ggsave(filename = "synthetic.prediction.png", plot = prediction.plot, path = paste0(output.path, "/HABITAT/", sim.version), scale = 1, dpi = 300, limitsize = F, width = 15, height = 15, units ="cm") + ggsave(filename = "synthetic.prediction.png", plot = prediction.plot, path = paste0(output.path, "/HABITAT"), scale = 1, dpi = 300, limitsize = F, width = 15, height = 15, units ="cm") #return the map return(prediction.plot) diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index cad017f..f549db6 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -100,9 +100,13 @@ train.RF.habitat = function(releves.PFG { stop("strata definition in releves.PFG is not in the right format. Please make sure you have a character or numeric values") } - fate_PFG = .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] - , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) - if (sort(as.factor(unique(releves.PFG$PFG))) != as.factor(fate_PFG$PFG)) + FATE_PFG = NULL + for(i in 1:length(all_of(sim.version))){ + fate_PFG = .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] + , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version[i], "_")[[1]][2], ".txt")) + FATE_PFG = c(FATE_PFG, fate_PFG$PFG) + } + if (sort(as.factor(unique(releves.PFG$PFG))) != sort(as.factor(unique(FATE_PFG)))) { stop("PFG list in releves.PFG does not correspond to PFG list in FATE") } @@ -208,7 +212,7 @@ train.RF.habitat = function(releves.PFG # 5. Save data ##################### - write.csv(mat.PFG.agg,paste0(output.path,"/HABITAT/", sim.version, "/obs.releves.prepared.csv"),row.names = FALSE) + write.csv(mat.PFG.agg,paste0(output.path,"/HABITAT/obs.releves.prepared.csv"),row.names = FALSE) # 6. Small adjustment in data structure ########################################## @@ -282,7 +286,7 @@ train.RF.habitat = function(releves.PFG # 8. Save and return output #######################################" - path.save = paste0(output.path, "/HABITAT/", sim.version) + path.save = paste0(output.path, "/HABITAT") write_rds(model, paste0(path.save, "/RF.model.rds"), compress = "none") write.csv(synthesis.training, paste0(path.save, "/RF_perf.per.hab_training.csv"), row.names = FALSE) From 6ed53a3dc1c7bb942e12aed617df468a24215d55 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 31 Mar 2022 17:24:42 +0200 Subject: [PATCH 078/176] Corrections for POST_FATE.validation, all associated functions and documentation of these functions --- NAMESPACE | 12 +- R/POST_FATE.validation.R | 403 +++++++++--------- R/UTILS.do_PFG_composition_validation.R | 40 +- R/UTILS.do_habitat_validation.R | 71 +-- R/UTILS.get_observed_distribution.R | 40 +- R/UTILS.plot_predicted_habitat.R | 6 +- R/UTILS.train_RF_habitat.R | 78 +--- _pkgdown.yml | 7 +- docs/reference/POST_FATE.validation.html | 125 +++--- .../reference/PRE_FATE.skeletonDirectory.html | 11 +- docs/reference/cluster.stats.html | 269 ++++-------- .../do.PFG.composition.validation.html | 61 +-- docs/reference/do.habitat.validation.html | 72 +--- .../do_PFG_composition_validation.html | 221 ++++++++++ docs/reference/do_habitat_validation.html | 227 ++++++++++ docs/reference/get.observed.distribution.html | 10 +- docs/reference/get_observed_distribution.html | 223 ++++++++++ docs/reference/index.html | 18 +- docs/reference/plot_predicted_habitat.html | 214 ++++++++++ docs/reference/train.RF.habitat.html | 12 +- docs/reference/train_RF_habitat.html | 232 ++++++++++ man/POST_FATE.validation.Rd | 125 +++--- man/PRE_FATE.skeletonDirectory.Rd | 11 +- man/do.PFG.composition.validation.Rd | 85 ---- man/do.habitat.validation.Rd | 91 ---- man/do_PFG_composition_validation.Rd | 55 +++ man/do_habitat_validation.Rd | 61 +++ ...bution.Rd => get_observed_distribution.Rd} | 18 +- ...d.habitat.Rd => plot_predicted_habitat.Rd} | 12 +- ...rain.RF.habitat.Rd => train_RF_habitat.Rd} | 11 +- src/libs/iostreams/src/zlib.o | Bin 244552 -> 244552 bytes 31 files changed, 1813 insertions(+), 1008 deletions(-) create mode 100644 docs/reference/do_PFG_composition_validation.html create mode 100644 docs/reference/do_habitat_validation.html create mode 100644 docs/reference/get_observed_distribution.html create mode 100644 docs/reference/plot_predicted_habitat.html create mode 100644 docs/reference/train_RF_habitat.html delete mode 100644 man/do.PFG.composition.validation.Rd delete mode 100644 man/do.habitat.validation.Rd create mode 100644 man/do_PFG_composition_validation.Rd create mode 100644 man/do_habitat_validation.Rd rename man/{get.observed.distribution.Rd => get_observed_distribution.Rd} (83%) rename man/{plot.predicted.habitat.Rd => plot_predicted_habitat.Rd} (89%) rename man/{train.RF.habitat.Rd => train_RF_habitat.Rd} (90%) diff --git a/NAMESPACE b/NAMESPACE index 579d50c..dece996 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,5 @@ # Generated by roxygen2: do not edit by hand -S3method(plot,predicted.habitat) export(.adaptMaps) export(.cropMaps) export(.getCutoff) @@ -55,12 +54,13 @@ export(betapart.core) export(cluster.stats) export(designLHDNorm) export(divLeinster) -export(do.PFG.composition.validation) -export(do.habitat.validation) +export(do_PFG_composition_validation) +export(do_habitat_validation) export(dunn) export(ecospat.kd) -export(get.observed.distribution) -export(train.RF.habitat) +export(get_observed_distribution) +export(plot_predicted_habitat) +export(train_RF_habitat) importFrom(FD,gowdis) importFrom(PresenceAbsence,auc) importFrom(PresenceAbsence,cmx) @@ -169,7 +169,6 @@ importFrom(grid,unit) importFrom(gridExtra,grid.arrange) importFrom(huge,huge.npn) importFrom(methods,as) -importFrom(parallel,detectCores) importFrom(parallel,mclapply) importFrom(phyloclim,niche.overlap) importFrom(prettyR,Mode) @@ -228,7 +227,6 @@ importFrom(stats,sd) importFrom(stats,var) importFrom(stats,weighted.mean) importFrom(stringr,str_split) -importFrom(stringr,str_sub) importFrom(tidyselect,all_of) importFrom(utils,combn) importFrom(utils,download.file) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index f1b40b5..95baf03 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -17,7 +17,7 @@ ##' for a \code{FATE} simulation and computes the difference between observed and simulated PFG richness. ##' ##' @param name.simulation simulation folder name. -##' @param sim.version name of the simulation to validate (it works with only one \code{sim.version}). +##' @param sim.version a character vector with the name(s) of the simulation(s) to validate. ##' @param year year of simulation for validation. ##' @param perStrata \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. ##' If \code{FALSE}, PFG abundance defined for all strata (habitat & PFG composition & PFG richness validation). @@ -28,18 +28,15 @@ ##' if \code{FALSE}, habitat validation module is disabled. ##' @param releves.PFG a data frame with abundance (column named abund) at each site ##' and for each PFG and strata (habitat & PFG composition validation). -##' @param releves.sites a data frame with coordinates and a description of the habitat associated with -##' the dominant species of each site in the studied map (habitat & PFG composition validation). ##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection ##' & resolution than simulation mask (habitat & PFG composition validation). ##' @param validation.mask a raster mask that specified which pixels need validation, with same projection ##' & resolution than simulation mask (habitat & PFG composition validation). -##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will -##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -##' in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -##' into account for the validation (habitat validation). -##' @param list.strata.simulations default \code{NULL}. A character vector which contain \code{FATE} -##' strata definition and correspondence with observed strata definition. +##' @param studied.habitat a 2 columns data frame which contains the habitats (2nd column) and the ID (1st column) +##' for each of them which will be taken into account for the validation (habitat & PFG composition validation). +##' @param list.strata.simulations If \code{perStrata} = \code{TRUE}, +##' a character vector which contain \code{FATE} strata definition and correspondence with observed strata definition. +##' If \code{perStrata} = \code{FALSE}, please specify \code{NULL} value. ##' ##' @param doComposition \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, ##' if \code{FALSE}, PFG composition validation module is disabled. @@ -61,20 +58,20 @@ ##' @details ##' ##' \describe{ -##' \item{Habitat validation}{The observed habitat is derived from a map of the area, the simulated habitat -##' is derived from \code{FATE} simulated relative abundance, based on a random forest -##' algorithm trained on observed releves data (see \code{\link{train.RF.habitat}}) \cr +##' \item{Habitat validation}{The observed habitat is derived from a map of the area or, if defined, +##' from \code{studied.habitat}, the simulated habitat is derived from \code{FATE} simulated relative +##' abundance, based on a random forest algorithm trained on observed releves data (see \code{\link{train_RF_habitat}}) \cr ##' To compare observations and simulations, the function computes confusion matrix between ##' observations and predictions and then compute the TSS for each habitat h ##' (number of prediction of habitat h/number of observation of habitat h + number of non-prediction ##' of habitat h/number of non-observation of habitat h). The final metrics this script use is the ##' mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed ##' habitat distribution. The habitat validation also provides a visual comparison of observed and -##' simulated habitat on the whole studied area (see \code{\link{do.habitat.validation}} & -##' \code{\link{plot.predicted.habitat}}).} \cr -##' \item{PFG composition validation}{This code firstly run the \code{get.observed.distribution} +##' simulated habitat on the whole studied area (see \code{\link{do_habitat_validation}} & +##' \code{\link{plot_predicted_habitat}}).} \cr +##' \item{PFG composition validation}{This code firstly run the \code{get_observed_distribution} ##' function in order to have a \code{obs.distri} file which contain the observed distribution -##' per PFG, strata and habitat. This file is also an argument for the \code{do.PFG.composition.validation} +##' per PFG, strata and habitat. This file is also an argument for the \code{do_PFG_composition_validation} ##' function run next. This second sub function provides the computation of distance between observed ##' and simulated distribution.} ##' \item{PFG richness validation}{Firstly, the function updates the \code{list.PFG} with \code{exclude.PFG} vector. @@ -89,87 +86,85 @@ ##' ##' Output files : ##' \describe{ -##' \item{\file{VALIDATION/HABITAT/sim.version}}{containing the prepared CBNA data, +##' \item{\file{VALIDATION/HABITAT}}{containing the prepared CBNA data, ##' RF model, the performance analyzes (confusion matrix and TSS) for the training and ##' testing parts of the RF model, the habitat performance file, the habitat prediction file with ##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} ##' } ##' \describe{ -##' \item{\file{VALIDATION/PFG_COMPOSITION/sim.version}}{1 .csv file which contain the proximity +##' \item{\file{VALIDATION/PFG_COMPOSITION}}{1 .csv file which contain the proximity ##' between observed and simulated data computed for each PFG/strata/habitat. \cr 1 .csv file which ##' contain the observed releves transformed into relative metrics. \cr 1 .csv file which contain ##' the final output with the distribution per PFG, strata and habitat.} ##' } ##' \describe{ -##' \item{\file{VALIDATION/PFG_RICHNESS/sim.version}}{1 .csv file of PFG richness in a \code{FATE} simulation. +##' \item{\file{VALIDATION/PFG_RICHNESS}}{1 .csv file of PFG richness in a \code{FATE} simulation. ##' \cr 1 .csv fie of the PFG extinction frequency in a \code{FATE} simulation. \cr 1 .rds file which is ##' the abundance per PFG file. ##' } ##' ##' @examples ##' -##' ## Habitat validation --------------------------------------------------------------------------------- -##' list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) -##' POST_FATE.validation(name.simulation = "FATE_Champsaur" -##' , sim.version = "SIMUL_V4.1" -##' , year = 2000 -##' , perStrata = TRUE -##' , doHabitat = TRUE -##' , obs.path = "FATE_Champsaur/DATA_OBS/" -##' , releves.PFG = "releves.PFG.abundance.csv" -##' , releves.sites = "releves.sites.shp" -##' , hab.obs = "simplified.cesbio.map.grd" -##' , validation.mask = "certain.habitat.100m.restricted.grd" -##' , studied.habitat = NULL -##' , list.strata.simulations = list.strata.simulations -##' , doComposition = FALSE -##' , doRichness = FALSE) -##' -##' ## PFG composition validation -------------------------------------------------------------------------- +##' library(raster) +##' library(sf) +##' +##' ## Define a vector to choose habitats taken into account +##' studied.habitat = data.frame(ID = c(6, 5, 7, 8), habitat = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland")) +##' ## Habitat & validation maps +##' hab.observed = raster("FATE_Champsaur/DATA_OBS/simplified.cesbio.map.grd") +##' validation.mask = raster("FATE_Champsaur/DATA_OBS/certain.habitat.100m.restricted.grd") +##' simulation.map = raster("FATE_Champsaur/DATA/MASK/MASK_Champsaur.tif") +##' hab.obs = projectRaster(from = hab.observed, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") +##' validation.mask = projectRaster(from = validation.mask, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") +##' ## Observed data +##' releves.sites = as.data.frame(st_read("FATE_Champsaur/DATA_OBS/releves.sites.shp")) +##' releves.PFG = as.data.frame(read.csv("FATE_Champsaur/DATA_OBS/releves.PFG.abundance.csv")) +##' releves.PFG = merge(releves.PFG, releves.sites[,c("site","geometry")], by = "site") +##' coor = SpatialPoints(st_coordinates(releves.PFG$geometry), proj4string = crs(hab.observed)) +##' coor = spTransform(coor, CRSobj = crs(hab.obs)) +##' releves.PFG$geometry = NULL +##' releves.PFG[,c("x","y")] = coor@coords +##' colnames(releves.PFG) = c("site", "abund", "PFG", "strata", "x", "y") +##' ## List of FATE strata & correspondence with observed strata (if perStrata = TRUE, if not = NULL) ##' list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) -##' list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +##' ## List of PFG taken into account in a FATE simulation +##' list.PFG = as.factor(c("C1", "C2", "C3", "C4", "H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5")) +##' ## Habitat, strata and PFG considered in PFG compo validation ##' habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") ##' strata.considered_PFG.compo = c("S", "M", "B") +##' PFG.considered_PFG.compo = as.factor(c("H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5")) +##' ##' POST_FATE.validation(name.simulation = "FATE_Champsaur" -##' , sim.version = "SIMUL_V4.1" -##' , year = 2000 -##' , perStrata = TRUE -##' , doHabitat = FALSE -##' , obs.path = "FATE_Champsaur/DATA_OBS/" -##' , releves.PFG = "releves.PFG.abundance.csv" -##' , releves.sites = "releves.sites.shp" -##' , hab.obs = "simplified.cesbio.map.grd" -##' , validation.mask = "certain.habitat.100m.restricted.grd" -##' , studied.habitat = NULL -##' , list.strata.simulations = list.strata.simulations -##' , doComposition = TRUE -##' , PFG.considered_PFG.compo = list.PFG -##' , habitat.considered_PFG.compo = habitat.considered -##' , strata.considered_PFG.compo = strata.considered_PFG.compo -##' , doRichness = FALSE) -##' -##' ## PFG richness validation ----------------------------------------------------------------------------- -##' list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) -##' POST_FATE.validation(name.simulation = "FATE_Champsaur" -##' , sim.version = "SIMUL_V4.1" -##' , year = 2000 -##' , perStrata = TRUE -##' , doHabitat = FALSE -##' , doComposition = FALSE -##' , doRichness = TRUE -##' , list.PFG = list.PFG -##' , exclude.PFG = NULL) +##' , sim.version = c("SIMUL_V4.1", "SIMUL_V8.1") +##' , year = 2000 +##' , perStrata = TRUE +##' , opt.no_CPU = 1 +##' , doHabitat = TRUE +##' , releves.PFG = releves.PFG +##' , hab.obs = hab.obs +##' , validation.mask = validation.mask +##' , studied.habitat = studied.habitat +##' , list.strata.simulations = list.strata.simulations +##' , doComposition = TRUE +##' , PFG.considered_PFG.compo = PFG.considered_PFG.compo +##' , habitat.considered_PFG.compo = habitat.considered +##' , strata.considered_PFG.compo = strata.considered_PFG.compo +##' , doRichness = TRUE +##' , list.PFG = list.PFG +##' , exclude.PFG = NULL) ##' ##' @export ##' ##' @importFrom stringr str_split -##' @importFrom raster raster projectRaster res crs crop origin +##' @importFrom raster raster res crop origin compareCRS extent ncell getValues levels ##' @importFrom utils read.csv write.csv ##' @importFrom foreach foreach %dopar% ##' @importFrom forcats fct_expand ##' @importFrom readr write_rds ##' @importFrom doParallel registerDoParallel -##' @importFrom parallel detectCores +##' @importFrom dplyr select rename +##' @importFrom tidyselect all_of +##' @importFrom stats aggregate ##' ### END OF HEADER ################################################################### @@ -181,11 +176,10 @@ POST_FATE.validation = function(name.simulation , opt.no_CPU = 1 , doHabitat = TRUE , releves.PFG - , releves.sites , hab.obs , validation.mask - , studied.habitat = NULL - , list.strata.simulations = NULL + , studied.habitat + , list.strata.simulations , doComposition = TRUE , PFG.considered_PFG.compo , habitat.considered_PFG.compo @@ -194,11 +188,23 @@ POST_FATE.validation = function(name.simulation , list.PFG , exclude.PFG = NULL){ - if (doHabitat == TRUE | doComposition == TRUE){ + if (doHabitat == TRUE | doComposition == TRUE){ # Habitat or composition or both validation + + if(doHabitat == TRUE & doComposition == TRUE){ + cat("\n\n #------------------------------------------------------------#") + cat("\n # HABITAT & PFG COMPOSITION VALIDATION") + cat("\n #------------------------------------------------------------# \n") + }else if(doHabitat == TRUE & doComposition == FALSE){ + cat("\n\n #------------------------------------------------------------#") + cat("\n # HABITAT VALIDATION") + cat("\n #------------------------------------------------------------# \n") + }else if(doHabitat == FALSE & doComposition == TRUE){ + cat("\n\n #------------------------------------------------------------#") + cat("\n # PFG COMPOSITION VALIDATION") + cat("\n #------------------------------------------------------------# \n") + } - cat("\n\n #------------------------------------------------------------#") - cat("\n # CHECKS & DATA PREPARATION") - cat("\n #------------------------------------------------------------# \n") + cat("\n ----------- CHECKS & DATA PREPARATION") ####################### # 0. Global parameters @@ -211,7 +217,7 @@ POST_FATE.validation = function(name.simulation # Observed releves data releves.PFG = releves.PFG - releves.sites = releves.sites + # releves.sites = releves.sites if(perStrata==TRUE){ list.strata.releves = as.character(unique(releves.PFG$strata)) list.strata.simulations = list.strata.simulations @@ -228,7 +234,7 @@ POST_FATE.validation = function(name.simulation name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), flag = "MASK", flag.split = "^--.*--$", - is.num = FALSE) #isolate the access path to the simulation mask for any FATE simulation + is.num = FALSE) # isolate the access path to the simulation mask for any FATE simulation simulation.map = raster(paste0(name)) # Check hab.obs map @@ -240,7 +246,7 @@ POST_FATE.validation = function(name.simulation habitat.FATE.map = hab.obs } if(!all(origin(simulation.map) == origin(habitat.FATE.map))){ - print("setting origin habitat.FATE.map to match simulation.map") + cat("\n setting origin habitat.FATE.map to match simulation.map \n") raster::origin(habitat.FATE.map) <- raster::origin(simulation.map) } @@ -253,15 +259,15 @@ POST_FATE.validation = function(name.simulation validation.mask = validation.mask } if(!all(origin(simulation.map) == origin(validation.mask))){ - print("setting origin validation mask to match simulation.map") + cat("\n setting origin validation mask to match simulation.map \n") raster::origin(validation.mask) <- raster::origin(simulation.map) } # Studied habitat if(is.null(studied.habitat)){ - studied.habitat = studied.habitat #if null, the function will study all the habitats in the map + stop("studied.habitat vector is null, please specify at least one habitat which will be taken into account in the validation") } else if(is.data.frame(studied.habitat)){ - studied.habitat = studied.habitat #if a character vector with habitat names, the function will study only the habitats in the vector + studied.habitat = studied.habitat # if a data frame with habitat names and codes, the function will study only the habitats in the vector } else{ stop("studied.habitat is not a data frame") } @@ -270,11 +276,11 @@ POST_FATE.validation = function(name.simulation # I. Preliminary checks ####################### - #check if strata definition used in the RF model is the same as the one used to analyze FATE output + # check if strata definition used in the RF model is the same as the one used to analyze FATE output if (perStrata == TRUE) { if (all(intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))) { list.strata = names(list.strata.simulations) - print("strata definition OK") + cat("\n strata definition OK \n") } else { stop("wrong strata definition") } @@ -284,39 +290,59 @@ POST_FATE.validation = function(name.simulation stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } - #initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) + # initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map) == res(validation.mask))){ stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") } + ####################################### + # I.2 Train a RF model on observed data + ####################################### + + if(doHabitat == TRUE){ + + cat("\n ----------- TRAIN A RANDOM FOREST MODEL ON OBSERVED DATA") + + output.path = paste0(name.simulation, "/VALIDATION") + + ## TRAIN A RF ON OBSERVED DATA + + RF.param = list(share.training = 0.7, ntree = 500) + + RF.model = train_RF_habitat(releves.PFG = releves.PFG + , hab.obs = hab.obs + , external.training.mask = NULL + , studied.habitat = studied.habitat + , RF.param = RF.param + , output.path = output.path + , perStrata = perStrata + , sim.version = sim.version) + + } + ####################################### # II. Prepare database for FATE habitat ####################################### - #habitat df for the whole simulation area + # habitat df for the whole simulation area habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) , code.habitat = getValues(habitat.FATE.map) , for.validation = getValues(validation.mask)) - habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] #index of the pixels in the simulation area + habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] # index of the pixels in the simulation area habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2){ habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(studied.habitat,c(ID,habitat)), by.x = "code.habitat", by.y = "ID") - habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] - } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)){ - habitat.whole.area.df <- merge(habitat.whole.area.df, dplyr::select(levels(hab.obs)[[1]],c(ID,habitat)), by.x = "code.habitat", by.y = "ID") - habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% RF.model$classes), ] + habitat.whole.area.df <- habitat.whole.area.df[which(habitat.whole.area.df$habitat %in% studied.habitat$habitat), ] + } else { + stop("Habitat definition in studied.habitat is not correct") + } + if(doComposition == TRUE){ + habitat.whole.area.df = habitat.whole.area.df[which(habitat.whole.area.df$for.validation == 1),] } - print(cat("Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t")) - - print("Habitat in the simulation area:") - table(habitat.whole.area.df$habitat, useNA = "always") - - print("Habitat in the subpart of the simulation area used for validation:") - table(habitat.whole.area.df$habitat[habitat.whole.area.df$for.validation == 1], useNA = "always") - + cat("\n Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t") - print("processing simulations") + cat("\n processing simulations \n") if (opt.no_CPU > 1) { @@ -328,18 +354,19 @@ POST_FATE.validation = function(name.simulation warning("Parallelisation with `foreach` is not available for Windows. Sorry.") } } - results.simul = foreach(i = 1:length(all_of(sim.version))) %dopar% + results.simul = foreach(i = 1:length(all_of(sim.version))) %dopar% # loop on simulations { sim <- sim.version[i] + cat("\n", sim, "\n") - #get simulated abundance per pixel*strata*PFG for pixels in the simulation area + # get simulated abundance per pixel*strata*PFG for pixels in the simulation area if (perStrata == FALSE) { if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv"))) { simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] #keep only the PFG, ID.pixel and abundance at any year columns - #careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function + simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] # keep only the PFG, ID.pixel and abundance at any year columns + # careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function colnames(simu_PFG) = c("PFG", "pixel", "abs") simu_PFG$strata <- "A" }else @@ -367,92 +394,49 @@ POST_FATE.validation = function(name.simulation simu_PFG <- aggregate(abs ~ pixel + strata + PFG, data = simu_PFG, FUN = "sum") - if (doHabitat == TRUE){ + if (doHabitat == TRUE){ # Only for habitat validation - cat("\n\n #------------------------------------------------------------#") - cat("\n # HABITAT VALIDATION") - cat("\n #------------------------------------------------------------# \n") - - output.path = paste0(name.simulation, "/VALIDATION") - - ## TRAIN A RF ON OBSERVED DATA - - RF.param = list(share.training = 0.7, ntree = 500) - - RF.model = train.RF.habitat(releves.PFG = releves.PFG - , releves.sites = releves.sites - , hab.obs = hab.obs - , external.training.mask = NULL - , studied.habitat = studied.habitat - , RF.param = RF.param - , output.path = output.path - , perStrata = perStrata - , sim.version = sim.version) + cat("\n ----------- HABITAT PREDICTION") ## USE THE RF MODEL TO VALIDATE FATE OUTPUT - # check consistency for PFG & strata classes between FATE output vs the RF model - RF.predictors <- rownames(RF.model$importance) - # RF.PFG <- unique(str_sub(RF.predictors, 1, 2)) - RF.PFG <- str_split(RF.predictors, "_")[[1]][1] # à vérifier - FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] - , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt")) - if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0) { - stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") - } - predict.all.map = TRUE - results.habitat = do.habitat.validation(output.path = output.path + results.habitat = do_habitat_validation(output.path = output.path , RF.model = RF.model , predict.all.map = predict.all.map - , sim.version = sim.version + , sim = sim , simu_PFG = simu_PFG - , habitat.whole.area.df = habitat.whole.area.df) - - #deal with the results regarding model performance - habitat.performance <- as.data.frame(matrix(unlist(lapply(results.habitat, "[[", 1)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) - names(habitat.performance) <- c(RF.model$classes, "weighted") - habitat.performance$simulation <- sim.version - - #save - write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) - - print("habitat performance saved") - - #deal with the results regarding habitat prediction over the whole map - all.map.prediction = results.habitat$y.all.map.predicted - all.map.prediction = merge(all.map.prediction, dplyr::select(habitat.whole.area.df, c(pixel,habitat)), by = "pixel") - all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") - - #save - write.csv(all.map.prediction,paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) + , habitat.whole.area.df = habitat.whole.area.df + , list.strata = list.strata + , perStrata = perStrata) } - if (doComposition == TRUE){ + if (doComposition == TRUE){ # Only for PFG composition validation - cat("\n\n #------------------------------------------------------------#") - cat("\n # PFG COMPOSITION VALIDATION") - cat("\n #------------------------------------------------------------# \n") + cat("\n ----------- PFG COMPOSITION VALIDATION") - output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION") + output.path.compo = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION") ## GET OBSERVED DISTRIBUTION - obs.distri = get.observed.distribution(name.simulation = name.simulation - , releves.PFG = releves.PFG - , releves.sites = releves.sites + cat("\n Get observed distribution \n") + + obs.distri = get_observed_distribution(releves.PFG = releves.PFG , hab.obs = hab.obs , studied.habitat = studied.habitat , PFG.considered_PFG.compo = PFG.considered_PFG.compo , strata.considered_PFG.compo = strata.considered_PFG.compo , habitat.considered_PFG.compo = habitat.considered_PFG.compo - , perStrata = perStrata) + , perStrata = perStrata + , output.path = output.path.compo) ## DO PFG COMPOSITION VALIDATION - performance.composition = do.PFG.composition.validation(sim = sim + cat("\n Comparison between observed and simulated distribution \n") + + performance.composition = do_PFG_composition_validation(sim = sim , PFG.considered_PFG.compo = PFG.considered_PFG.compo , strata.considered_PFG.compo = strata.considered_PFG.compo , habitat.considered_PFG.compo = habitat.considered_PFG.compo @@ -463,27 +447,41 @@ POST_FATE.validation = function(name.simulation } if(doHabitat == TRUE & doComposition == TRUE){ - results = list(habitat.prediction = all.map.prediction, RF.model = RF.model, performance.compo = performance.composition) + results = list(habitat.prediction = results.habitat$y.all.map.predicted, habitat.performance = results.habitat$output.validation, RF.model = RF.model, performance.compo = performance.composition) return(results) } if(doHabitat == TRUE & doComposition == FALSE){ - results = list(habitat.prediction = all.map.prediction, RF.model = RF.model) + results = list(habitat.prediction = results.habitat$all.map.prediction, habitat.performance = output.performance.habitat, RF.model = RF.model) return(results) } if(doHabitat == FALSE & doComposition == TRUE){ results = list(performance.compo = performance.composition) return(results) - } - - } # end of loop + } # Based on choice of the user, foreach loop returns different results + + } # End of loop on simulations + cat("\n End of loop on simulations") - if(doHabitat == TRUE){ + if(doHabitat == TRUE){ # If habitat validation activated, the function uses the results to build and save a final map of habitat prediction + # deal with the results regarding model performance output.path = paste0(name.simulation, "/VALIDATION") + RF.model = results.simul[[1]]$RF.model + habitat.performance <- as.data.frame(matrix(unlist(lapply(results.simul,"[[", 2)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) + colnames(habitat.performance) <- c(RF.model$classes, "weighted") + habitat.performance$simulation <- sim.version + # save + write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) - ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT + # deal with the results regarding habitat prediction over the whole map + all.map.prediction = as.data.frame(lapply(results.simul, "[[", 1)) + all.map.prediction = all.map.prediction[,c(sim.version, "pixel", "habitat")] + all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") + # save + write.csv(all.map.prediction, paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) + cat("\n Habitat results saved \n") - RF.model = results.simul$RF.model + ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT # Provide a color df col.df = data.frame( @@ -491,32 +489,35 @@ POST_FATE.validation = function(name.simulation failure = terrain.colors(length(RF.model$classes), alpha = 0.5), success = terrain.colors(length(RF.model$classes), alpha = 1)) - prediction.map = plot.predicted.habitat(predicted.habitat = results.simul$habitat.prediction + prediction.map = plot_predicted_habitat(predicted.habitat = all.map.prediction , col.df = col.df , simulation.map = simulation.map , output.path = output.path , sim.version = sim.version) + + cat("\n Predicted habitat plot saved \n") } - if(doComposition == TRUE){ + if(doComposition == TRUE){ # If PFG composition validation activated, the function uses the results to save a table with proximity of PFG composition for each PFG and habitat*strata define by the user - output.path = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION") + output.path.compo = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION") - results.composition = results.simul$performance.compo - results.compo <- sapply(results.composition, function(X){X$aggregated.proximity}) - rownames(results.compo) <- paste0(results.composition[[1]]$habitat, "_", results.composition[[1]]$strata) - colnames(results.compo) <- sim.version - results.compo <- t(results.compo) - results.compo <- as.data.frame(results.compo) - results.compo$simulation <- rownames(results.compo) + results.compo = sapply(results.simul, "[[", "performance.compo") + results <- sapply(results.compo, function(X){X$aggregated.proximity}) + rownames(results) <- paste0(results.compo[[1]]$habitat, "_", results.compo[[1]]$strata) + colnames(results) <- sim.version + results.compo <- t(results) + results.compo <- as.data.frame(results) + results.compo$simulation <- rownames(results) #save and return - write.csv(results.compo, paste0(output.path, "/performance.composition.csv"), row.names = FALSE) + write.csv(results.compo, paste0(output.path.compo, "/performance.composition.csv"), row.names = FALSE) + cat("\n Performance composition file saved \n") } - } + } # End of (doHabitat | doComposition) condition - if(doRichness == TRUE){ + if(doRichness == TRUE){ # PFG Richness validation cat("\n\n #------------------------------------------------------------#") cat("\n # PFG RICHNESS VALIDATION") @@ -528,7 +529,7 @@ POST_FATE.validation = function(name.simulation #list of PFG of interest list.PFG = setdiff(list.PFG,exclude.PFG) - print("processing simulations") + cat("\n Data preparation \n") if (opt.no_CPU > 1) { @@ -540,23 +541,25 @@ POST_FATE.validation = function(name.simulation warning("Parallelisation with `foreach` is not available for Windows. Sorry.") } } - dying.PFG.list <- foreach(i = 1:length(all_of(sim.version))) %dopar% { + dying.PFG.list <- foreach(i = 1:length(all_of(sim.version))) %dopar% { # Loop on simulations sim <- sim.version[i] if(perStrata == FALSE){ - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv"))){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim.version, ".csv")) + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv"))){ + + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv")) simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] colnames(simu_PFG) = c("PFG", "pixel", "abs") + } } else if(perStrata == TRUE){ - if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv"))){ + if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv"))){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim.version, ".csv")) + simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv")) simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") @@ -565,20 +568,22 @@ POST_FATE.validation = function(name.simulation return(setdiff(list.PFG,unique(simu_PFG$PFG))) - } + } # End of loop + + cat("\n Richness computation \n") - #names the results + # names the results names(dying.PFG.list) = sim.version - #get table with PFG richness + # get table with PFG richness PFG.richness.df = data.frame(simulation = names(dying.PFG.list), richness = length(list.PFG) - unlist(lapply(dying.PFG.list, FUN = "length"))) - #get vector with one occurence per PFG*simulation with dying of the PFG, as factor with completed levels in order to have table with all PFG, including those which never die + # get vector with one occurence per PFG*simulation with dying of the PFG, as factor with completed levels in order to have table with all PFG, including those which never die dyingPFG.vector = as.factor(unlist(dying.PFG.list)) dyingPFG.vector = fct_expand(dyingPFG.vector, list.PFG) dying.distribution = round(table(dyingPFG.vector)/length(sim.version), digits = 2) - #output + # output output = list(PFG.richness.df, dying.distribution , dying.PFG.list) names(output) = c("PFG.richness.df", "dying.distribution", "dying.PFG.list") @@ -588,6 +593,7 @@ POST_FATE.validation = function(name.simulation write.csv(dying.distribution, paste0(output.path, "/PFG.extinction.frequency.csv"), row.names = F) write_rds(dying.PFG.list, file = paste0(output.path, "/dying.PFG.list.rds"), compress = "none") + cat("\n PFG richness results saved \n") } cat("\n\n #------------------------------------------------------------#") @@ -597,8 +603,7 @@ POST_FATE.validation = function(name.simulation if(doRichness == TRUE){ cat("\n ---------- PFG RICHNESS : \n") - cat(paste0("\n Richness at year ", year, " : ", output[[1]][2], "\n")) - cat(paste0("\n Number of PFG extinction at year ", year, " : ", sum(output[[2]]), "\n")) + cat(paste0("\n Richness at year ", year, " : ", output[[1]], "\n")) } else{ @@ -626,7 +631,7 @@ POST_FATE.validation = function(name.simulation if(doComposition == TRUE){ cat("\n ---------- PFG COMPOSITION : \n") - return(results.simul$performance.compo) + return(results.compo[,sim.version]) } else{ diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index e88f672..35212a5 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -2,20 +2,16 @@ ##' ##' @title Compute distance between observed and simulated distribution ##' -##' @name do.PFG.composition.validation +##' @name do_PFG_composition_validation ##' ##' @author Matthieu Combaud, Maxime Delprat ##' ##' @description This script is designed to compare the difference between the ##' PFG distribution in observed and simulated data. For a set of PFG, strata and -##' habitats chosen, the function compute distance between observed and simulated +##' habitats chosen, the function computes distance between observed and simulated ##' distribution for a precise \code{FATE} simulation. ##' -##' @param name.simulation simulation folder name. -##' @param sim.version name of the simulation we want to validate (it works with -##' only one \code{sim.version}). -##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection -##' & resolution than simulation mask. +##' @param sim name of the single simulation to validate. ##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered ##' in the validation. ##' @param strata.considered_PFG.compo a character vector of the list of precise @@ -23,26 +19,14 @@ ##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) ##' considered in the validation. ##' @param observed.distribution PFG observed distribution table provides by \code{get.observed.distribution} function. -##' @param perStrata.compo \code{Logical}. All strata together (FALSE) or per strata (TRUE). -##' @param validation.mask a raster mask that specified -##' which pixels need validation, with same projection & resolution than simulation mask. -##' @param year year of simulation to validate. -##' @param list.strata.simulations a character vector which contain \code{FATE} -##' strata definition and correspondence with observed strata definition. -##' @param list.strata.releves a character vector which contain the observed strata -##' definition, extracted from observed PFG releves. -##' @param habitat.FATE.map a raster map of the observed habitat in the -##' studied area with same projection & resolution than validation mask and simulation mask. -##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will -##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -##' in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -##' into account for the validation. +##' @param simu_PFG a \code{data frame} with simulated abundance for each PFG and strata +##' (if option selected) and pixel ID, extracted from a \code{FATE} simulation (see \code{\link{POST_FATE.temporalEvolution}}). +##' @param habitat.whole.area.df a \code{data frame} which contain habitat names and code for each pixel that needs validation. ##' ##' @details ##' -##' After preliminary checks, this code extract observed habitat from the \code{hab.obs} -##' map and, then, merge it with the simulated PFG abundance file (with or without strata definition) -##' from results of the \code{FATE} simulation selected with \code{sim.version}. +##' Firstly, this code merges \code{habitat.whole.area.df} data frame with the simulated PFG abundance +##' \code{simu_PFG} data frame (with or without strata definition). ##' After filtration of the required PFG, strata and habitats, the function transforms ##' the data into relative metrics and, then, compute distribution per PFG, strata ##' and habitat (if necessary). Finally, the code computes proximity between observed @@ -52,7 +36,7 @@ ##' ##' 1 file is created in ##' \describe{ -##' \item{\file{VALIDATION/PFG_COMPOSITION/sim.version} : +##' \item{\file{VALIDATION/PFG_COMPOSITION} : ##' A .csv file which contain the proximity between observed and simulated data computed ##' for each PFG/strata/habitat. ##' @@ -62,17 +46,15 @@ ##' @importFrom raster raster projectRaster res crs crop extent origin compareRaster ##' getValues ncell compareCRS levels ##' @importFrom stats aggregate -##' @importFrom utils read.csv write.csv ##' @importFrom data.table setDT ##' @importFrom tidyselect all_of ##' ### END OF HEADER ############################################################## -do.PFG.composition.validation <- function(sim, PFG.considered_PFG.compo +do_PFG_composition_validation <- function(sim, PFG.considered_PFG.compo , strata.considered_PFG.compo, habitat.considered_PFG.compo , observed.distribution, simu_PFG, habitat.whole.area.df){ - cat("\n ---------- PFG COMPOSITION VALIDATION \n") #Auxiliary function to compute proximity (on a 0 to 1 scale, 1 means quantile equality) compute.proximity <- function(simulated.quantile,observed.quantile){ @@ -198,6 +180,8 @@ do.PFG.composition.validation <- function(sim, PFG.considered_PFG.compo aggregated.proximity$simul <- sim results.PFG.compo <- list(aggregated.proximity = aggregated.proximity) + names(results.PFG.compo) <- "aggregated.proximity" + return(results.PFG.compo) } diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 54eacb5..3bd6fa7 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -1,9 +1,9 @@ ### HEADER ##################################################################### ##' ##' @title Compare observed and simulated habitat of a \code{FATE} simulation -##' at the last simulation year. +##' to a chosen year. ##' -##' @name do.habitat.validation +##' @name do_habitat_validation ##' ##' @author Matthieu Combaud & Maxime Delprat ##' @@ -15,38 +15,22 @@ ##' will be created. ##' @param RF.model random forest model trained on CBNA data (train.RF.habitat ##' function) -##' @param habitat.FATE.map a raster map of the observed habitat in the -##' studied area with same projection & resolution than validation mask and simulation mask. -##' @param validation.mask a raster mask that specified -##' which pixels need validation, with same projection & resolution than simulation mask. -##' @param simulation.map a raster map of the whole studied area (provides by FATE parameters functions). ##' @param predict.all.map \code{Logical}. If TRUE, the script will predict ##' habitat for the whole map. -##' @param sim.version name of the simulation to validate. -##' @param name.simulation simulation folder name. -##' @param perStrata \code{Logical}. If TRUE, the PFG abundance is defined -##' by strata in each pixel. If FALSE, PFG abundance is defined for all strata. -##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection -##' & resolution than simulation mask. -##' @param year simulation year selected for validation. -##' @param list.strata.releves a character vector which contain the observed strata -##' definition, extracted from observed PFG releves. -##' @param list.strata.simulations a character vector which contain \code{FATE} -##' strata definition and correspondence with observed strata definition. -##' @param opt.no_CPU default \code{1}. \cr The number of -##' resources that can be used to parallelize the computation of performance of -##' habitat prediction. -##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will -##' take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -##' in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -##' into account for the validation. +##' @param sim name of the single simulation to validate. +##' @param simu_PFG a \code{data frame} with simulated abundance for each PFG and strata +##' (if option selected) and pixel ID, extracted from a \code{FATE} simulation (see \code{\link{POST_FATE.temporalEvolution}}). +##' @param habitat.whole.area.df a \code{data frame} which contain habitat names and code for each pixel that needs validation. +##' @param list.strata If abundance file is defined by strata : a character vector which contain \code{FATE} +##' strata definition and correspondence with observed strata definition. +##' If abundance file is defined for all strata : a chracter vector with value "all". +##' @param perStrata \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. +##' If \code{FALSE}, PFG abundance defined for all strata. ##' ##' @details ##' -##' After several preliminary checks, the function is going to prepare the observations -##' database by extracting the observed habitat from a raster map. Then, for the -##' simulation \code{sim.version}, the script take the evolution abundance for each PFG -##' and all strata (or for each PFG & each strata if option selected) file and predict +##' For a given simulation \code{sim}, this script takes the evolution abundance for each PFG +##' and all strata (or for each PFG & each strata if option selected) data frame and predicts ##' the habitat for the whole map (if option selected) thanks to the RF model. ##' Finally, the function computes habitat performance based on TSS for each habitat. ##' @@ -64,13 +48,28 @@ ##' @importFrom caret confusionMatrix ##' @importFrom utils write.csv ##' @importFrom tidyselect all_of +##' @importFrom stringr str_split ##' ### END OF HEADER ############################################################## -do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim, simu_PFG, habitat.whole.area.df) +do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, simu_PFG, habitat.whole.area.df, list.strata, perStrata) { - cat("\n ---------- FATE OUTPUT ANALYSIS \n") + # check consistency for PFG & strata classes between FATE output vs the RF model + RF.predictors <- rownames(RF.model$importance) + PFG <- str_split(RF.predictors, "_") + RF.PFG = NULL + for(n in 1:length(PFG)){ + pfg = PFG[[n]][1] + RF.PFG = c(RF.PFG,pfg) + RF.PFG = unique(RF.PFG) + } + FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] + , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim, "_")[[1]][2], ".txt")) + FATE.PFG = FATE.PFG$PFG + if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0) { + stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") + } ####################### # I. Data preparation @@ -82,17 +81,16 @@ do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim, s simu_PFG <- as.data.frame(simu_PFG) #drop the absolute abundance - simu_PFG$abs<-NULL + simu_PFG$abs <- NULL #correct the levels (to have all PFG and all strata) to make the dcast transfo easier (all PFG*strata combination will be automatically created thanks to the factor structure, even if no line corresponds to it) simu_PFG$PFG <- as.factor(simu_PFG$PFG) simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$PFG), RF.PFG)))) simu_PFG$strata <- as.factor(simu_PFG$strata) - simu_PFG$PFG <- factor(simu_PFG$PFG, sort(unique(c(levels(simu_PFG$strata), list.strata)))) + simu_PFG$strata <- factor(simu_PFG$strata, sort(unique(c(levels(simu_PFG$strata), list.strata)))) #cast simu_PFG <- reshape2::dcast(simu_PFG, pixel ~ PFG * strata, value.var = c("relative.abundance"), fill = 0, drop = FALSE) - #merge PFG info and habitat + transform habitat into factor #here it is crucial to have exactly the same raster structure for "simulation.map" and "habitat.FATE.map", so as to be able to do the merge on the "pixel" variable @@ -132,7 +130,8 @@ do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim, s y.all.map.predicted = predict(object = RF.model, newdata = dplyr::select(data.FATE.PFG.habitat, all_of(RF.predictors)), type = "response", norm.votes = TRUE) y.all.map.predicted = as.data.frame(y.all.map.predicted) y.all.map.predicted$pixel = data.FATE.PFG.habitat$pixel - colnames(y.all.map.predicted) = c(sim, "pixel") + y.all.map.predicted$habitat = data.FATE.PFG.habitat$habitat + colnames(y.all.map.predicted) = c(sim, "pixel", "habitat") } else { y.all.map.predicted <- NULL } @@ -142,6 +141,8 @@ do.habitat.validation <- function(output.path, RF.model, predict.all.map, sim, s names(output.validation) <- c(synthesis.validation$habitat, "aggregated") results.habitat <- list(output.validation = output.validation, y.all.map.predicted = y.all.map.predicted) + names(results.habitat) <- c("output.validation", "y.all.map.predicted") + return(results.habitat) } diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index e4e8a1e..c5829cf 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -2,7 +2,7 @@ ##' ##' @title Compute distribution of relative abundance over observed relevés ##' -##' @name get.observed.distribution +##' @name get_observed_distribution ##' ##' @author Matthieu Combaud, Maxime Delprat ##' @@ -12,8 +12,6 @@ ##' @param name.simulation simulation folder name. ##' @param releves.PFG a data frame with abundance (column named abund) at each site ##' and for each PFG and strata. -##' @param releves.sites a data frame with coordinates and a description of the habitat associated with -##' the dominant species of each site in the studied map. ##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection ##' & resolution than simulation mask. ##' @param studied.habitat default \code{NULL}. If \code{NULL}, the function will @@ -27,8 +25,6 @@ ##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) ##' considered in the validation. ##' @param perStrata \code{Logical}. All strata together (FALSE) or per strata (TRUE). -##' @param sim.version name of the simulation we want to validate (it works with -##' only one \code{sim.version}). ##' ##' @details ##' @@ -42,7 +38,7 @@ ##' ##' 2 files are created in ##' \describe{ -##' \item{\file{VALIDATION/PFG_COMPOSITION/sim.version} : +##' \item{\file{VALIDATION/PFG_COMPOSITION} : ##' 1 .csv file which contain the observed relevés transformed into relative metrics. ##' 1 .csv file which contain the final output with the distribution per PFG, strata and habitat. ##' @@ -57,16 +53,15 @@ ##' ### END OF HEADER ############################################################## -get.observed.distribution<-function(releves.PFG - , releves.sites +get_observed_distribution <- function(releves.PFG , hab.obs , studied.habitat = NULL , PFG.considered_PFG.compo , strata.considered_PFG.compo , habitat.considered_PFG.compo - , perStrata){ - - cat("\n ---------- GET OBSERVED DISTRIBUTION \n") + , perStrata + , output.path){ + # composition.mask = NULL @@ -96,33 +91,32 @@ get.observed.distribution<-function(releves.PFG #2. Get habitat information ################################### - #get sites coordinates - mat.PFG.agg = merge(releves.sites, mat.PFG.agg, by = "site") - #get habitat code and name - mat.PFG.agg$code.habitat = raster::extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) + coord = releves.PFG %>% group_by(site) %>% filter(!duplicated(site)) + mat.PFG.agg = merge(mat.PFG.agg, coord[,c("site","x","y")], by = "site") + mat.PFG.agg$code.habitat = extract(x = hab.obs, y = mat.PFG.agg[,c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] if (nrow(mat.PFG.agg) == 0) { stop("Code habitat vector is empty. Please verify values of your hab.obs map") } - #correspondance habitat code/habitat name + #correspondence habitat code/habitat name if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2) { # cas où pas de levels dans la carte d'habitat et utilisation d'un vecteur d'habitat - colnames(obs.habitat) = c("ID", "habitat") table.habitat.releve = studied.habitat + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) - } else if (names(raster::levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(raster::levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) + cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t") + } else if (names(levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) { # cas où on utilise les levels définis dans la carte table.habitat.releve = levels(hab.obs)[[1]] mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat$habitat), ] - print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) + cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t") } else { stop("Habitat definition in hab.obs map is not correct") - } + } # #(optional) keep only releves data in a specific area # if(!is.null(composition.mask)){ @@ -159,7 +153,7 @@ get.observed.distribution<-function(releves.PFG mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) mat.PFG.agg$coverage <- NULL - print("releve data have been transformed into a relative metric") + cat("\n releve data have been transformed into a relative metric \n") # 5. Save data @@ -206,4 +200,4 @@ get.observed.distribution<-function(releves.PFG return(observed.distribution) -} \ No newline at end of file +} diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 2fd5899..a13cb7d 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -3,7 +3,7 @@ ##' @title Create a raster map of habitat prediction for a specific \code{FATE} ##' simulation at the last simulation year. ##' -##' @name plot.predicted.habitat +##' @name plot_predicted_habitat ##' ##' @author Matthieu Combaud, Maxime Delprat ##' @@ -46,15 +46,13 @@ ### END OF HEADER ############################################################## -plot.predicted.habitat = function(predicted.habitat +plot_predicted_habitat = function(predicted.habitat , col.df , simulation.map , output.path , sim.version) { - cat("\n ---------- AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT \n") - #auxiliary function to compute the proportion of simulations lead to the modal prediction count.habitat = function(df){ index = which(names(df) == "modal.predicted.habitat") diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index f549db6..8732cf9 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -2,7 +2,7 @@ ##' ##' @title Create a random forest algorithm trained on CBNA data. ##' -##' @name train.RF.habitat +##' @name train_RF_habitat ##' ##' @author Matthieu Combaud, Maxime Delprat ##' @@ -11,10 +11,7 @@ ##' habitat. ##' ##' @param releves.PFG a data frame with abundance (column named abund) at each site -##' and for each PFG and strata. -##' @param releves.sites a data frame with coordinates and a description of -##' the habitat associated with the dominant species of each site in the -##' studied map. +##' and for each PFG and strata. ##' @param hab.obs a raster map of the observed habitat in the ##' extended studied area. ##' @param external.training.mask default \code{NULL}. (optional) Keep only @@ -49,13 +46,13 @@ ##' the performance analyzes (confusion matrix and TSS) for the training and ##' testing parts. ##' -##' @export +##' @export ##' ##' @importFrom dplyr filter %>% group_by select ##' @importFrom stats aggregate ##' @importFrom reshape2 dcast ##' @importFrom data.table setDT -##' @importFrom raster extract compareCRS levels +##' @importFrom raster extract compareCRS levels crs ##' @importFrom sf st_transform st_crop ##' @importFrom randomForest randomForest tuneRF ##' @importFrom caret confusionMatrix @@ -66,8 +63,7 @@ ### END OF HEADER ############################################################## -train.RF.habitat = function(releves.PFG - , releves.sites +train_RF_habitat = function(releves.PFG , hab.obs , external.training.mask = NULL , studied.habitat = NULL @@ -77,8 +73,6 @@ train.RF.habitat = function(releves.PFG , sim.version) { - cat("\n ---------- TRAIN A RANDOM FOREST MODEL ON OBSERVED DATA \n") - ############################################################################# ## CHECK parameter releves.PFG @@ -88,44 +82,18 @@ train.RF.habitat = function(releves.PFG } else { releves.PFG = as.data.frame(releves.PFG) - if (nrow(releves.PFG) == 0 || ncol(releves.PFG) != 4) + if (nrow(releves.PFG) == 0 || ncol(releves.PFG) != 6) { - .stopMessage_numRowCol("releves.PFG", c("site", "PFG", "strata", "abund")) + .stopMessage_numRowCol("releves.PFG", c("site", "PFG", "strata", "abund", "x", "y")) } if (!is.numeric(releves.PFG$site)) { stop("Sites in releves.PFG are not in the right format. Please make sure you have numeric values") } - if (!is.character(releves.PFG$strata) | !is.numeric(releves.PFG$strata)) + if (!is.character(releves.PFG$strata) & !is.numeric(releves.PFG$strata)) { stop("strata definition in releves.PFG is not in the right format. Please make sure you have a character or numeric values") } - FATE_PFG = NULL - for(i in 1:length(all_of(sim.version))){ - fate_PFG = .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] - , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version[i], "_")[[1]][2], ".txt")) - FATE_PFG = c(FATE_PFG, fate_PFG$PFG) - } - if (sort(as.factor(unique(releves.PFG$PFG))) != sort(as.factor(unique(FATE_PFG)))) - { - stop("PFG list in releves.PFG does not correspond to PFG list in FATE") - } - } - ## CHECK parameter releves.sites - if (.testParam_notDf(releves.sites)) - { - .stopMessage_beDataframe("releves.sites") - } else - { - releves.sites = as.data.frame(releves.sites) - if (nrow(releves.sites) == 0 || ncol(releves.sites) != 3) - { - .stopMessage_numRowCol("releves.sites", c("site", "x", "y")) - } - if (!is.numeric(releves.sites$site)) - { - stop("Sites in releves.sites are not in the right format. Please make sure you have numeric values") - } } @@ -160,7 +128,7 @@ train.RF.habitat = function(releves.PFG mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) mat.PFG.agg$coverage = NULL - print("releve data have been transformed into a relative metric") + cat("\n releve data have been transformed into a relative metric \n") #2. Cast the df ####################### @@ -168,16 +136,15 @@ train.RF.habitat = function(releves.PFG #transfo into factor to be sure to create all the combination when doing "dcast" mat.PFG.agg$PFG = as.factor(mat.PFG.agg$PFG) mat.PFG.agg$strata = as.factor(mat.PFG.agg$strata) - mat.PFG.agg = dcast(mat.PFG.agg, site ~ PFG + strata, value.var = "relative.metric", fill = 0, drop = FALSE) + mat.PFG.agg = reshape2::dcast(mat.PFG.agg, site ~ PFG + strata, value.var = "relative.metric", fill = 0, drop = FALSE) #3. Get habitat information ################################### - #get sites coordinates - mat.PFG.agg = merge(releves.sites, mat.PFG.agg, by = "site") - #get habitat code and name - mat.PFG.agg$code.habitat = extract(x = hab.obs, y = mat.PFG.agg[, c("x", "y")]) + coord = releves.PFG %>% group_by(site) %>% filter(!duplicated(site)) + mat.PFG.agg = merge(mat.PFG.agg, coord[,c("site","x","y")], by = "site") + mat.PFG.agg$code.habitat = extract(x = hab.obs, y = mat.PFG.agg[,c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] if (nrow(mat.PFG.agg) == 0) { stop("Code habitat vector is empty. Please verify values of your hab.obs map") @@ -186,27 +153,20 @@ train.RF.habitat = function(releves.PFG #correspondence habitat code/habitat name if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2) { # cas où pas de levels dans la carte d'habitat et utilisation d'un vecteur d'habitat - colnames(obs.habitat) = c("ID", "habitat") table.habitat.releve = studied.habitat mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - print(cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t")) - } else if (names(levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) - { # cas où on utilise les levels définis dans la carte - table.habitat.releve = levels(hab.obs)[[1]] - mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat$habitat), ] - print(cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t")) + cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t") } else { - stop("Habitat definition in hab.obs map is not correct") + stop("Habitat definition in studied.habitat is not correct") } #(optional) keep only releves data in a specific area if (!is.null(external.training.mask)) { val.inMask = extract(x = external.training.mask, y = mat.PFG.agg[, c("x", "y")]) mat.PFG.agg = mat.PFG.agg[which(!is.na(val.inMask)), ] - print("'releve' map has been cropped to match 'external.training.mask'.") + cat("\n 'releve' map has been cropped to match 'external.training.mask'. \n") } # 5. Save data @@ -233,7 +193,7 @@ train.RF.habitat = function(releves.PFG #train the model (with correction for imbalances in sampling) #run optimization algo (careful : optimization over OOB...) - mtry.perf = tuneRF(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), + mtry.perf = tuneRF(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, x, y)), y = releves.training$habitat, strata = releves.training$habitat, sampsize = nrow(releves.training), @@ -249,9 +209,9 @@ train.RF.habitat = function(releves.PFG mtry = mtry.perf$mtry[mtry.perf$OOBError == min(mtry.perf$OOBError)][1] #the lowest n achieving minimum OOB #run real model - model = randomForest(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, geometry)), + model = randomForest(x = dplyr::select(releves.training, -c(code.habitat, site, habitat, x, y)), y = releves.training$habitat, - xtest = dplyr::select(releves.testing, -c(code.habitat, site, habitat, geometry)), + xtest = dplyr::select(releves.testing, -c(code.habitat, site, habitat, x, y)), ytest = releves.testing$habitat, strata = releves.training$habitat, sampsize = nrow(releves.training), diff --git a/_pkgdown.yml b/_pkgdown.yml index af61946..618bd5e 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -145,8 +145,11 @@ reference: - ".scaleMaps" - ".getCutoff" - ".unzip_ALL" - - "train.RF.habitat" - - "do.habitat.validation" + - "train_RF_habitat" + - "do_habitat_validation" + - "do_PFG_composition_validation" + - "get_observed_distribution" + - "plot_predicted_habitat" - "plot.predicted.habitat" - "get.observed.distribution" - "do.PFG.composition.validation" diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index c230721..e18c9cd 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -163,11 +163,10 @@

Computes validation data for habitat, PFG richness and composition for a = 1, doHabitat = TRUE, releves.PFG, - releves.sites, hab.obs, validation.mask, - studied.habitat = NULL, - list.strata.simulations = NULL, + studied.habitat, + list.strata.simulations, doComposition = TRUE, PFG.considered_PFG.compo, habitat.considered_PFG.compo, @@ -183,7 +182,7 @@

Arguments

name.simulation

simulation folder name.

sim.version
-

name of the simulation to validate (it works with only one sim.version).

+

a character vector with the name(s) of the simulation(s) to validate.

year

year of simulation for validation.

perStrata
@@ -198,9 +197,6 @@

Arguments

releves.PFG

a data frame with abundance (column named abund) at each site and for each PFG and strata (habitat & PFG composition validation).

-
releves.sites
-

a data frame with coordinates and a description of the habitat associated with -the dominant species of each site in the studied map (habitat & PFG composition validation).

hab.obs

a raster map of the extended studied map in the simulation, with same projection & resolution than simulation mask (habitat & PFG composition validation).

@@ -208,13 +204,12 @@

Arguments

a raster mask that specified which pixels need validation, with same projection & resolution than simulation mask (habitat & PFG composition validation).

studied.habitat
-

default NULL. If NULL, the function will -take into account of habitats define in the hab.obs map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation (habitat validation).

+

a 2 columns data frame which contains the habitats (2nd column) and the ID (1st column) +for each of them which will be taken into account for the validation (habitat & PFG composition validation).

list.strata.simulations
-

default NULL. A character vector which contain FATE -strata definition and correspondence with observed strata definition.

+

If perStrata = TRUE, +a character vector which contain FATE strata definition and correspondence with observed strata definition. +If perStrata = FALSE, please specify NULL value.

doComposition

Logical. Default TRUE. If TRUE, PFG composition validation module is activated, if FALSE, PFG composition validation module is disabled.

@@ -246,22 +241,22 @@

Value

Details

Habitat validation
-

The observed habitat is derived from a map of the area, the simulated habitat -is derived from FATE simulated relative abundance, based on a random forest -algorithm trained on observed releves data (see train.RF.habitat)
+

The observed habitat is derived from a map of the area or, if defined, +from studied.habitat, the simulated habitat is derived from FATE simulated relative +abundance, based on a random forest algorithm trained on observed releves data (see train_RF_habitat)
To compare observations and simulations, the function computes confusion matrix between observations and predictions and then compute the TSS for each habitat h (number of prediction of habitat h/number of observation of habitat h + number of non-prediction of habitat h/number of non-observation of habitat h). The final metrics this script use is the mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed habitat distribution. The habitat validation also provides a visual comparison of observed and -simulated habitat on the whole studied area (see do.habitat.validation & - plot.predicted.habitat).

+simulated habitat on the whole studied area (see do_habitat_validation & +plot_predicted_habitat).

PFG composition validation
-

This code firstly run the get.observed.distribution +

This code firstly run the get_observed_distribution function in order to have a obs.distri file which contain the observed distribution -per PFG, strata and habitat. This file is also an argument for the do.PFG.composition.validation +per PFG, strata and habitat. This file is also an argument for the do_PFG_composition_validation function run next. This second sub function provides the computation of distance between observed and simulated distribution.

@@ -283,57 +278,53 @@

Author

Examples


-## Habitat validation ---------------------------------------------------------------------------------
-list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7))
-POST_FATE.validation(name.simulation = "FATE_Champsaur"
-                      , sim.version = "SIMUL_V4.1"
-                      , year = 2000
-                      , perStrata = TRUE
-                      , doHabitat = TRUE
-                      , obs.path = "FATE_Champsaur/DATA_OBS/"
-                      , releves.PFG = "releves.PFG.abundance.csv"
-                      , releves.sites = "releves.sites.shp"
-                      , hab.obs = "simplified.cesbio.map.grd"
-                      , validation.mask = "certain.habitat.100m.restricted.grd"
-                      , studied.habitat = NULL
-                      , list.strata.simulations = list.strata.simulations
-                      , doComposition = FALSE
-                      , doRichness = FALSE)
-                      
-## PFG composition validation --------------------------------------------------------------------------
+library(raster)
+library(sf)
+
+## Define a vector to choose habitats taken into account
+studied.habitat = data.frame(ID = c(6, 5, 7, 8), habitat = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland"))
+## Habitat & validation maps
+hab.observed = raster("FATE_Champsaur/DATA_OBS/simplified.cesbio.map.grd")
+validation.mask = raster("FATE_Champsaur/DATA_OBS/certain.habitat.100m.restricted.grd")
+simulation.map = raster("FATE_Champsaur/DATA/MASK/MASK_Champsaur.tif")
+hab.obs = projectRaster(from = hab.observed, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb")
+validation.mask = projectRaster(from = validation.mask, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb")
+## Observed data
+releves.sites = as.data.frame(st_read("FATE_Champsaur/DATA_OBS/releves.sites.shp"))
+releves.PFG = as.data.frame(read.csv("FATE_Champsaur/DATA_OBS/releves.PFG.abundance.csv"))
+releves.PFG = merge(releves.PFG, releves.sites[,c("site","geometry")], by = "site")
+coor = SpatialPoints(st_coordinates(releves.PFG$geometry), proj4string = crs(hab.observed))
+coor = spTransform(coor, CRSobj = crs(hab.obs))
+releves.PFG$geometry = NULL
+releves.PFG[,c("x","y")] = coor@coords
+colnames(releves.PFG) = c("site", "abund", "PFG", "strata", "x", "y")
+## List of FATE strata & correspondence with observed strata (if perStrata = TRUE, if not = NULL)
 list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7))
-list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5"))
+## List of PFG taken into account in a FATE simulation
+list.PFG = as.factor(c("C1", "C2", "C3", "C4", "H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5"))
+## Habitat, strata and PFG considered in PFG compo validation
 habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland")
 strata.considered_PFG.compo = c("S", "M", "B")
+PFG.considered_PFG.compo = as.factor(c("H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5"))
+
 POST_FATE.validation(name.simulation = "FATE_Champsaur"
-                      , sim.version = "SIMUL_V4.1"
-                      , year = 2000
-                      , perStrata = TRUE
-                      , doHabitat = FALSE
-                      , obs.path = "FATE_Champsaur/DATA_OBS/"
-                      , releves.PFG = "releves.PFG.abundance.csv"
-                      , releves.sites = "releves.sites.shp"
-                      , hab.obs = "simplified.cesbio.map.grd"
-                      , validation.mask = "certain.habitat.100m.restricted.grd"
-                      , studied.habitat = NULL
-                      , list.strata.simulations = list.strata.simulations
-                      , doComposition = TRUE
-                      , PFG.considered_PFG.compo = list.PFG
-                      , habitat.considered_PFG.compo = habitat.considered
-                      , strata.considered_PFG.compo = strata.considered_PFG.compo
-                      , doRichness = FALSE)
-                      
-## PFG richness validation -----------------------------------------------------------------------------
-list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5"))
-POST_FATE.validation(name.simulation = "FATE_Champsaur"
-                      , sim.version = "SIMUL_V4.1"
-                      , year = 2000
-                      , perStrata = TRUE
-                      , doHabitat = FALSE
-                      , doComposition = FALSE
-                      , doRichness = TRUE
-                      , list.PFG = list.PFG
-                      , exclude.PFG = NULL)
+                     , sim.version = c("SIMUL_V4.1", "SIMUL_V8.1")
+                     , year = 2000
+                     , perStrata = TRUE
+                     , opt.no_CPU = 1
+                     , doHabitat = TRUE
+                     , releves.PFG = releves.PFG
+                     , hab.obs = hab.obs
+                     , validation.mask = validation.mask
+                     , studied.habitat = studied.habitat
+                     , list.strata.simulations = list.strata.simulations
+                     , doComposition = TRUE
+                     , PFG.considered_PFG.compo = PFG.considered_PFG.compo
+                     , habitat.considered_PFG.compo = habitat.considered
+                     , strata.considered_PFG.compo = strata.considered_PFG.compo
+                     , doRichness = TRUE
+                     , list.PFG = list.PFG
+                     , exclude.PFG = NULL)
 
 
diff --git a/docs/reference/PRE_FATE.skeletonDirectory.html b/docs/reference/PRE_FATE.skeletonDirectory.html index 492b294..da8a783 100644 --- a/docs/reference/PRE_FATE.skeletonDirectory.html +++ b/docs/reference/PRE_FATE.skeletonDirectory.html @@ -235,17 +235,14 @@

Details

VALIDATION

this folder will collect all the validation files produced - by POST_FATE.validation function

HABITAT
-

this folder will collect all the validation files produces - by the function POST_FATE.validation with habitat validation activated

+ by the POST_FATE.validation function

HABITAT
+

files containing outputs from habitat validation

PFG_RICHNESS
-

this folder will collect all the validation files produces - by the function POST_FATE.validation with PFG richness validation activated

+

files containing outputs from PFG richness validation

PFG_COMPOSITION
-

this folder will collect all the validation files produces - by the function POST_FATE.validation with PFG composition validation activated

+

files containing outputs from PFG composition validation

diff --git a/docs/reference/cluster.stats.html b/docs/reference/cluster.stats.html index 43e7e0f..a61d073 100644 --- a/docs/reference/cluster.stats.html +++ b/docs/reference/cluster.stats.html @@ -1,67 +1,12 @@ - - - - - - - -From fpc package 2.2-9 : cluster.stats function — cluster.stats • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -From fpc package 2.2-9 : cluster.stats function — cluster.stats • RFate - + + - - - -
-
- -
- -
+
@@ -204,136 +138,107 @@

From fpc package 2.2-9 : cluster.stats function

From fpc package 2.2-9 : cluster.stats function

-
cluster.stats(
-  d = NULL,
-  clustering,
-  alt.clustering = NULL,
-  noisecluster = FALSE,
-  silhouette = TRUE,
-  G2 = FALSE,
-  G3 = FALSE,
-  wgap = TRUE,
-  sepindex = TRUE,
-  sepprob = 0.1,
-  sepwithnoise = TRUE,
-  compareonly = FALSE,
-  aggregateonly = FALSE
-)
+
+
cluster.stats(
+  d = NULL,
+  clustering,
+  alt.clustering = NULL,
+  noisecluster = FALSE,
+  silhouette = TRUE,
+  G2 = FALSE,
+  G3 = FALSE,
+  wgap = TRUE,
+  sepindex = TRUE,
+  sepprob = 0.1,
+  sepwithnoise = TRUE,
+  compareonly = FALSE,
+  aggregateonly = FALSE
+)
+
-

Arguments

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
d

a distance object (as generated by dist) or a distance matrix -between cases.

clustering

an integer vector of length of the number of cases, +

+

Arguments

+
d
+

a distance object (as generated by dist) or a distance matrix +between cases.

+
clustering
+

an integer vector of length of the number of cases, which indicates a clustering. The clusters have to be numbered from 1 to -the number of clusters.

alt.clustering

an integer vector such as for clustering, indicating +the number of clusters.

+
alt.clustering
+

an integer vector such as for clustering, indicating an alternative clustering. If provided, the corrected Rand index and -Meila's VI for clustering vs. alt.clustering are computed.

noisecluster

logical. If TRUE, it is assumed that the largest +Meila's VI for clustering vs. alt.clustering are computed.

+
noisecluster
+

logical. If TRUE, it is assumed that the largest cluster number in clustering denotes a 'noise class', i.e. points that do not belong to any cluster. These points are not taken into account for the computation of all functions of within and between cluster distances -including the validation indexes.

silhouette

logical. If TRUE, the silhouette statistics are computed, -which requires package cluster.

G2

logical. If TRUE, Goodman and Kruskal's index G2 (cf. Gordon +including the validation indexes.

+
silhouette
+

logical. If TRUE, the silhouette statistics are computed, +which requires package cluster.

+
G2
+

logical. If TRUE, Goodman and Kruskal's index G2 (cf. Gordon (1999), p. 62) is computed. This executes lots of sorting algorithms and -can be very slow (it has been improved by R. Francois - thanks!)

G3

logical. If TRUE, the index G3 (cf. Gordon (1999), p. 62) is -computed. This executes sort on all distances and can be extremely slow.

wgap

logical. If TRUE, the widest within-cluster gaps (largest +can be very slow (it has been improved by R. Francois - thanks!)

+
G3
+

logical. If TRUE, the index G3 (cf. Gordon (1999), p. 62) is +computed. This executes sort on all distances and can be extremely slow.

+
wgap
+

logical. If TRUE, the widest within-cluster gaps (largest link in within-cluster minimum spanning tree) are computed. This is used -for finding a good number of clusters in Hennig (2013).

sepindex

logical. If TRUE, a separation index is computed, defined +for finding a good number of clusters in Hennig (2013).

+
sepindex
+

logical. If TRUE, a separation index is computed, defined based on the distances for every point to the closest point not in the same cluster. The separation index is then the mean of the smallest proportion sepprob of these. This allows to formalise separation less sensitive to a single or a few ambiguous points. The output component corresponding to this is sindex, not separation! This is used for finding -a good number of clusters in Hennig (2013).

sepprob

numerical between 0 and 1, see sepindex.

sepwithnoise

logical. If TRUE and sepindex and noisecluster are +a good number of clusters in Hennig (2013).

+
sepprob
+

numerical between 0 and 1, see sepindex.

+
sepwithnoise
+

logical. If TRUE and sepindex and noisecluster are both TRUE, the noise points are incorporated as cluster in the separation index (sepindex) computation. Also they are taken into account for the -computation for the minimum cluster separation.

compareonly

logical. If TRUE, only the corrected Rand index and +computation for the minimum cluster separation.

+
compareonly
+

logical. If TRUE, only the corrected Rand index and Meila's VI are computed and given out (this requires alt.clustering to -be specified).

aggregateonly

logical. If TRUE (and not compareonly), no +be specified).

+
aggregateonly
+

logical. If TRUE (and not compareonly), no clusterwise but only aggregated information is given out (this cuts -the size of the output down a bit).

- -

See also

- -

cluster.stats

+the size of the output down a bit).

+

+
+

See also

+

cluster.stats

+
+
- - - + + diff --git a/docs/reference/do.PFG.composition.validation.html b/docs/reference/do.PFG.composition.validation.html index ba4facb..63ca544 100644 --- a/docs/reference/do.PFG.composition.validation.html +++ b/docs/reference/do.PFG.composition.validation.html @@ -1,7 +1,7 @@ Compute distance between observed and simulated distribution — do.PFG.composition.validation • RFateCompare observed and simulated habitat of a FATE simulation -at the last simulation year. — do.habitat.validation • RFateCompute distance between observed and simulated distribution — do_PFG_composition_validation • RFate + + +
+
+ + + +
+
+ + +
+

This script is designed to compare the difference between the +PFG distribution in observed and simulated data. For a set of PFG, strata and +habitats chosen, the function computes distance between observed and simulated +distribution for a precise FATE simulation.

+
+ +
+
do_PFG_composition_validation(
+  sim,
+  PFG.considered_PFG.compo,
+  strata.considered_PFG.compo,
+  habitat.considered_PFG.compo,
+  observed.distribution,
+  simu_PFG,
+  habitat.whole.area.df
+)
+
+ +
+

Arguments

+
sim
+

name of the single simulation to validate.

+
PFG.considered_PFG.compo
+

a character vector of the list of PFG considered +in the validation.

+
strata.considered_PFG.compo
+

a character vector of the list of precise +strata considered in the validation.

+
habitat.considered_PFG.compo
+

a character vector of the list of habitat(s) +considered in the validation.

+
observed.distribution
+

PFG observed distribution table provides by get.observed.distribution function.

+
simu_PFG
+

a data frame with simulated abundance for each PFG and strata +(if option selected) and pixel ID, extracted from a FATE simulation (see POST_FATE.temporalEvolution).

+
habitat.whole.area.df
+

a data frame which contain habitat names and code for each pixel that needs validation.

+
+
+

Value

+ +
+
+

Details

+

Firstly, this code merges habitat.whole.area.df data frame with the simulated PFG abundance +simu_PFG data frame (with or without strata definition). +After filtration of the required PFG, strata and habitats, the function transforms +the data into relative metrics and, then, compute distribution per PFG, strata +and habitat (if necessary). Finally, the code computes proximity between observed +and simulated data, per PFG, strata and habitat.

+
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/docs/reference/do_habitat_validation.html b/docs/reference/do_habitat_validation.html new file mode 100644 index 0000000..66eab66 --- /dev/null +++ b/docs/reference/do_habitat_validation.html @@ -0,0 +1,227 @@ + +Compare observed and simulated habitat of a FATE simulation +to a chosen year. — do_habitat_validation • RFate + + +
+
+ + + +
+
+ + +
+

To compare observations and simulations, this function compute +confusion matrix between observation and prediction and then compute the TSS +for each habitat.

+
+ +
+
do_habitat_validation(
+  output.path,
+  RF.model,
+  predict.all.map,
+  sim,
+  simu_PFG,
+  habitat.whole.area.df,
+  list.strata,
+  perStrata
+)
+
+ +
+

Arguments

+
output.path
+

access path to the for the folder where output files +will be created.

+
RF.model
+

random forest model trained on CBNA data (train.RF.habitat +function)

+
predict.all.map
+

Logical. If TRUE, the script will predict +habitat for the whole map.

+
sim
+

name of the single simulation to validate.

+
simu_PFG
+

a data frame with simulated abundance for each PFG and strata +(if option selected) and pixel ID, extracted from a FATE simulation (see POST_FATE.temporalEvolution).

+
habitat.whole.area.df
+

a data frame which contain habitat names and code for each pixel that needs validation.

+
list.strata
+

If abundance file is defined by strata : a character vector which contain FATE +strata definition and correspondence with observed strata definition. +If abundance file is defined for all strata : a chracter vector with value "all".

+
perStrata
+

Logical. Default TRUE. If TRUE, PFG abundance is defined by strata. +If FALSE, PFG abundance defined for all strata.

+
+
+

Value

+

Habitat performance file.
If option selected, the function also returns an habitat prediction file with +observed and simulated habitat for each pixel of the whole map.

+
+
+

Details

+

For a given simulation sim, this script takes the evolution abundance for each PFG +and all strata (or for each PFG & each strata if option selected) data frame and predicts +the habitat for the whole map (if option selected) thanks to the RF model. +Finally, the function computes habitat performance based on TSS for each habitat.

+
+
+

Author

+

Matthieu Combaud & Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/docs/reference/get.observed.distribution.html b/docs/reference/get.observed.distribution.html index e318c82..6af2c8f 100644 --- a/docs/reference/get.observed.distribution.html +++ b/docs/reference/get.observed.distribution.html @@ -142,7 +142,6 @@

Compute distribution of relative abundance over observed relevés

get.observed.distribution(
-  name.simulation,
   releves.PFG,
   releves.sites,
   hab.obs,
@@ -150,16 +149,13 @@ 

Compute distribution of relative abundance over observed relevés

PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, - perStrata, - sim.version + perStrata )

Arguments

-
name.simulation
-

simulation folder name.

-
releves.PFG
+
releves.PFG

a data frame with abundance (column named abund) at each site and for each PFG and strata.

releves.sites
@@ -184,6 +180,8 @@

Arguments

considered in the validation.

perStrata

Logical. All strata together (FALSE) or per strata (TRUE).

+
name.simulation
+

simulation folder name.

sim.version

name of the simulation we want to validate (it works with only one sim.version).

diff --git a/docs/reference/get_observed_distribution.html b/docs/reference/get_observed_distribution.html new file mode 100644 index 0000000..7f78da3 --- /dev/null +++ b/docs/reference/get_observed_distribution.html @@ -0,0 +1,223 @@ + +Compute distribution of relative abundance over observed relevés — get_observed_distribution • RFate + + +
+
+ + + +
+
+ + +
+

This script is designed to compute distribution, per PFG/strata/habitat, +of relative abundance, from observed data.

+
+ +
+
get_observed_distribution(
+  releves.PFG,
+  hab.obs,
+  studied.habitat = NULL,
+  PFG.considered_PFG.compo,
+  strata.considered_PFG.compo,
+  habitat.considered_PFG.compo,
+  perStrata,
+  output.path
+)
+
+ +
+

Arguments

+
releves.PFG
+

a data frame with abundance (column named abund) at each site +and for each PFG and strata.

+
hab.obs
+

a raster map of the extended studied map in the simulation, with same projection +& resolution than simulation mask.

+
studied.habitat
+

default NULL. If NULL, the function will +take into account of habitats define in the hab.obs map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.

+
PFG.considered_PFG.compo
+

a character vector of the list of PFG considered +in the validation.

+
strata.considered_PFG.compo
+

a character vector of the list of precise +strata considered in the validation.

+
habitat.considered_PFG.compo
+

a character vector of the list of habitat(s) +considered in the validation.

+
perStrata
+

Logical. All strata together (FALSE) or per strata (TRUE).

+
name.simulation
+

simulation folder name.

+
+
+

Value

+ +
+
+

Details

+

The function takes the releves.PFG and releves.sites files and +aggregate coverage per PFG. Then, the code get habitat information from also +the hab.obs map, keep only interesting habitat, strata and PFG, and +transform the data into relative metrics. Finally, the script computes distribution +per PFG, and if require per strata/habitat (else all strata/habitat will be considered together).

+
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/docs/reference/index.html b/docs/reference/index.html index ed88d72..481b584 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -372,27 +372,27 @@

Tool box

train.RF.habitat()

+

train_RF_habitat()

Create a random forest algorithm trained on CBNA data.

-

do.habitat.validation()

+

do_habitat_validation()

Compare observed and simulated habitat of a FATE simulation -at the last simulation year.

+to a chosen year.

-

plot(<predicted.habitat>)

+

do_PFG_composition_validation()

-

Create a raster map of habitat prediction for a specific FATE -simulation at the last simulation year.

+

Compute distance between observed and simulated distribution

-

get.observed.distribution()

+

get_observed_distribution()

Compute distribution of relative abundance over observed relevés

-

do.PFG.composition.validation()

+

plot_predicted_habitat()

-

Compute distance between observed and simulated distribution

+

Create a raster map of habitat prediction for a specific FATE +simulation at the last simulation year.

-
train.RF.habitat(
+    
# S3 method for RF.habitat
+train(
   releves.PFG,
-  releves.sites,
   hab.obs,
   external.training.mask = NULL,
   studied.habitat = NULL,
@@ -161,10 +161,6 @@ 

Arguments

releves.PFG

a data frame with abundance (column named abund) at each site and for each PFG and strata.

-
releves.sites
-

a data frame with coordinates and a description of -the habitat associated with the dominant species of each site in the -studied map.

hab.obs

a raster map of the observed habitat in the extended studied area.

@@ -189,6 +185,10 @@

Arguments

by strata in each site. If FALSE, PFG abundance is defined for all strata.

sim.version

name of the simulation we want to validate.

+
releves.sites
+

a data frame with coordinates and a description of +the habitat associated with the dominant species of each site in the +studied map.

Value

diff --git a/docs/reference/train_RF_habitat.html b/docs/reference/train_RF_habitat.html new file mode 100644 index 0000000..b2ac244 --- /dev/null +++ b/docs/reference/train_RF_habitat.html @@ -0,0 +1,232 @@ + +Create a random forest algorithm trained on CBNA data. — train_RF_habitat • RFate + + +
+
+ + + +
+
+ + +
+

This script is designed to produce a random forest model +trained on observed PFG abundance, sites releves and a map of observed +habitat.

+
+ +
+
train_RF_habitat(
+  releves.PFG,
+  hab.obs,
+  external.training.mask = NULL,
+  studied.habitat = NULL,
+  RF.param,
+  output.path,
+  perStrata,
+  sim.version
+)
+
+ +
+

Arguments

+
releves.PFG
+

a data frame with abundance (column named abund) at each site +and for each PFG and strata.

+
hab.obs
+

a raster map of the observed habitat in the +extended studied area.

+
external.training.mask
+

default NULL. (optional) Keep only +releves data in a specific area.

+
studied.habitat
+

default NULL. If NULL, the function will +take into account of habitats define in the hab.obs map. Otherwise, please specify +in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken +into account for the validation.

+
RF.param
+

a list of 2 parameters for random forest model : +share.training defines the size of the trainig part of the data base. +ntree is the number of trees build by the algorithm, it allows to reduce +the prediction error.

+
output.path
+

access path to the for the folder where output files +will be created.

+
perStrata
+

Logical. If TRUE, the PFG abundance is defined +by strata in each site. If FALSE, PFG abundance is defined for all strata.

+
sim.version
+

name of the simulation we want to validate.

+
+
+

Value

+

2 prepared observed releves files are created before the building of the random +forest model in a habitat validation folder. +5 more files are created at the end of the script to save the RF model and +the performance analyzes (confusion matrix and TSS) for the training and +testing parts.

+
+
+

Details

+

This function transform PFG abundance in relative abundance, +get habitat information from the releves map of from a vector previously defined, +keep releves on interesting habitat and then builds a random forest model. Finally, +the function analyzes the model performance with computation of confusion matrix and TSS between +the training and testing sample.

+
+
+

Author

+

Matthieu Combaud, Maxime Delprat

+
+ +
+ +
+ + +
+ + + + + + + + diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index 3415743..aaa7438 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -12,11 +12,10 @@ POST_FATE.validation( opt.no_CPU = 1, doHabitat = TRUE, releves.PFG, - releves.sites, hab.obs, validation.mask, - studied.habitat = NULL, - list.strata.simulations = NULL, + studied.habitat, + list.strata.simulations, doComposition = TRUE, PFG.considered_PFG.compo, habitat.considered_PFG.compo, @@ -29,7 +28,7 @@ POST_FATE.validation( \arguments{ \item{name.simulation}{simulation folder name.} -\item{sim.version}{name of the simulation to validate (it works with only one \code{sim.version}).} +\item{sim.version}{a character vector with the name(s) of the simulation(s) to validate.} \item{year}{year of simulation for validation.} @@ -45,22 +44,18 @@ if \code{FALSE}, habitat validation module is disabled.} \item{releves.PFG}{a data frame with abundance (column named abund) at each site and for each PFG and strata (habitat & PFG composition validation).} -\item{releves.sites}{a data frame with coordinates and a description of the habitat associated with -the dominant species of each site in the studied map (habitat & PFG composition validation).} - \item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection & resolution than simulation mask (habitat & PFG composition validation).} \item{validation.mask}{a raster mask that specified which pixels need validation, with same projection & resolution than simulation mask (habitat & PFG composition validation).} -\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will -take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation (habitat validation).} +\item{studied.habitat}{a 2 columns data frame which contains the habitats (2nd column) and the ID (1st column) +for each of them which will be taken into account for the validation (habitat & PFG composition validation).} -\item{list.strata.simulations}{default \code{NULL}. A character vector which contain \code{FATE} -strata definition and correspondence with observed strata definition.} +\item{list.strata.simulations}{If \code{perStrata} = \code{TRUE}, +a character vector which contain \code{FATE} strata definition and correspondence with observed strata definition. +If \code{perStrata} = \code{FALSE}, please specify \code{NULL} value.} \item{doComposition}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, if \code{FALSE}, PFG composition validation module is disabled.} @@ -100,20 +95,20 @@ for a \code{FATE} simulation and computes the difference between observed and si } \details{ \describe{ - \item{Habitat validation}{The observed habitat is derived from a map of the area, the simulated habitat -is derived from \code{FATE} simulated relative abundance, based on a random forest -algorithm trained on observed releves data (see \code{\link{train.RF.habitat}}) \cr + \item{Habitat validation}{The observed habitat is derived from a map of the area or, if defined, +from \code{studied.habitat}, the simulated habitat is derived from \code{FATE} simulated relative +abundance, based on a random forest algorithm trained on observed releves data (see \code{\link{train_RF_habitat}}) \cr To compare observations and simulations, the function computes confusion matrix between observations and predictions and then compute the TSS for each habitat h (number of prediction of habitat h/number of observation of habitat h + number of non-prediction of habitat h/number of non-observation of habitat h). The final metrics this script use is the mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed habitat distribution. The habitat validation also provides a visual comparison of observed and -simulated habitat on the whole studied area (see \code{\link{do.habitat.validation}} & - \code{\link{plot.predicted.habitat}}).} \cr - \item{PFG composition validation}{This code firstly run the \code{get.observed.distribution} +simulated habitat on the whole studied area (see \code{\link{do_habitat_validation}} & +\code{\link{plot_predicted_habitat}}).} \cr + \item{PFG composition validation}{This code firstly run the \code{get_observed_distribution} function in order to have a \code{obs.distri} file which contain the observed distribution -per PFG, strata and habitat. This file is also an argument for the \code{do.PFG.composition.validation} +per PFG, strata and habitat. This file is also an argument for the \code{do_PFG_composition_validation} function run next. This second sub function provides the computation of distance between observed and simulated distribution.} \item{PFG richness validation}{Firstly, the function updates the \code{list.PFG} with \code{exclude.PFG} vector. @@ -126,57 +121,53 @@ simulation at a specific year.} } \examples{ -## Habitat validation --------------------------------------------------------------------------------- -list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) -POST_FATE.validation(name.simulation = "FATE_Champsaur" - , sim.version = "SIMUL_V4.1" - , year = 2000 - , perStrata = TRUE - , doHabitat = TRUE - , obs.path = "FATE_Champsaur/DATA_OBS/" - , releves.PFG = "releves.PFG.abundance.csv" - , releves.sites = "releves.sites.shp" - , hab.obs = "simplified.cesbio.map.grd" - , validation.mask = "certain.habitat.100m.restricted.grd" - , studied.habitat = NULL - , list.strata.simulations = list.strata.simulations - , doComposition = FALSE - , doRichness = FALSE) - -## PFG composition validation -------------------------------------------------------------------------- +library(raster) +library(sf) + +## Define a vector to choose habitats taken into account +studied.habitat = data.frame(ID = c(6, 5, 7, 8), habitat = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland")) +## Habitat & validation maps +hab.observed = raster("FATE_Champsaur/DATA_OBS/simplified.cesbio.map.grd") +validation.mask = raster("FATE_Champsaur/DATA_OBS/certain.habitat.100m.restricted.grd") +simulation.map = raster("FATE_Champsaur/DATA/MASK/MASK_Champsaur.tif") +hab.obs = projectRaster(from = hab.observed, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") +validation.mask = projectRaster(from = validation.mask, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") +## Observed data +releves.sites = as.data.frame(st_read("FATE_Champsaur/DATA_OBS/releves.sites.shp")) +releves.PFG = as.data.frame(read.csv("FATE_Champsaur/DATA_OBS/releves.PFG.abundance.csv")) +releves.PFG = merge(releves.PFG, releves.sites[,c("site","geometry")], by = "site") +coor = SpatialPoints(st_coordinates(releves.PFG$geometry), proj4string = crs(hab.observed)) +coor = spTransform(coor, CRSobj = crs(hab.obs)) +releves.PFG$geometry = NULL +releves.PFG[,c("x","y")] = coor@coords +colnames(releves.PFG) = c("site", "abund", "PFG", "strata", "x", "y") +## List of FATE strata & correspondence with observed strata (if perStrata = TRUE, if not = NULL) list.strata.simulations = list(S = c(1,2,3), M = c(4), B = c(5,6,7)) -list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) +## List of PFG taken into account in a FATE simulation +list.PFG = as.factor(c("C1", "C2", "C3", "C4", "H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5")) +## Habitat, strata and PFG considered in PFG compo validation habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") strata.considered_PFG.compo = c("S", "M", "B") +PFG.considered_PFG.compo = as.factor(c("H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5")) + POST_FATE.validation(name.simulation = "FATE_Champsaur" - , sim.version = "SIMUL_V4.1" - , year = 2000 - , perStrata = TRUE - , doHabitat = FALSE - , obs.path = "FATE_Champsaur/DATA_OBS/" - , releves.PFG = "releves.PFG.abundance.csv" - , releves.sites = "releves.sites.shp" - , hab.obs = "simplified.cesbio.map.grd" - , validation.mask = "certain.habitat.100m.restricted.grd" - , studied.habitat = NULL - , list.strata.simulations = list.strata.simulations - , doComposition = TRUE - , PFG.considered_PFG.compo = list.PFG - , habitat.considered_PFG.compo = habitat.considered - , strata.considered_PFG.compo = strata.considered_PFG.compo - , doRichness = FALSE) - -## PFG richness validation ----------------------------------------------------------------------------- -list.PFG = as.factor(c("C1","C2","C3","C4","H1","H2","H3","H4","H5","H6","P1","P2","P3","P4","P5")) -POST_FATE.validation(name.simulation = "FATE_Champsaur" - , sim.version = "SIMUL_V4.1" - , year = 2000 - , perStrata = TRUE - , doHabitat = FALSE - , doComposition = FALSE - , doRichness = TRUE - , list.PFG = list.PFG - , exclude.PFG = NULL) + , sim.version = c("SIMUL_V4.1", "SIMUL_V8.1") + , year = 2000 + , perStrata = TRUE + , opt.no_CPU = 1 + , doHabitat = TRUE + , releves.PFG = releves.PFG + , hab.obs = hab.obs + , validation.mask = validation.mask + , studied.habitat = studied.habitat + , list.strata.simulations = list.strata.simulations + , doComposition = TRUE + , PFG.considered_PFG.compo = PFG.considered_PFG.compo + , habitat.considered_PFG.compo = habitat.considered + , strata.considered_PFG.compo = strata.considered_PFG.compo + , doRichness = TRUE + , list.PFG = list.PFG + , exclude.PFG = NULL) } \author{ diff --git a/man/PRE_FATE.skeletonDirectory.Rd b/man/PRE_FATE.skeletonDirectory.Rd index 1e35d48..214f93f 100644 --- a/man/PRE_FATE.skeletonDirectory.Rd +++ b/man/PRE_FATE.skeletonDirectory.Rd @@ -77,14 +77,11 @@ The tree structure is detailed below : \item{\code{RESULTS}}{this folder will collect all the results produced by the software with a folder for each simulation} \item{\code{VALIDATION}}{this folder will collect all the validation files produced - by POST_FATE.validation function + by the \code{\link{POST_FATE.validation}} function \describe{ - \item{\code{HABITAT}}{this folder will collect all the validation files produces - by the function POST_FATE.validation with habitat validation activated} - \item{\code{PFG_RICHNESS}}{this folder will collect all the validation files produces - by the function POST_FATE.validation with PFG richness validation activated} - \item{\code{PFG_COMPOSITION}}{this folder will collect all the validation files produces - by the function POST_FATE.validation with PFG composition validation activated} + \item{\code{HABITAT}}{files containing outputs from habitat validation} + \item{\code{PFG_RICHNESS}}{files containing outputs from PFG richness validation} + \item{\code{PFG_COMPOSITION}}{files containing outputs from PFG composition validation} } } } diff --git a/man/do.PFG.composition.validation.Rd b/man/do.PFG.composition.validation.Rd deleted file mode 100644 index 46dcc15..0000000 --- a/man/do.PFG.composition.validation.Rd +++ /dev/null @@ -1,85 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/UTILS.do_PFG_composition_validation.R -\name{do.PFG.composition.validation} -\alias{do.PFG.composition.validation} -\title{Compute distance between observed and simulated distribution} -\usage{ -do.PFG.composition.validation( - name.simulation, - sim.version, - hab.obs, - PFG.considered_PFG.compo, - strata.considered_PFG.compo, - habitat.considered_PFG.compo, - observed.distribution, - perStrata, - validation.mask, - year, - list.strata.simulations, - list.strata.releves, - habitat.FATE.map, - studied.habitat -) -} -\arguments{ -\item{name.simulation}{simulation folder name.} - -\item{sim.version}{name of the simulation we want to validate (it works with -only one \code{sim.version}).} - -\item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection -& resolution than simulation mask.} - -\item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered -in the validation.} - -\item{strata.considered_PFG.compo}{a character vector of the list of precise -strata considered in the validation.} - -\item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) -considered in the validation.} - -\item{observed.distribution}{PFG observed distribution table provides by \code{get.observed.distribution} function.} - -\item{validation.mask}{a raster mask that specified -which pixels need validation, with same projection & resolution than simulation mask.} - -\item{year}{year of simulation to validate.} - -\item{list.strata.simulations}{a character vector which contain \code{FATE} -strata definition and correspondence with observed strata definition.} - -\item{list.strata.releves}{a character vector which contain the observed strata -definition, extracted from observed PFG releves.} - -\item{habitat.FATE.map}{a raster map of the observed habitat in the -studied area with same projection & resolution than validation mask and simulation mask.} - -\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will -take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation.} - -\item{perStrata.compo}{\code{Logical}. All strata together (FALSE) or per strata (TRUE).} -} -\value{ - -} -\description{ -This script is designed to compare the difference between the -PFG distribution in observed and simulated data. For a set of PFG, strata and -habitats chosen, the function compute distance between observed and simulated -distribution for a precise \code{FATE} simulation. -} -\details{ -After preliminary checks, this code extract observed habitat from the \code{hab.obs} -map and, then, merge it with the simulated PFG abundance file (with or without strata definition) -from results of the \code{FATE} simulation selected with \code{sim.version}. -After filtration of the required PFG, strata and habitats, the function transforms -the data into relative metrics and, then, compute distribution per PFG, strata -and habitat (if necessary). Finally, the code computes proximity between observed -and simulated data, per PFG, strata and habitat. -} -\author{ -Matthieu Combaud, Maxime Delprat -} diff --git a/man/do.habitat.validation.Rd b/man/do.habitat.validation.Rd deleted file mode 100644 index a3c82e5..0000000 --- a/man/do.habitat.validation.Rd +++ /dev/null @@ -1,91 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/UTILS.do_habitat_validation.R -\name{do.habitat.validation} -\alias{do.habitat.validation} -\title{Compare observed and simulated habitat of a \code{FATE} simulation -at the last simulation year.} -\usage{ -do.habitat.validation( - output.path, - RF.model, - habitat.FATE.map, - validation.mask, - simulation.map, - predict.all.map, - sim.version, - name.simulation, - perStrata, - hab.obs, - year, - list.strata.releves, - list.strata.simulations, - opt.no_CPU = 1, - studied.habitat = NULL -) -} -\arguments{ -\item{output.path}{access path to the for the folder where output files -will be created.} - -\item{RF.model}{random forest model trained on CBNA data (train.RF.habitat -function)} - -\item{habitat.FATE.map}{a raster map of the observed habitat in the -studied area with same projection & resolution than validation mask and simulation mask.} - -\item{validation.mask}{a raster mask that specified -which pixels need validation, with same projection & resolution than simulation mask.} - -\item{simulation.map}{a raster map of the whole studied area (provides by FATE parameters functions).} - -\item{predict.all.map}{\code{Logical}. If TRUE, the script will predict -habitat for the whole map.} - -\item{sim.version}{name of the simulation to validate.} - -\item{name.simulation}{simulation folder name.} - -\item{perStrata}{\code{Logical}. If TRUE, the PFG abundance is defined -by strata in each pixel. If FALSE, PFG abundance is defined for all strata.} - -\item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection -& resolution than simulation mask.} - -\item{year}{simulation year selected for validation.} - -\item{list.strata.releves}{a character vector which contain the observed strata -definition, extracted from observed PFG releves.} - -\item{list.strata.simulations}{a character vector which contain \code{FATE} -strata definition and correspondence with observed strata definition.} - -\item{opt.no_CPU}{default \code{1}. \cr The number of -resources that can be used to parallelize the computation of performance of -habitat prediction.} - -\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will -take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation.} -} -\value{ -Habitat performance file. \cr -If option selected, the function also returns an habitat prediction file with -observed and simulated habitat for each pixel of the whole map. -} -\description{ -To compare observations and simulations, this function compute -confusion matrix between observation and prediction and then compute the TSS -for each habitat. -} -\details{ -After several preliminary checks, the function is going to prepare the observations -database by extracting the observed habitat from a raster map. Then, for the -simulation \code{sim.version}, the script take the evolution abundance for each PFG -and all strata (or for each PFG & each strata if option selected) file and predict -the habitat for the whole map (if option selected) thanks to the RF model. -Finally, the function computes habitat performance based on TSS for each habitat. -} -\author{ -Matthieu Combaud & Maxime Delprat -} diff --git a/man/do_PFG_composition_validation.Rd b/man/do_PFG_composition_validation.Rd new file mode 100644 index 0000000..4a51258 --- /dev/null +++ b/man/do_PFG_composition_validation.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/UTILS.do_PFG_composition_validation.R +\name{do_PFG_composition_validation} +\alias{do_PFG_composition_validation} +\title{Compute distance between observed and simulated distribution} +\usage{ +do_PFG_composition_validation( + sim, + PFG.considered_PFG.compo, + strata.considered_PFG.compo, + habitat.considered_PFG.compo, + observed.distribution, + simu_PFG, + habitat.whole.area.df +) +} +\arguments{ +\item{sim}{name of the single simulation to validate.} + +\item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered +in the validation.} + +\item{strata.considered_PFG.compo}{a character vector of the list of precise +strata considered in the validation.} + +\item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) +considered in the validation.} + +\item{observed.distribution}{PFG observed distribution table provides by \code{get.observed.distribution} function.} + +\item{simu_PFG}{a \code{data frame} with simulated abundance for each PFG and strata +(if option selected) and pixel ID, extracted from a \code{FATE} simulation (see \code{\link{POST_FATE.temporalEvolution}}).} + +\item{habitat.whole.area.df}{a \code{data frame} which contain habitat names and code for each pixel that needs validation.} +} +\value{ + +} +\description{ +This script is designed to compare the difference between the +PFG distribution in observed and simulated data. For a set of PFG, strata and +habitats chosen, the function computes distance between observed and simulated +distribution for a precise \code{FATE} simulation. +} +\details{ +Firstly, this code merges \code{habitat.whole.area.df} data frame with the simulated PFG abundance +\code{simu_PFG} data frame (with or without strata definition). +After filtration of the required PFG, strata and habitats, the function transforms +the data into relative metrics and, then, compute distribution per PFG, strata +and habitat (if necessary). Finally, the code computes proximity between observed +and simulated data, per PFG, strata and habitat. +} +\author{ +Matthieu Combaud, Maxime Delprat +} diff --git a/man/do_habitat_validation.Rd b/man/do_habitat_validation.Rd new file mode 100644 index 0000000..b07c9e6 --- /dev/null +++ b/man/do_habitat_validation.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/UTILS.do_habitat_validation.R +\name{do_habitat_validation} +\alias{do_habitat_validation} +\title{Compare observed and simulated habitat of a \code{FATE} simulation +to a chosen year.} +\usage{ +do_habitat_validation( + output.path, + RF.model, + predict.all.map, + sim, + simu_PFG, + habitat.whole.area.df, + list.strata, + perStrata +) +} +\arguments{ +\item{output.path}{access path to the for the folder where output files +will be created.} + +\item{RF.model}{random forest model trained on CBNA data (train.RF.habitat +function)} + +\item{predict.all.map}{\code{Logical}. If TRUE, the script will predict +habitat for the whole map.} + +\item{sim}{name of the single simulation to validate.} + +\item{simu_PFG}{a \code{data frame} with simulated abundance for each PFG and strata +(if option selected) and pixel ID, extracted from a \code{FATE} simulation (see \code{\link{POST_FATE.temporalEvolution}}).} + +\item{habitat.whole.area.df}{a \code{data frame} which contain habitat names and code for each pixel that needs validation.} + +\item{list.strata}{If abundance file is defined by strata : a character vector which contain \code{FATE} +strata definition and correspondence with observed strata definition. +If abundance file is defined for all strata : a chracter vector with value "all".} + +\item{perStrata}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. +If \code{FALSE}, PFG abundance defined for all strata.} +} +\value{ +Habitat performance file. \cr +If option selected, the function also returns an habitat prediction file with +observed and simulated habitat for each pixel of the whole map. +} +\description{ +To compare observations and simulations, this function compute +confusion matrix between observation and prediction and then compute the TSS +for each habitat. +} +\details{ +For a given simulation \code{sim}, this script takes the evolution abundance for each PFG +and all strata (or for each PFG & each strata if option selected) data frame and predicts +the habitat for the whole map (if option selected) thanks to the RF model. +Finally, the function computes habitat performance based on TSS for each habitat. +} +\author{ +Matthieu Combaud & Maxime Delprat +} diff --git a/man/get.observed.distribution.Rd b/man/get_observed_distribution.Rd similarity index 83% rename from man/get.observed.distribution.Rd rename to man/get_observed_distribution.Rd index a12ac12..500e696 100644 --- a/man/get.observed.distribution.Rd +++ b/man/get_observed_distribution.Rd @@ -1,31 +1,24 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/UTILS.get_observed_distribution.R -\name{get.observed.distribution} -\alias{get.observed.distribution} +\name{get_observed_distribution} +\alias{get_observed_distribution} \title{Compute distribution of relative abundance over observed relevés} \usage{ -get.observed.distribution( - name.simulation, +get_observed_distribution( releves.PFG, - releves.sites, hab.obs, studied.habitat = NULL, PFG.considered_PFG.compo, strata.considered_PFG.compo, habitat.considered_PFG.compo, perStrata, - sim.version + output.path ) } \arguments{ -\item{name.simulation}{simulation folder name.} - \item{releves.PFG}{a data frame with abundance (column named abund) at each site and for each PFG and strata.} -\item{releves.sites}{a data frame with coordinates and a description of the habitat associated with -the dominant species of each site in the studied map.} - \item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection & resolution than simulation mask.} @@ -45,8 +38,7 @@ considered in the validation.} \item{perStrata}{\code{Logical}. All strata together (FALSE) or per strata (TRUE).} -\item{sim.version}{name of the simulation we want to validate (it works with -only one \code{sim.version}).} +\item{name.simulation}{simulation folder name.} } \value{ diff --git a/man/plot.predicted.habitat.Rd b/man/plot_predicted_habitat.Rd similarity index 89% rename from man/plot.predicted.habitat.Rd rename to man/plot_predicted_habitat.Rd index 14f4615..d7f4fee 100644 --- a/man/plot.predicted.habitat.Rd +++ b/man/plot_predicted_habitat.Rd @@ -1,11 +1,17 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/UTILS.plot_predicted_habitat.R -\name{plot.predicted.habitat} -\alias{plot.predicted.habitat} +\name{plot_predicted_habitat} +\alias{plot_predicted_habitat} \title{Create a raster map of habitat prediction for a specific \code{FATE} simulation at the last simulation year.} \usage{ -\method{plot}{predicted.habitat}(predicted.habitat, col.df, simulation.map, output.path, sim.version) +plot_predicted_habitat( + predicted.habitat, + col.df, + simulation.map, + output.path, + sim.version +) } \arguments{ \item{predicted.habitat}{a csv file created by the do.habitat.validation function diff --git a/man/train.RF.habitat.Rd b/man/train_RF_habitat.Rd similarity index 90% rename from man/train.RF.habitat.Rd rename to man/train_RF_habitat.Rd index 97ca461..304ca17 100644 --- a/man/train.RF.habitat.Rd +++ b/man/train_RF_habitat.Rd @@ -1,12 +1,11 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/UTILS.train_RF_habitat.R -\name{train.RF.habitat} -\alias{train.RF.habitat} +\name{train_RF_habitat} +\alias{train_RF_habitat} \title{Create a random forest algorithm trained on CBNA data.} \usage{ -train.RF.habitat( +train_RF_habitat( releves.PFG, - releves.sites, hab.obs, external.training.mask = NULL, studied.habitat = NULL, @@ -20,10 +19,6 @@ train.RF.habitat( \item{releves.PFG}{a data frame with abundance (column named abund) at each site and for each PFG and strata.} -\item{releves.sites}{a data frame with coordinates and a description of -the habitat associated with the dominant species of each site in the -studied map.} - \item{hab.obs}{a raster map of the observed habitat in the extended studied area.} diff --git a/src/libs/iostreams/src/zlib.o b/src/libs/iostreams/src/zlib.o index 00d4b325c1a6284d54ca842fb77f2b9fd157cd73..b882b7784ab33a1f4ecfdc0e9c0bcf5b710e54ca 100644 GIT binary patch delta 50 zcmV-20L}l%_72GQ4uG@)Ldgj(F)1K9E;KGMhfB!;w@b+a9q<7xlQG8^mw>qgAOTFb Ip}7O60rCP8!~g&Q delta 50 zcmV-20L}l%_72GQ4uG@)Ldgj!AUQ5HE-(N9hfB!;w@b+a9q<7vlQG8^mw>qgAOT9Z Ip}7O60p@EGVE_OC From d4f4afd94cb598bb6e9c2c71b0c418a71ceed5bc Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:27:07 +0200 Subject: [PATCH 079/176] Delete do.habitat.validation.html delete old files --- docs/do.habitat.validation.html | 239 -------------------------------- 1 file changed, 239 deletions(-) delete mode 100644 docs/do.habitat.validation.html diff --git a/docs/do.habitat.validation.html b/docs/do.habitat.validation.html deleted file mode 100644 index b33aa00..0000000 --- a/docs/do.habitat.validation.html +++ /dev/null @@ -1,239 +0,0 @@ - -Compare observed and simulated habitat of a FATE simulation -at the last simulation year. — do.habitat.validation • RFate - - -
-
- - - -
-
- - -
-

To compare observations and simulations, this function compute -confusion matrix between observation and prediction and then compute the TSS -for each habitat.

-
- -
-
do.habitat.validation(
-  output.path,
-  RF.model,
-  habitat.FATE.map,
-  validation.mask,
-  simulation.map,
-  predict.all.map,
-  sim.version,
-  name.simulation,
-  perStrata,
-  hab.obs,
-  year
-)
-
- -
-

Arguments

-
output.path
-

access path to the for the folder where output files -will be created.

-
RF.model
-

random forest model trained on CBNA data (train.RF.habitat -function)

-
habitat.FATE.map
-

a raster map of the observed habitat in the -studied area.

-
validation.mask
-

a raster mask that specified which pixels need validation.

-
simulation.map
-

a raster map of the whole studied area use to check -the consistency between simulation map and the observed habitat map.

-
predict.all.map
-

a TRUE/FALSE vector. If TRUE, the script will predict -habitat for the whole map.

-
sim.version
-

name of the simulation to validate.

-
name.simulation
-

simulation folder name.

-
perStrata
-

a TRUE/FALSE vector. If TRUE, the PFG abundance is defined -by strata in each pixel. If FALSE, PFG abundance is defined for all strata.

-
hab.obs
-

a raster map of the observed habitat in the -extended studied area.

-
year
-

year of simulation for validation.

-
-
-

Value

-

Habitat performance file -If option selected, the function returns an habitat prediction file with -observed and simulated habitat for each pixel of the whole map.

-
-
-

Details

-

After several preliminary checks, the function is going to prepare the observations -database by extracting the observed habitat from a raster map. Then, for each -simulations (sim.version), the script take the evolution abundance for each PFG -and all strata file and predict the habitat for the whole map (if option selected) -thanks to the RF model. Finally, the function computes habitat performance based on -TSS for each habitat.

-
-
-

Author

-

Matthieu Combaud & Maxime Delprat

-
- -
- -
- - -
- - - - - - - - From 8144c794d862df5cf21fcbd62c62bb9587049320 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:27:37 +0200 Subject: [PATCH 080/176] Delete do.PFG.composition.validation.html delete old file --- .../do.PFG.composition.validation.html | 222 ------------------ 1 file changed, 222 deletions(-) delete mode 100644 docs/reference/do.PFG.composition.validation.html diff --git a/docs/reference/do.PFG.composition.validation.html b/docs/reference/do.PFG.composition.validation.html deleted file mode 100644 index 63ca544..0000000 --- a/docs/reference/do.PFG.composition.validation.html +++ /dev/null @@ -1,222 +0,0 @@ - -Compute distance between observed and simulated distribution — do.PFG.composition.validation • RFate - - -
-
- - - -
-
- - -
-

This script is designed to compare the difference between the -PFG distribution in observed and simulated data. For a set of PFG, strata and -habitats chosen, the function computes distance between observed and simulated -distribution for a precise FATE simulation.

-
- -
-
# S3 method for PFG.composition.validation
-do(
-  sim,
-  PFG.considered_PFG.compo,
-  strata.considered_PFG.compo,
-  habitat.considered_PFG.compo,
-  observed.distribution,
-  simu_PFG,
-  habitat.whole.area.df
-)
-
- -
-

Arguments

-
sim
-

name of the single simulation to validate.

-
PFG.considered_PFG.compo
-

a character vector of the list of PFG considered -in the validation.

-
strata.considered_PFG.compo
-

a character vector of the list of precise -strata considered in the validation.

-
habitat.considered_PFG.compo
-

a character vector of the list of habitat(s) -considered in the validation.

-
observed.distribution
-

PFG observed distribution table provides by get.observed.distribution function.

-
simu_PFG
-

a data frame with simulated abundance for each PFG and strata -(if option selected) and pixel ID, extracted from a FATE simulation (see POST_FATE.temporalEvolution).

-
habitat.whole.area.df
-

a data frame which contain habitat names and code for each pixel that needs validation.

-
-
-

Value

- -
-
-

Details

-

Firstly, this code merges habitat.whole.area.df data frame with the simulated PFG abundance -simu_PFG data frame (with or without strata definition). -After filtration of the required PFG, strata and habitats, the function transforms -the data into relative metrics and, then, compute distribution per PFG, strata -and habitat (if necessary). Finally, the code computes proximity between observed -and simulated data, per PFG, strata and habitat.

-
-
-

Author

-

Matthieu Combaud, Maxime Delprat

-
- -
- -
- - -
- - - - - - - - From 36e7bc33874494d299462fe61be7a813605f41d8 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:27:50 +0200 Subject: [PATCH 081/176] Delete do.habitat.validation.html delete old file --- docs/reference/do.habitat.validation.html | 219 ---------------------- 1 file changed, 219 deletions(-) delete mode 100644 docs/reference/do.habitat.validation.html diff --git a/docs/reference/do.habitat.validation.html b/docs/reference/do.habitat.validation.html deleted file mode 100644 index f42d85e..0000000 --- a/docs/reference/do.habitat.validation.html +++ /dev/null @@ -1,219 +0,0 @@ - -Compare observed and simulated habitat of a FATE simulation -to a chosen year. — do.habitat.validation • RFate - - -
-
- - - -
-
- - -
-

To compare observations and simulations, this function compute -confusion matrix between observation and prediction and then compute the TSS -for each habitat.

-
- -
-
# S3 method for habitat.validation
-do(
-  output.path,
-  RF.model,
-  predict.all.map,
-  sim,
-  simu_PFG,
-  habitat.whole.area.df
-)
-
- -
-

Arguments

-
output.path
-

access path to the for the folder where output files -will be created.

-
RF.model
-

random forest model trained on CBNA data (train.RF.habitat -function)

-
predict.all.map
-

Logical. If TRUE, the script will predict -habitat for the whole map.

-
sim
-

name of the single simulation to validate.

-
simu_PFG
-

a data frame with simulated abundance for each PFG and strata -(if option selected) and pixel ID, extracted from a FATE simulation (see POST_FATE.temporalEvolution).

-
habitat.whole.area.df
-

a data frame which contain habitat names and code for each pixel that needs validation.

-
-
-

Value

-

Habitat performance file.
If option selected, the function also returns an habitat prediction file with -observed and simulated habitat for each pixel of the whole map.

-
-
-

Details

-

For a given simulation sim, this script takes the evolution abundance for each PFG -and all strata (or for each PFG & each strata if option selected) data frame and predicts -the habitat for the whole map (if option selected) thanks to the RF model. -Finally, the function computes habitat performance based on TSS for each habitat.

-
-
-

Author

-

Matthieu Combaud & Maxime Delprat

-
- -
- -
- - -
- - - - - - - - From 7d895010a279f6009c617aad9a5d428827750264 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:28:12 +0200 Subject: [PATCH 082/176] Delete get.observed.distribution.html delete old file --- docs/reference/get.observed.distribution.html | 229 ------------------ 1 file changed, 229 deletions(-) delete mode 100644 docs/reference/get.observed.distribution.html diff --git a/docs/reference/get.observed.distribution.html b/docs/reference/get.observed.distribution.html deleted file mode 100644 index 6af2c8f..0000000 --- a/docs/reference/get.observed.distribution.html +++ /dev/null @@ -1,229 +0,0 @@ - -Compute distribution of relative abundance over observed relevés — get.observed.distribution • RFate - - -
-
- - - -
-
- - -
-

This script is designed to compute distribution, per PFG/strata/habitat, -of relative abundance, from observed data.

-
- -
-
get.observed.distribution(
-  releves.PFG,
-  releves.sites,
-  hab.obs,
-  studied.habitat = NULL,
-  PFG.considered_PFG.compo,
-  strata.considered_PFG.compo,
-  habitat.considered_PFG.compo,
-  perStrata
-)
-
- -
-

Arguments

-
releves.PFG
-

a data frame with abundance (column named abund) at each site -and for each PFG and strata.

-
releves.sites
-

a data frame with coordinates and a description of the habitat associated with -the dominant species of each site in the studied map.

-
hab.obs
-

a raster map of the extended studied map in the simulation, with same projection -& resolution than simulation mask.

-
studied.habitat
-

default NULL. If NULL, the function will -take into account of habitats define in the hab.obs map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation.

-
PFG.considered_PFG.compo
-

a character vector of the list of PFG considered -in the validation.

-
strata.considered_PFG.compo
-

a character vector of the list of precise -strata considered in the validation.

-
habitat.considered_PFG.compo
-

a character vector of the list of habitat(s) -considered in the validation.

-
perStrata
-

Logical. All strata together (FALSE) or per strata (TRUE).

-
name.simulation
-

simulation folder name.

-
sim.version
-

name of the simulation we want to validate (it works with -only one sim.version).

-
-
-

Value

- -
-
-

Details

-

The function takes the releves.PFG and releves.sites files and -aggregate coverage per PFG. Then, the code get habitat information from also -the hab.obs map, keep only interesting habitat, strata and PFG, and -transform the data into relative metrics. Finally, the script computes distribution -per PFG, and if require per strata/habitat (else all strata/habitat will be considered together).

-
-
-

Author

-

Matthieu Combaud, Maxime Delprat

-
- -
- -
- - -
- - - - - - - - From 3320e502bd8279fcd2ef3305b14f0f0839369abf Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:28:32 +0200 Subject: [PATCH 083/176] Delete plot.predicted.habitat.html delete old file --- docs/reference/plot.predicted.habitat.html | 209 --------------------- 1 file changed, 209 deletions(-) delete mode 100644 docs/reference/plot.predicted.habitat.html diff --git a/docs/reference/plot.predicted.habitat.html b/docs/reference/plot.predicted.habitat.html deleted file mode 100644 index ee0045a..0000000 --- a/docs/reference/plot.predicted.habitat.html +++ /dev/null @@ -1,209 +0,0 @@ - -Create a raster map of habitat prediction for a specific FATE -simulation at the last simulation year. — plot.predicted.habitat • RFate - - -
-
- - - -
-
- - -
-

This script is designed to create a raster map of habitat prediction -based on a habitat prediction file. For each pixel, the habitat failure or success value -is associated to a color and then, the map is built.

-
- -
-
# S3 method for predicted.habitat
-plot(predicted.habitat, col.df, simulation.map, output.path, sim.version)
-
- -
-

Arguments

-
predicted.habitat
-

a csv file created by the do.habitat.validation function -which contain, for each pixel of the studied map, the simulated and observed habitat.

-
col.df
-

a data frame with all the colors associated with the failure or -success of each studied habitat prediction.

-
simulation.map
-

a raster map of the whole studied area.

-
output.path
-

access path to the for the folder where output files -will be created.

-
sim.version
-

name of the simulation we want to validate.

-
-
-

Value

-

a synthetic.prediction.png file which contain the final prediction plot.

-
-
-

Details

-

The function determines true/false prediction ('failure' if false, 'success' if true) -and prepare a dataframe containing color and habitat code. Then, the script merge -the prediction dataframe with the color and code habitat dataframe. Finally, -the function draw a raster map and a plot of prediction habitat over it thanks -to the data prepared before.

-
-
-

Author

-

Matthieu Combaud, Maxime Delprat

-
- -
- -
- - -
- - - - - - - - From 5f6d4b3fcc58429a46ae52964aa989faff5467bb Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:28:45 +0200 Subject: [PATCH 084/176] Delete train.RF.habitat.html delete old file --- docs/reference/train.RF.habitat.html | 237 --------------------------- 1 file changed, 237 deletions(-) delete mode 100644 docs/reference/train.RF.habitat.html diff --git a/docs/reference/train.RF.habitat.html b/docs/reference/train.RF.habitat.html deleted file mode 100644 index aa3a7fd..0000000 --- a/docs/reference/train.RF.habitat.html +++ /dev/null @@ -1,237 +0,0 @@ - -Create a random forest algorithm trained on CBNA data. — train.RF.habitat • RFate - - -
-
- - - -
-
- - -
-

This script is designed to produce a random forest model -trained on observed PFG abundance, sites releves and a map of observed -habitat.

-
- -
-
# S3 method for RF.habitat
-train(
-  releves.PFG,
-  hab.obs,
-  external.training.mask = NULL,
-  studied.habitat = NULL,
-  RF.param,
-  output.path,
-  perStrata,
-  sim.version
-)
-
- -
-

Arguments

-
releves.PFG
-

a data frame with abundance (column named abund) at each site -and for each PFG and strata.

-
hab.obs
-

a raster map of the observed habitat in the -extended studied area.

-
external.training.mask
-

default NULL. (optional) Keep only -releves data in a specific area.

-
studied.habitat
-

default NULL. If NULL, the function will -take into account of habitats define in the hab.obs map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation.

-
RF.param
-

a list of 2 parameters for random forest model : -share.training defines the size of the trainig part of the data base. -ntree is the number of trees build by the algorithm, it allows to reduce -the prediction error.

-
output.path
-

access path to the for the folder where output files -will be created.

-
perStrata
-

Logical. If TRUE, the PFG abundance is defined -by strata in each site. If FALSE, PFG abundance is defined for all strata.

-
sim.version
-

name of the simulation we want to validate.

-
releves.sites
-

a data frame with coordinates and a description of -the habitat associated with the dominant species of each site in the -studied map.

-
-
-

Value

-

2 prepared observed releves files are created before the building of the random -forest model in a habitat validation folder. -5 more files are created at the end of the script to save the RF model and -the performance analyzes (confusion matrix and TSS) for the training and -testing parts.

-
-
-

Details

-

This function transform PFG abundance in relative abundance, -get habitat information from the releves map of from a vector previously defined, -keep releves on interesting habitat and then builds a random forest model. Finally, -the function analyzes the model performance with computation of confusion matrix and TSS between -the training and testing sample.

-
-
-

Author

-

Matthieu Combaud, Maxime Delprat

-
- -
- -
- - -
- - - - - - - - From 5bb9966513495bd7e2559cc1faac6458cd2dd5b1 Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:31:17 +0200 Subject: [PATCH 085/176] Update _pkgdown.yml Correction with deletion of former functions --- _pkgdown.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index 618bd5e..ed474a0 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -150,6 +150,3 @@ reference: - "do_PFG_composition_validation" - "get_observed_distribution" - "plot_predicted_habitat" - - "plot.predicted.habitat" - - "get.observed.distribution" - - "do.PFG.composition.validation" From 54c4a71d4adb02377efc7c4493fa3d14cb28f91a Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Wed, 6 Apr 2022 14:38:07 +0200 Subject: [PATCH 086/176] Minor corrections in documentation of the function --- R/POST_FATE.validation.R | 26 ++++++++++++------------ docs/reference/POST_FATE.validation.html | 16 +++++++-------- man/POST_FATE.validation.Rd | 10 ++++----- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 95baf03..a97325e 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -7,13 +7,13 @@ ##' @author Matthieu Combaud, Maxime Delprat ##' ##' @description This script is designed to compute validation data for : -##' \code{Habitat} : compares habitat simulations and observations and +##' \cr \code{Habitat} : compares habitat simulations and observations and ##' create a map to visualize this comparison with all the \code{FATE} and ##' observed data. -##' \code{PFG Composition} : produced a computation of observed distribution +##' \cr \code{PFG Composition} : produced a computation of observed distribution ##' of relative abundance in the simulation area and a computation of distance between ##' observed and simulated distribution. -##' \code{PFG Richness} : computes the PFG richness over the whole simulation area +##' \cr \code{PFG Richness} : computes the PFG richness over the whole simulation area ##' for a \code{FATE} simulation and computes the difference between observed and simulated PFG richness. ##' ##' @param name.simulation simulation folder name. @@ -60,11 +60,11 @@ ##' \describe{ ##' \item{Habitat validation}{The observed habitat is derived from a map of the area or, if defined, ##' from \code{studied.habitat}, the simulated habitat is derived from \code{FATE} simulated relative -##' abundance, based on a random forest algorithm trained on observed releves data (see \code{\link{train_RF_habitat}}) \cr +##' abundance, based on a random forest algorithm trained on observed releves data (see \code{\link{train_RF_habitat}}). \cr ##' To compare observations and simulations, the function computes confusion matrix between ##' observations and predictions and then compute the TSS for each habitat h ##' (number of prediction of habitat h/number of observation of habitat h + number of non-prediction -##' of habitat h/number of non-observation of habitat h). The final metrics this script use is the +##' of habitat h/number of non-observation of habitat h). \cr The final metrics this script use is the ##' mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed ##' habitat distribution. The habitat validation also provides a visual comparison of observed and ##' simulated habitat on the whole studied area (see \code{\link{do_habitat_validation}} & @@ -237,6 +237,10 @@ POST_FATE.validation = function(name.simulation is.num = FALSE) # isolate the access path to the simulation mask for any FATE simulation simulation.map = raster(paste0(name)) + ####################### + # I. Preliminary checks + ####################### + # Check hab.obs map if(!compareCRS(simulation.map, hab.obs) | !all(res(hab.obs)==res(simulation.map))){ stop(paste0("Projection & resolution of hab.obs map does not match with simulation mask. Please reproject hab.obs map with projection & resolution of ", names(simulation.map))) @@ -263,7 +267,7 @@ POST_FATE.validation = function(name.simulation raster::origin(validation.mask) <- raster::origin(simulation.map) } - # Studied habitat + # Check studied habitat if(is.null(studied.habitat)){ stop("studied.habitat vector is null, please specify at least one habitat which will be taken into account in the validation") } else if(is.data.frame(studied.habitat)){ @@ -272,10 +276,6 @@ POST_FATE.validation = function(name.simulation stop("studied.habitat is not a data frame") } - ####################### - # I. Preliminary checks - ####################### - # check if strata definition used in the RF model is the same as the one used to analyze FATE output if (perStrata == TRUE) { if (all(intersect(names(list.strata.simulations), list.strata.releves) == names(list.strata.simulations))) { @@ -295,9 +295,9 @@ POST_FATE.validation = function(name.simulation stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") } - ####################################### - # I.2 Train a RF model on observed data - ####################################### + ################################################################# + # I.2 Train a RF model on observed data (habitat validation only) + ################################################################# if(doHabitat == TRUE){ diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index e18c9cd..d43b823 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -1,12 +1,12 @@ Computes validation data for habitat, PFG richness and composition for a FATE simulation. — POST_FATE.validation • RFateCompute distance between observed and simulated distribution — do_PFG_composition_validation • RFateCompute distance between observed and simulated distribution. — do_PFG_composition_validation • RFate @@ -142,7 +142,7 @@

Compare observed and simulated habitat of a FATE simulation

To compare observations and simulations, this function compute confusion matrix between observation and prediction and then compute the TSS -for each habitat.

+for each habitats.

@@ -164,7 +164,7 @@

Arguments

access path to the for the folder where output files will be created.

RF.model
-

random forest model trained on CBNA data (train.RF.habitat +

random forest model trained on observed abundance data (train.RF.habitat function)

predict.all.map

Logical. If TRUE, the script will predict @@ -175,11 +175,11 @@

Arguments

a data frame with simulated abundance for each PFG and strata (if option selected) and pixel ID, extracted from a FATE simulation (see POST_FATE.temporalEvolution).

habitat.whole.area.df
-

a data frame which contain habitat names and code for each pixel that needs validation.

+

a data frame which contain habitat names and code for each pixel that need validation.

list.strata
-

If abundance file is defined by strata : a character vector which contain FATE -strata definition and correspondence with observed strata definition. -If abundance file is defined for all strata : a chracter vector with value "all".

+

if abundance file is defined by strata : a character vector which contains FATE +strata definition and correspondence with observed strata definition.
+If abundance file is defined for all strata : a character vector with value "all".

perStrata

Logical. Default TRUE. If TRUE, PFG abundance is defined by strata. If FALSE, PFG abundance defined for all strata.

diff --git a/docs/reference/get_observed_distribution.html b/docs/reference/get_observed_distribution.html index 7f78da3..e798c79 100644 --- a/docs/reference/get_observed_distribution.html +++ b/docs/reference/get_observed_distribution.html @@ -1,5 +1,5 @@ -Compute distribution of relative abundance over observed relevés — get_observed_distribution • RFateCompute distribution of relative abundance over observed releves. — get_observed_distribution • RFateCreate a raster map of habitat prediction for a specific FATE -simulation at the last simulation year. — plot_predicted_habitat • RFateCreate a raster map of habitat prediction for a set of FATE +simulations at a specific simulation year. — plot_predicted_habitat • RFateCreate a random forest algorithm trained on CBNA data. — train_RF_habitat • RFateCreate a random forest algorithm trained on observed vegetation data — train_RF_habitat • RFate @@ -131,15 +130,14 @@

This script is designed to produce a random forest model -trained on observed PFG abundance, sites releves and a map of observed -habitat.

+trained on observed PFG abundance and a map of observed habitat.

@@ -158,46 +156,41 @@

Create a random forest algorithm trained on CBNA data.

Arguments

releves.PFG
-

a data frame with abundance (column named abund) at each site -and for each PFG and strata.

+

a data frame with PFG abundances (column named abund) at each site, +for each PFG and strata & coordinates of each site.

hab.obs
-

a raster map of the observed habitat in the -extended studied area.

+

a raster map of the observed habitat in the extended studied area.

external.training.mask
-

default NULL. (optional) Keep only -releves data in a specific area.

+

default NULL. (optional) a raster map for keeping +releves data only in a specific area.

studied.habitat
-

default NULL. If NULL, the function will -take into account of habitats define in the hab.obs map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation.

+

default NULL. a 2 columns data frame which contains +the habitats (2nd column) and the ID (1st column) for each of them which +will be taken into account for the validation.

RF.param
-

a list of 2 parameters for random forest model : -share.training defines the size of the trainig part of the data base. -ntree is the number of trees build by the algorithm, it allows to reduce -the prediction error.

+

a list of 2 parameters for random forest model :
+share.training defines the size of the training part of the data base.
+ntree is the number of trees build by the algorithm, it allows to reduce the prediction error.

output.path
-

access path to the for the folder where output files -will be created.

+

access path to the folder where output files will be created.

perStrata
-

Logical. If TRUE, the PFG abundance is defined -by strata in each site. If FALSE, PFG abundance is defined for all strata.

+

Logical. If TRUE, the PFG abundance is defined +by strata in each site. If FALSE, PFG abundance is defined for all strata.

sim.version
-

name of the simulation we want to validate.

+

a character vector with the name(s) of the simulation(s) to validate.

Value

2 prepared observed releves files are created before the building of the random -forest model in a habitat validation folder. -5 more files are created at the end of the script to save the RF model and +forest model in a folder previously defined.
5 more files are created at the end of the script to save the RF model and the performance analyzes (confusion matrix and TSS) for the training and testing parts.

Details

-

This function transform PFG abundance in relative abundance, -get habitat information from the releves map of from a vector previously defined, -keep releves on interesting habitat and then builds a random forest model. Finally, +

This function transforms PFG abundance in relative abundance, +gets habitat information from an habitat data frame previously defined and a habitat map, +keep releves on interesting habitat(s) and then builds a random forest model. Finally, the function analyzes the model performance with computation of confusion matrix and TSS between the training and testing sample.

diff --git a/man/do_PFG_composition_validation.Rd b/man/do_PFG_composition_validation.Rd index 4a51258..ab05a17 100644 --- a/man/do_PFG_composition_validation.Rd +++ b/man/do_PFG_composition_validation.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/UTILS.do_PFG_composition_validation.R \name{do_PFG_composition_validation} \alias{do_PFG_composition_validation} -\title{Compute distance between observed and simulated distribution} +\title{Compute distance between observed and simulated distribution.} \usage{ do_PFG_composition_validation( sim, @@ -26,7 +26,7 @@ strata considered in the validation.} \item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) considered in the validation.} -\item{observed.distribution}{PFG observed distribution table provides by \code{get.observed.distribution} function.} +\item{observed.distribution}{PFG observed distribution table provides by \code{\link{get.observed.distribution}} function.} \item{simu_PFG}{a \code{data frame} with simulated abundance for each PFG and strata (if option selected) and pixel ID, extracted from a \code{FATE} simulation (see \code{\link{POST_FATE.temporalEvolution}}).} @@ -45,7 +45,7 @@ distribution for a precise \code{FATE} simulation. \details{ Firstly, this code merges \code{habitat.whole.area.df} data frame with the simulated PFG abundance \code{simu_PFG} data frame (with or without strata definition). -After filtration of the required PFG, strata and habitats, the function transforms +After filtration of the required PFG, strata and habitat(s), the function transforms the data into relative metrics and, then, compute distribution per PFG, strata and habitat (if necessary). Finally, the code computes proximity between observed and simulated data, per PFG, strata and habitat. diff --git a/man/do_habitat_validation.Rd b/man/do_habitat_validation.Rd index b07c9e6..8167f36 100644 --- a/man/do_habitat_validation.Rd +++ b/man/do_habitat_validation.Rd @@ -20,7 +20,7 @@ do_habitat_validation( \item{output.path}{access path to the for the folder where output files will be created.} -\item{RF.model}{random forest model trained on CBNA data (train.RF.habitat +\item{RF.model}{random forest model trained on observed abundance data (\code{\link{train.RF.habitat}} function)} \item{predict.all.map}{\code{Logical}. If TRUE, the script will predict @@ -31,11 +31,11 @@ habitat for the whole map.} \item{simu_PFG}{a \code{data frame} with simulated abundance for each PFG and strata (if option selected) and pixel ID, extracted from a \code{FATE} simulation (see \code{\link{POST_FATE.temporalEvolution}}).} -\item{habitat.whole.area.df}{a \code{data frame} which contain habitat names and code for each pixel that needs validation.} +\item{habitat.whole.area.df}{a \code{data frame} which contain habitat names and code for each pixel that need validation.} -\item{list.strata}{If abundance file is defined by strata : a character vector which contain \code{FATE} -strata definition and correspondence with observed strata definition. -If abundance file is defined for all strata : a chracter vector with value "all".} +\item{list.strata}{if abundance file is defined by strata : a character vector which contains \code{FATE} +strata definition and correspondence with observed strata definition. \cr +If abundance file is defined for all strata : a character vector with value "all".} \item{perStrata}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. If \code{FALSE}, PFG abundance defined for all strata.} @@ -48,7 +48,7 @@ observed and simulated habitat for each pixel of the whole map. \description{ To compare observations and simulations, this function compute confusion matrix between observation and prediction and then compute the TSS -for each habitat. +for each habitats. } \details{ For a given simulation \code{sim}, this script takes the evolution abundance for each PFG diff --git a/man/get_observed_distribution.Rd b/man/get_observed_distribution.Rd index 500e696..ce3fcd6 100644 --- a/man/get_observed_distribution.Rd +++ b/man/get_observed_distribution.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/UTILS.get_observed_distribution.R \name{get_observed_distribution} \alias{get_observed_distribution} -\title{Compute distribution of relative abundance over observed relevés} +\title{Compute distribution of relative abundance over observed releves.} \usage{ get_observed_distribution( releves.PFG, @@ -16,24 +16,23 @@ get_observed_distribution( ) } \arguments{ -\item{releves.PFG}{a data frame with abundance (column named abund) at each site -and for each PFG and strata.} +\item{releves.PFG}{a data frame with PFG abundances (column named abund) at each site, +for each PFG and strata & coordinates of each site.} \item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection & resolution than simulation mask.} -\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will -take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation.} +\item{studied.habitat}{default \code{NULL}. a 2 columns data frame which contains +the habitats (2nd column) and the ID (1st column) for each of them which +will be taken into account for the validation.} -\item{PFG.considered_PFG.compo}{a character vector of the list of PFG considered +\item{PFG.considered_PFG.compo}{a character vector which contains the list of PFG considered in the validation.} -\item{strata.considered_PFG.compo}{a character vector of the list of precise +\item{strata.considered_PFG.compo}{a character vector which contains the list of precise strata considered in the validation.} -\item{habitat.considered_PFG.compo}{a character vector of the list of habitat(s) +\item{habitat.considered_PFG.compo}{a character vector which contains the list of habitat(s) considered in the validation.} \item{perStrata}{\code{Logical}. All strata together (FALSE) or per strata (TRUE).} @@ -48,11 +47,11 @@ This script is designed to compute distribution, per PFG/strata/habitat, of relative abundance, from observed data. } \details{ -The function takes the \code{releves.PFG} and \code{releves.sites} files and -aggregate coverage per PFG. Then, the code get habitat information from also -the \code{hab.obs} map, keep only interesting habitat, strata and PFG, and -transform the data into relative metrics. Finally, the script computes distribution -per PFG, and if require per strata/habitat (else all strata/habitat will be considered together). +The function takes the \code{releves.PFG} file and aggregate coverage per PFG. +Then, the code gets habitat information from the \code{hab.obs} map & the \code{studied.habitat} +data frame, keep only interesting habitat(s), strata and PFG, and transforms +the data into relative metrics. Finally, the script computes distribution per PFG +and if required per strata/habitat (else all strata/habitat will be considered together). } \author{ Matthieu Combaud, Maxime Delprat diff --git a/man/plot_predicted_habitat.Rd b/man/plot_predicted_habitat.Rd index d7f4fee..3427c77 100644 --- a/man/plot_predicted_habitat.Rd +++ b/man/plot_predicted_habitat.Rd @@ -2,8 +2,8 @@ % Please edit documentation in R/UTILS.plot_predicted_habitat.R \name{plot_predicted_habitat} \alias{plot_predicted_habitat} -\title{Create a raster map of habitat prediction for a specific \code{FATE} -simulation at the last simulation year.} +\title{Create a raster map of habitat prediction for a set of \code{FATE} +simulations at a specific simulation year.} \usage{ plot_predicted_habitat( predicted.habitat, @@ -14,8 +14,8 @@ plot_predicted_habitat( ) } \arguments{ -\item{predicted.habitat}{a csv file created by the do.habitat.validation function -which contain, for each pixel of the studied map, the simulated and observed habitat.} +\item{predicted.habitat}{a csv file created by the \code{\link{do.habitat.validation}} function +which contains, for each pixel of the studied map, the simulated and observed habitat.} \item{col.df}{a data frame with all the colors associated with the failure or success of each studied habitat prediction.} @@ -25,21 +25,21 @@ success of each studied habitat prediction.} \item{output.path}{access path to the for the folder where output files will be created.} -\item{sim.version}{name of the simulation we want to validate.} +\item{sim.version}{a character vector with the name(s) of the simulation(s) to validate.} } \value{ -a synthetic.prediction.png file which contain the final prediction plot. +a synthetic.prediction.png file which contain the final prediction map. } \description{ This script is designed to create a raster map of habitat prediction -based on a habitat prediction file. For each pixel, the habitat failure or success value +based on an habitat prediction file. For each pixel, the habitat failure or success value is associated to a color and then, the map is built. } \details{ The function determines true/false prediction ('failure' if false, 'success' if true) -and prepare a dataframe containing color and habitat code. Then, the script merge -the prediction dataframe with the color and code habitat dataframe. Finally, -the function draw a raster map and a plot of prediction habitat over it thanks +and prepare a data frame containing colors and habitats codes. Then, the script merge +the prediction data frame with the color and code habitat data frame. Finally, +the function draw a raster map and a plot of prediction habitat over it, thanks to the data prepared before. } \author{ diff --git a/man/train_RF_habitat.Rd b/man/train_RF_habitat.Rd index 304ca17..7698105 100644 --- a/man/train_RF_habitat.Rd +++ b/man/train_RF_habitat.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/UTILS.train_RF_habitat.R \name{train_RF_habitat} \alias{train_RF_habitat} -\title{Create a random forest algorithm trained on CBNA data.} +\title{Create a random forest algorithm trained on observed vegetation data} \usage{ train_RF_habitat( releves.PFG, @@ -16,49 +16,44 @@ train_RF_habitat( ) } \arguments{ -\item{releves.PFG}{a data frame with abundance (column named abund) at each site -and for each PFG and strata.} +\item{releves.PFG}{a data frame with PFG abundances (column named abund) at each site, +for each PFG and strata & coordinates of each site.} -\item{hab.obs}{a raster map of the observed habitat in the -extended studied area.} +\item{hab.obs}{a raster map of the observed habitat in the extended studied area.} -\item{external.training.mask}{default \code{NULL}. (optional) Keep only -releves data in a specific area.} +\item{external.training.mask}{default \code{NULL}. (optional) a raster map for keeping +releves data only in a specific area.} -\item{studied.habitat}{default \code{NULL}. If \code{NULL}, the function will -take into account of habitats define in the \code{hab.obs} map. Otherwise, please specify -in a 2 columns data frame the habitats (2nd column) and the ID (1st column) for each of them which will be taken -into account for the validation.} +\item{studied.habitat}{default \code{NULL}. a 2 columns data frame which contains +the habitats (2nd column) and the ID (1st column) for each of them which +will be taken into account for the validation.} -\item{RF.param}{a list of 2 parameters for random forest model : -share.training defines the size of the trainig part of the data base. -ntree is the number of trees build by the algorithm, it allows to reduce -the prediction error.} +\item{RF.param}{a list of 2 parameters for random forest model : \cr +share.training defines the size of the training part of the data base. \cr +ntree is the number of trees build by the algorithm, it allows to reduce the prediction error.} -\item{output.path}{access path to the for the folder where output files -will be created.} +\item{output.path}{access path to the folder where output files will be created.} -\item{perStrata}{\code{Logical}. If TRUE, the PFG abundance is defined -by strata in each site. If FALSE, PFG abundance is defined for all strata.} +\item{perStrata}{\code{Logical}. If \code{TRUE}, the PFG abundance is defined +by strata in each site. If \code{FALSE}, PFG abundance is defined for all strata.} -\item{sim.version}{name of the simulation we want to validate.} +\item{sim.version}{a character vector with the name(s) of the simulation(s) to validate.} } \value{ 2 prepared observed releves files are created before the building of the random -forest model in a habitat validation folder. +forest model in a folder previously defined. \cr 5 more files are created at the end of the script to save the RF model and the performance analyzes (confusion matrix and TSS) for the training and testing parts. } \description{ This script is designed to produce a random forest model -trained on observed PFG abundance, sites releves and a map of observed -habitat. +trained on observed PFG abundance and a map of observed habitat. } \details{ -This function transform PFG abundance in relative abundance, -get habitat information from the releves map of from a vector previously defined, -keep releves on interesting habitat and then builds a random forest model. Finally, +This function transforms PFG abundance in relative abundance, +gets habitat information from an habitat data frame previously defined and a habitat map, +keep releves on interesting habitat(s) and then builds a random forest model. Finally, the function analyzes the model performance with computation of confusion matrix and TSS between the training and testing sample. } From 95c8ec56c1660a9da628b14b7d0fd933132a016f Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 8 Apr 2022 09:53:45 +0200 Subject: [PATCH 089/176] Minor corrections of documentation --- R/POST_FATE.validation.R | 2 +- R/UTILS.train_RF_habitat.R | 2 +- docs/reference/POST_FATE.validation.html | 2 +- docs/reference/train_RF_habitat.html | 2 +- man/POST_FATE.validation.Rd | 2 +- man/train_RF_habitat.Rd | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index a97325e..9038a4b 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -27,7 +27,7 @@ ##' @param doHabitat \code{Logical}. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, ##' if \code{FALSE}, habitat validation module is disabled. ##' @param releves.PFG a data frame with abundance (column named abund) at each site -##' and for each PFG and strata (habitat & PFG composition validation). +##' and for each PFG and strata, if necessary, & sites coordinates (habitat & PFG composition validation). ##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection ##' & resolution than simulation mask (habitat & PFG composition validation). ##' @param validation.mask a raster mask that specified which pixels need validation, with same projection diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 19618dc..efe4b18 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -10,7 +10,7 @@ ##' trained on observed PFG abundance and a map of observed habitat. ##' ##' @param releves.PFG a data frame with PFG abundances (column named abund) at each site, -##' for each PFG and strata & coordinates of each site. +##' for each PFG and strata, if necessary, & coordinates of each site. ##' @param hab.obs a raster map of the observed habitat in the extended studied area. ##' @param external.training.mask default \code{NULL}. (optional) a raster map for keeping ##' releves data only in a specific area. diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index d43b823..96f9adc 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -196,7 +196,7 @@

Arguments

if FALSE, habitat validation module is disabled.

releves.PFG

a data frame with abundance (column named abund) at each site -and for each PFG and strata (habitat & PFG composition validation).

+and for each PFG and strata, if necessary, & sites coordinates (habitat & PFG composition validation).

hab.obs

a raster map of the extended studied map in the simulation, with same projection & resolution than simulation mask (habitat & PFG composition validation).

diff --git a/docs/reference/train_RF_habitat.html b/docs/reference/train_RF_habitat.html index 079c042..b58d455 100644 --- a/docs/reference/train_RF_habitat.html +++ b/docs/reference/train_RF_habitat.html @@ -157,7 +157,7 @@

Create a random forest algorithm trained on observed vegetation data

Arguments

releves.PFG

a data frame with PFG abundances (column named abund) at each site, -for each PFG and strata & coordinates of each site.

+for each PFG and strata, if necessary, & coordinates of each site.

hab.obs

a raster map of the observed habitat in the extended studied area.

external.training.mask
diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index 3730250..722e0e0 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -42,7 +42,7 @@ parallelize the computation of prediction performance for habitat & richness val if \code{FALSE}, habitat validation module is disabled.} \item{releves.PFG}{a data frame with abundance (column named abund) at each site -and for each PFG and strata (habitat & PFG composition validation).} +and for each PFG and strata, if necessary, & sites coordinates (habitat & PFG composition validation).} \item{hab.obs}{a raster map of the extended studied map in the simulation, with same projection & resolution than simulation mask (habitat & PFG composition validation).} diff --git a/man/train_RF_habitat.Rd b/man/train_RF_habitat.Rd index 7698105..2e8d7be 100644 --- a/man/train_RF_habitat.Rd +++ b/man/train_RF_habitat.Rd @@ -17,7 +17,7 @@ train_RF_habitat( } \arguments{ \item{releves.PFG}{a data frame with PFG abundances (column named abund) at each site, -for each PFG and strata & coordinates of each site.} +for each PFG and strata, if necessary, & coordinates of each site.} \item{hab.obs}{a raster map of the observed habitat in the extended studied area.} From a044571ec7346bca2a99992cf1c1c5a5ede85dd8 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 28 Apr 2022 09:24:40 +0200 Subject: [PATCH 090/176] minor corrections & deletion of those corrections --- R/UTILS.scaleMaps.R | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/R/UTILS.scaleMaps.R b/R/UTILS.scaleMaps.R index 8fe6301..845a324 100644 --- a/R/UTILS.scaleMaps.R +++ b/R/UTILS.scaleMaps.R @@ -85,9 +85,7 @@ NULL ras.new = projectRaster(from = ras , res = resolution , crs = old.proj - , method = proj.method - , filename = fi - , overwrite = TRUE) + , method = proj.method) message(paste0("The raster file ", fi, " has been successfully rescaled !")) } else { From d002b3fc001cfb90d5d5507aa9c3e66e5ba66d79 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 28 Apr 2022 09:27:17 +0200 Subject: [PATCH 091/176] Choice in validation mask (provide a map or using simulation mask) & deleting missing PFG if RF & FATE PFG are not the same --- R/POST_FATE.validation.R | 49 +++++++++++++++++++-------------- R/UTILS.do_habitat_validation.R | 12 ++++++-- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 9038a4b..83a6ded 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -177,7 +177,7 @@ POST_FATE.validation = function(name.simulation , doHabitat = TRUE , releves.PFG , hab.obs - , validation.mask + , validation.mask = NULL , studied.habitat , list.strata.simulations , doComposition = TRUE @@ -228,7 +228,6 @@ POST_FATE.validation = function(name.simulation # Habitat map hab.obs = hab.obs - validation.mask = validation.mask # Simulation mask name = .getParam(params.lines = paste0(name.simulation, "/PARAM_SIMUL/Simul_parameters_", str_split(sim.version, "_")[[1]][2], ".txt"), @@ -237,6 +236,11 @@ POST_FATE.validation = function(name.simulation is.num = FALSE) # isolate the access path to the simulation mask for any FATE simulation simulation.map = raster(paste0(name)) + # Validation mask (if provided) + if(!is.null(validation.mask)){ + validation.mask = validation.mask + } + ####################### # I. Preliminary checks ####################### @@ -255,16 +259,18 @@ POST_FATE.validation = function(name.simulation } # Check validation mask - if(!compareCRS(simulation.map, validation.mask) | !all(res(validation.mask)==res(simulation.map))){ - stop(paste0("Projection & resolution of validation mask does not match with simulation mask. Please reproject validation mask with projection & resolution of ", names(simulation.map))) - }else if(extent(validation.mask) != extent(simulation.map)){ - validation.mask = crop(validation.mask, simulation.map) - }else { - validation.mask = validation.mask - } - if(!all(origin(simulation.map) == origin(validation.mask))){ - cat("\n setting origin validation mask to match simulation.map \n") - raster::origin(validation.mask) <- raster::origin(simulation.map) + if(!is.null(validation.mask)){ + if(!compareCRS(simulation.map, validation.mask) | !all(res(validation.mask)==res(simulation.map))){ + stop(paste0("Projection & resolution of validation mask does not match with simulation mask. Please reproject validation mask with projection & resolution of ", names(simulation.map))) + }else if(extent(validation.mask) != extent(simulation.map)){ + validation.mask = crop(validation.mask, simulation.map) + }else { + validation.mask = vlidation.mask + } + if(!all(origin(simulation.map) == origin(validation.mask))){ + cat("\n setting origin validation mask to match simulation.map \n") + raster::origin(validation.mask) <- raster::origin(simulation.map) + } } # Check studied habitat @@ -290,11 +296,6 @@ POST_FATE.validation = function(name.simulation stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } - # initial consistency between habitat.FATE.map and validation.mask (do it before the adjustement of habitat.FATE.map) - if(!compareCRS(habitat.FATE.map,validation.mask) | !all(res(habitat.FATE.map) == res(validation.mask))){ - stop("please provide rasters with same crs and resolution for habitat.FATE.map and validation.mask") - } - ################################################################# # I.2 Train a RF model on observed data (habitat validation only) ################################################################# @@ -325,9 +326,15 @@ POST_FATE.validation = function(name.simulation ####################################### # habitat df for the whole simulation area - habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) - , code.habitat = getValues(habitat.FATE.map) - , for.validation = getValues(validation.mask)) + if(is.null(validation.mask)){ + habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) + , code.habitat = getValues(habitat.FATE.map) + , for.validation = getValues(simulation.map)) + }else { + habitat.whole.area.df <- data.frame(pixel = seq(1, ncell(habitat.FATE.map), 1) + , code.habitat = getValues(habitat.FATE.map) + , for.validation = getValues(validation.mask)) + } habitat.whole.area.df <- habitat.whole.area.df[which(getValues(simulation.map) == 1), ] # index of the pixels in the simulation area habitat.whole.area.df <- habitat.whole.area.df[which(!is.na(habitat.whole.area.df$for.validation)), ] if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2){ @@ -342,7 +349,7 @@ POST_FATE.validation = function(name.simulation cat("\n Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t") - cat("\n processing simulations \n") + cat("\n ----------- PROCESSING SIMULATIONS") if (opt.no_CPU > 1) { diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 7c2903c..00ca272 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -67,15 +67,21 @@ do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, s FATE.PFG <- .getGraphics_PFG(name.simulation = str_split(output.path, "/")[[1]][1] , abs.simulParam = paste0(str_split(output.path, "/")[[1]][1], "/PARAM_SIMUL/Simul_parameters_", str_split(sim, "_")[[1]][2], ".txt")) FATE.PFG = FATE.PFG$PFG - if(length(setdiff(FATE.PFG,RF.PFG)) > 0 | length(setdiff(RF.PFG,FATE.PFG)) > 0) { - stop("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE.") - } + if(length(setdiff(FATE.PFG,RF.PFG)) > 0) { + warning(paste0("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG", setdiff(FATE.PFG,RF.PFG), "will be deleted")) + FATE.PFG = RF.PFG + }else if(length(setdiff(RF.PFG,FATE.PFG)) > 0){ + warning(paste0("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG", setdiff(RF.PFG,FATE.PFG), "will be deleted")) + RF.PFG = FATE.PFG + } + ####################### # I. Data preparation ####################### #transform absolute abundance into relative abundance + simu_PFG = simu_PFG[which(simu_PFG$PFG %in% FATE.PFG), ] simu_PFG <- simu_PFG %>% group_by(pixel,strata) %>% mutate(relative.abundance = round(prop.table(abs), digits = 2)) #those are proportions, not percentages simu_PFG$relative.abundance[is.na(simu_PFG$relative.abundance)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (necessary to avoid risk of confusion with NA in pixels because out of the map) simu_PFG <- as.data.frame(simu_PFG) From 62bd5c3ffc25514fc4b126f5c9dd3fde46f24217 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 28 Apr 2022 11:40:43 +0200 Subject: [PATCH 092/176] Add BinaryMaps function corrections to devel_validation --- R/POST_FATE.binaryMaps.R | 284 ++-- docs/reference/POST_FATE.binaryMaps.html | 291 ++-- docs/reference/POST_FATE.validation.html | 2 +- .../PRE_FATE.params_globalParameters.html | 1236 +++++++---------- .../PRE_FATE.speciesClustering_step1.html | 347 ++--- .../PRE_FATE.speciesDistanceCombine.html | 316 ++--- man/POST_FATE.binaryMaps.Rd | 13 +- man/POST_FATE.validation.Rd | 2 +- src/libs/iostreams/src/zlib.o | Bin 244552 -> 184896 bytes 9 files changed, 1063 insertions(+), 1428 deletions(-) diff --git a/R/POST_FATE.binaryMaps.R b/R/POST_FATE.binaryMaps.R index eba2bdd..883152f 100644 --- a/R/POST_FATE.binaryMaps.R +++ b/R/POST_FATE.binaryMaps.R @@ -55,16 +55,18 @@ ##' \Leftrightarrow \;\; 1}} ##' } ##' -##' Binary maps per stratum are obtained by multiplying raster maps from -##' \code{ABUND_perPFG_perStrata} folder by corresponding raster maps from -##' \code{BIN_perPFG_allStrata} folder. -##' ##' \strong{It requires} that the \code{\link{POST_FATE.relativeAbund}} ##' function has been run and that the folder \code{ABUND_REL_perPFG_allStrata} ##' exists. \cr If \code{method = 2}, it requires that the ##' \code{\link{POST_FATE.graphic_validationStatistics}} function has been run. ##' \cr \cr ##' +##' \strong{If pixel abundances per PFG per stratum were saved} (see +##' \code{\link{PRE_FATE.params_globalParameters}}), binary maps per stratum are +##' obtained by multiplying raster maps from \code{ABUND_perPFG_perStrata} folder +##' by corresponding raster maps from \code{BIN_perPFG_allStrata} folder. \cr \cr +##' +##' ##' \strong{These binary \code{raster} files can then be used by other ##' functions} : ##' @@ -81,7 +83,8 @@ ##' \item{\file{BIN_perPFG \cr_allStrata}}{containing presence / absence ##' raster maps for each PFG across all strata} ##' \item{\file{BIN_perPFG \cr_perStrata}}{containing presence / absence -##' raster maps for each PFG for each stratum} +##' raster maps for each PFG for each stratum \cr (\emph{if pixel abundances per PFG +##' per stratum were saved (see \code{\link{PRE_FATE.params_globalParameters}})})} ##' } ##' ##' @@ -171,152 +174,155 @@ POST_FATE.binaryMaps = function( ############################################################################# res = foreach (abs.simulParam = abs.simulParams) %do% - { - - cat("\n+++++++\n") - cat("\n Simulation name : ", name.simulation) - cat("\n Simulation file : ", abs.simulParam) - cat("\n") - - ## Get results directories ------------------------------------------------ - GLOB_DIR = .getGraphics_results(name.simulation = name.simulation - , abs.simulParam = abs.simulParam) - - ## Get number of PFGs ----------------------------------------------------- - ## Get PFG names ---------------------------------------------------------- - GLOB_SIM = .getGraphics_PFG(name.simulation = name.simulation - , abs.simulParam = abs.simulParam) - - ## Get raster mask -------------------------------------------------------- - GLOB_MASK = .getGraphics_mask(name.simulation = name.simulation - , abs.simulParam = abs.simulParam) - - ## Get list of arrays and extract years of simulation --------------------- - years = sort(unique(as.numeric(years))) - no_years = length(years) - - if (method == 1) - { - mat.cutoff = expand.grid(year = years - , PFG = GLOB_SIM$PFG - , cutoff = method1.threshold - , stringsAsFactors = FALSE) - } else if (exists("valid.files")) { - valid.files = valid.files[grep(basename(GLOB_DIR$dir.save), valid.files)] - if (length(valid.files) > 0) + + cat("\n+++++++\n") + cat("\n Simulation name : ", name.simulation) + cat("\n Simulation file : ", abs.simulParam) + cat("\n") + + ## Get results directories ------------------------------------------------ + GLOB_DIR = .getGraphics_results(name.simulation = name.simulation + , abs.simulParam = abs.simulParam) + + ## Get number of PFGs ----------------------------------------------------- + ## Get PFG names ---------------------------------------------------------- + GLOB_SIM = .getGraphics_PFG(name.simulation = name.simulation + , abs.simulParam = abs.simulParam) + + ## Get raster mask -------------------------------------------------------- + GLOB_MASK = .getGraphics_mask(name.simulation = name.simulation + , abs.simulParam = abs.simulParam) + + ## Get list of arrays and extract years of simulation --------------------- + years = sort(unique(as.numeric(years))) + no_years = length(years) + + if (method == 1) { - mat.cutoff = foreach(fi = valid.files, .combine = "rbind") %do% + mat.cutoff = expand.grid(year = years + , PFG = GLOB_SIM$PFG + , cutoff = method1.threshold + , stringsAsFactors = FALSE) + } else if (exists("valid.files")) + { + valid.files = valid.files[grep(basename(GLOB_DIR$dir.save), valid.files)] + if (length(valid.files) > 0) + { + mat.cutoff = foreach(fi = valid.files, .combine = "rbind") %do% + { + ye = sub("_validationStatistics.*", "", fi) + ye = sub("POST_FATE_TABLE_YEAR_", "", ye) + tab = fread(paste0(name.simulation, "/RESULTS/", fi)) + tab = as.data.frame(tab, stringsAsFactors = FALSE) + tab = unique(tab[, c("PFG", "cutoff")]) + tab$year = as.numeric(ye) + return(tab) + } + } else { - ye = sub("_validationStatistics.*", "", fi) - ye = sub("POST_FATE_TABLE_YEAR_", "", ye) - tab = fread(paste0(name.simulation, "/RESULTS/", fi)) - tab = as.data.frame(tab, stringsAsFactors = FALSE) - tab = unique(tab[, c("PFG", "cutoff")]) - tab$year = as.numeric(ye) - return(tab) + stop(paste0("Missing data!\n The folder ", name.simulation + , "/RESULTS/ does not contain adequate files " + , "(starting by `POST_FATE_TABLE_YEAR_[...]_validationStatistics` " + , "and corresponding to `", basename(GLOB_DIR$dir.save), "` folder)")) } - } else - { - stop(paste0("Missing data!\n The folder ", name.simulation - , "/RESULTS/ does not contain adequate files " - , "(starting by `POST_FATE_TABLE_YEAR_[...]_validationStatistics` " - , "and corresponding to `", basename(GLOB_DIR$dir.save), "` folder)")) } - } - - ## UNZIP the raster saved ------------------------------------------------- - raster.perPFG.perStrata = .getRasterNames(years, "perStrata", "ABUND", GLOB_DIR) - .unzip(folder_name = GLOB_DIR$dir.output.perPFG.perStrata - , list_files = raster.perPFG.perStrata - , no_cores = opt.no_CPU) - - - - ## get the data inside the rasters ---------------------------------------- - cat("\n ---------- GETTING PRESENCE/ABSENCE maps for") - for (y in years) - { - cat("\n> year", y) - file_name = paste0(GLOB_DIR$dir.output.perPFG.allStrata.REL - , "Abund_relative_YEAR_" - , y - , "_" - , GLOB_SIM$PFG - , "_STRATA_all.tif") - gp = GLOB_SIM$PFG[which(file.exists(file_name))] - file_name = file_name[which(file.exists(file_name))] + ## UNZIP the raster saved ------------------------------------------------- + if (GLOB_SIM$saveStrat) { + raster.perPFG.perStrata = .getRasterNames(years, "perStrata", "ABUND", GLOB_DIR) + .unzip(folder_name = GLOB_DIR$dir.output.perPFG.perStrata + , list_files = raster.perPFG.perStrata + , no_cores = opt.no_CPU) + } + - if (length(file_name) > 0) + ## get the data inside the rasters ---------------------------------------- + cat("\n ---------- GETTING PRESENCE/ABSENCE maps for") + for (y in years) { - ras = stack(file_name) * GLOB_MASK$ras.mask - names(ras) = gp + cat("\n> year", y) - for (fg in gp) + file_name = paste0(GLOB_DIR$dir.output.perPFG.allStrata.REL + , "Abund_relative_YEAR_" + , y + , "_" + , GLOB_SIM$PFG + , "_STRATA_all.tif") + gp = GLOB_SIM$PFG[which(file.exists(file_name))] + file_name = file_name[which(file.exists(file_name))] + + if (length(file_name) > 0) { - ## Produce binary maps ------------------------------------ - ## ALL STRATA - new_name = paste0(GLOB_DIR$dir.output.perPFG.allStrata.BIN - , "Binary_YEAR_" - , y - , "_" - , fg - , "_STRATA_all.tif") - ind.cutoff = which(mat.cutoff$year == y & mat.cutoff$PFG == fg) - if (length(ind.cutoff) > 0) + ras = stack(file_name) * GLOB_MASK$ras.mask + names(ras) = gp + + for (fg in gp) { - cutoff = mat.cutoff$cutoff[ind.cutoff] - ras.bin = ras[[fg]] - ras.bin[] = ifelse(ras.bin[] >= cutoff, 1, 0) - writeRaster(x = ras.bin - , filename = new_name - , overwrite = TRUE) - - ## SEPARATED STRATA - prev_names = list.files(path = GLOB_DIR$dir.output.perPFG.perStrata - , pattern = paste0("Abund_YEAR_" - , y - , "_" - , fg - , "_STRATA") - , full.names = TRUE) - prev_names = prev_names[grep(".tif$", prev_names)] - if (length(prev_names) > 0) + ## Produce binary maps ------------------------------------ + ## ALL STRATA + new_name = paste0(GLOB_DIR$dir.output.perPFG.allStrata.BIN + , "Binary_YEAR_" + , y + , "_" + , fg + , "_STRATA_all.tif") + ind.cutoff = which(mat.cutoff$year == y & mat.cutoff$PFG == fg) + if (length(ind.cutoff) > 0) { - new_names = sub(GLOB_DIR$dir.output.perPFG.perStrata - , GLOB_DIR$dir.output.perPFG.perStrata.BIN - , prev_names) - new_names = sub("Abund_YEAR_", "Binary_YEAR_", new_names) - ras.bin.str = stack(prev_names) - ras.bin.str = ras.bin.str * ras.bin - writeRaster(x = ras.bin.str - , filename = new_names - , overwrite = TRUE - , bylayer = TRUE) + cutoff = mat.cutoff$cutoff[ind.cutoff] + ras.bin = ras[[fg]] + ras.bin[] = ifelse(ras.bin[] >= cutoff, 1, 0) + writeRaster(x = ras.bin + , filename = new_name + , overwrite = TRUE) - message(paste0("\n The output files \n" - , paste0(" > ", basename(new_names), " \n" - , collapse = "") - , "have been successfully created !\n")) + ## SEPARATED STRATA + if (GLOB_SIM$saveStrat) { + prev_names = list.files(path = GLOB_DIR$dir.output.perPFG.perStrata + , pattern = paste0("Abund_YEAR_" + , y + , "_" + , fg + , "_STRATA") + , full.names = TRUE) + prev_names = prev_names[grep(".tif$", prev_names)] + if (length(prev_names) > 0) + { + new_names = sub(GLOB_DIR$dir.output.perPFG.perStrata + , GLOB_DIR$dir.output.perPFG.perStrata.BIN + , prev_names) + new_names = sub("Abund_YEAR_", "Binary_YEAR_", new_names) + ras.bin.str = stack(prev_names) + ras.bin.str = ras.bin.str * ras.bin + writeRaster(x = ras.bin.str + , filename = new_names + , overwrite = TRUE + , bylayer = TRUE) + + message(paste0("\n The output files \n" + , paste0(" > ", basename(new_names), " \n" + , collapse = "") + , "have been successfully created !\n")) + } + } + } else + { + warning(paste0("Missing data!\n No cutoff for year ", y, ", PFG ", fg + , ".\n No binary maps will be produced!")) } - } else - { - warning(paste0("Missing data!\n No cutoff for year ", y, ", PFG ", fg - , ".\n No binary maps will be produced!")) - } - } ## END loop on PFG - } ## END condition file_name - } ## END loop on years - cat("\n") - - - ## ZIP the raster saved --------------------------------------------------- - .zip(folder_name = GLOB_DIR$dir.output.perPFG.perStrata - , list_files = raster.perPFG.perStrata - , no_cores = opt.no_CPU) - - cat("\n> Done!\n") - - } ## END loop on abs.simulParams + } ## END loop on PFG + } ## END condition file_name + } ## END loop on years + cat("\n") + + + ## ZIP the raster saved --------------------------------------------------- + .zip(folder_name = GLOB_DIR$dir.output.perPFG.perStrata + , list_files = raster.perPFG.perStrata + , no_cores = opt.no_CPU) + + cat("\n> Done!\n") + + } ## END loop on abs.simulParams } diff --git a/docs/reference/POST_FATE.binaryMaps.html b/docs/reference/POST_FATE.binaryMaps.html index 4a0b90b..b2a49e7 100644 --- a/docs/reference/POST_FATE.binaryMaps.html +++ b/docs/reference/POST_FATE.binaryMaps.html @@ -1,70 +1,15 @@ - - - - - - - -Create binary maps for each Plant Functional Group for one (or -several) specific year of a FATE simulation — POST_FATE.binaryMaps • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create binary maps for each Plant Functional Group for one (or +several) specific year of a FATE simulation — POST_FATE.binaryMaps • RFate + + - - - - -
-
- -
- -
+
@@ -209,146 +143,133 @@

Create binary maps for each Plant Functional Group for one (or / absence for one (or several) specific FATE simulation year.

-
POST_FATE.binaryMaps(
-  name.simulation,
-  file.simulParam = NULL,
-  years,
-  method,
-  method1.threshold = 0.05,
-  method2.cutoff = NULL,
-  opt.no_CPU = 1
-)
+
+
POST_FATE.binaryMaps(
+  name.simulation,
+  file.simulParam = NULL,
+  years,
+  method,
+  method1.threshold = 0.05,
+  method2.cutoff = NULL,
+  opt.no_CPU = 1
+)
+
-

Arguments

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

years

an integer, or a vector of integer, +the PARAM_SIMUL folder of the FATE simulation

+
years
+

an integer, or a vector of integer, corresponding to the simulation year(s) that will be used to extract PFG -abundance maps

method

an integer to choose the transformation method :
-1 (relative abundance) or 2 (optimizing TSS) (see -Details)

method1.threshold

default 0.05.
If method = 1, +abundance maps

+
method
+

an integer to choose the transformation method :
1 (relative abundance) or 2 (optimizing TSS) (see +Details)

+
method1.threshold
+

default 0.05.
If method = 1, minimum relative abundance required for each PFG to be considered as present -in the concerned pixel

method2.cutoff

default NULL.
If method = 2, a -data.frame with 3 columns : year, PFG, cutoff
-(see Details)

opt.no_CPU

(optional) default 1.
The number of +in the concerned pixel

+
method2.cutoff
+

default NULL.
If method = 2, a +data.frame with 3 columns : year, PFG, cutoff
+(see Details)

+
opt.no_CPU
+

(optional) default 1.
The number of resources that can be used to parallelize the unzip/zip of raster -files

- -

Value

- -

Two folders are created :

-
BIN_perPFG
_allStrata

containing presence / absence +files

+
+
+

Value

+

Two folders are created :

BIN_perPFG
_allStrata
+

containing presence / absence raster maps for each PFG across all strata

-
BIN_perPFG
_perStrata

containing presence / absence - raster maps for each PFG for each stratum

-
+
BIN_perPFG
_perStrata
+

containing presence / absence + raster maps for each PFG for each stratum
(if pixel abundances per PFG + per stratum were saved (see PRE_FATE.params_globalParameters))

-

Details

+
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, raster maps of PFG -presence / absence.

+presence / absence.

For each PFG and each selected simulation year, raster maps are retrieved from the results folder ABUND_REL_perPFG_allStrata and lead to the production of as many maps as those found :

-
-
1 fixed threshold

relative abundance maps are transformed +

1 fixed threshold
+

relative abundance maps are transformed into binary maps according to the threshold given by method1.threshold : $$abund\_rel_{\text{ PFG}_i} > \text{method1.threshold} \;\; \Leftrightarrow \;\; 1$$

-
2 optimizing TSS

relative abundance maps are transformed - into binary maps according to the cutoff found - with the POST_FATE.graphic_validationStatistics function : + +

2 optimizing TSS
+

relative abundance maps are transformed + into binary maps according to the cutoff found + with the POST_FATE.graphic_validationStatistics function : $$abund\_rel_{\text{ PFG}_i} > \text{method2.cutoff}_{\text{ PFG}_i} \;\; \Leftrightarrow \;\; 1$$

-
-

Binary maps per stratum are obtained by multiplying raster maps from -ABUND_perPFG_perStrata folder by corresponding raster maps from -BIN_perPFG_allStrata folder.

-

It requires that the POST_FATE.relativeAbund +

It requires that the POST_FATE.relativeAbund function has been run and that the folder ABUND_REL_perPFG_allStrata -exists.
If method = 2, it requires that the -POST_FATE.graphic_validationStatistics function has been run. -

+exists.
If method = 2, it requires that the +POST_FATE.graphic_validationStatistics function has been run. +

+

If pixel abundances per PFG per stratum were saved (see +PRE_FATE.params_globalParameters), binary maps per stratum are +obtained by multiplying raster maps from ABUND_perPFG_perStrata folder +by corresponding raster maps from BIN_perPFG_allStrata folder.

+

These binary raster files can then be used by other functions :

- - -

See also

- - -

Author

- +
+ +
+

Author

Maya Guéguen

+
+
-

- - + + diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index 96f9adc..0e574be 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -164,7 +164,7 @@

Computes validation data for habitat, PFG richness and composition for a = TRUE, releves.PFG, hab.obs, - validation.mask, + validation.mask = NULL, studied.habitat, list.strata.simulations, doComposition = TRUE, diff --git a/docs/reference/PRE_FATE.params_globalParameters.html b/docs/reference/PRE_FATE.params_globalParameters.html index ee161aa..020c0cd 100644 --- a/docs/reference/PRE_FATE.params_globalParameters.html +++ b/docs/reference/PRE_FATE.params_globalParameters.html @@ -1,70 +1,15 @@ - - - - - - - -Create Global_parameters parameter file for a FATE -simulation — PRE_FATE.params_globalParameters • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create Global_parameters parameter file for a FATE +simulation — PRE_FATE.params_globalParameters • RFate - - + + - - -
-
- -
- -
+
@@ -209,415 +143,302 @@

Create Global_parameters parameter file for a FATE containing GLOBAL PARAMETERS used in FATE model.

-
PRE_FATE.params_globalParameters(
-  name.simulation,
-  opt.no_CPU = 1,
-  opt.replacePrevious = FALSE,
-  opt.saving_abund_PFG_stratum = TRUE,
-  opt.saving_abund_PFG = TRUE,
-  opt.saving_abund_stratum = FALSE,
-  required.no_PFG,
-  required.no_strata,
-  required.simul_duration = 1000,
-  required.seeding_duration = 300,
-  required.seeding_timestep = 1,
-  required.seeding_input = 100,
-  required.potential_fecundity = 100,
-  required.max_abund_low,
-  required.max_abund_medium,
-  required.max_abund_high,
-  doLight = FALSE,
-  LIGHT.thresh_medium,
-  LIGHT.thresh_low,
-  LIGHT.saving = TRUE,
-  doSoil = FALSE,
-  SOIL.init,
-  SOIL.retention,
-  SOIL.saving = TRUE,
-  doDispersal = FALSE,
-  DISPERSAL.mode = 1,
-  DISPERSAL.saving = FALSE,
-  doHabSuitability = FALSE,
-  HABSUIT.mode = 1,
-  doDisturbances = FALSE,
-  DIST.no,
-  DIST.no_sub = 4,
-  DIST.freq = 1,
-  doDrought = FALSE,
-  DROUGHT.no_sub = 4,
-  doAliens = FALSE,
-  ALIEN.no,
-  ALIEN.freq = 1,
-  doFire = FALSE,
-  FIRE.no,
-  FIRE.no_sub = 4,
-  FIRE.freq = 1,
-  FIRE.ignit_mode = 1,
-  FIRE.ignit_no,
-  FIRE.ignit_noHist,
-  FIRE.ignit_logis = c(0.6, 2.5, 0.05),
-  FIRE.ignit_flammMax,
-  FIRE.neigh_mode = 1,
-  FIRE.neigh_CC = c(2, 2, 2, 2),
-  FIRE.prop_mode = 1,
-  FIRE.prop_intensity,
-  FIRE.prop_logis = c(0.6, 2.5, 0.05),
-  FIRE.quota_mode = 4,
-  FIRE.quota_max
-)
+
+
PRE_FATE.params_globalParameters(
+  name.simulation,
+  opt.no_CPU = 1,
+  opt.replacePrevious = FALSE,
+  opt.saving_abund_PFG_stratum = TRUE,
+  opt.saving_abund_PFG = TRUE,
+  opt.saving_abund_stratum = FALSE,
+  required.no_PFG,
+  required.no_strata,
+  required.simul_duration = 1000,
+  required.seeding_duration = 300,
+  required.seeding_timestep = 1,
+  required.seeding_input = 100,
+  required.potential_fecundity = 100,
+  required.max_abund_low,
+  required.max_abund_medium,
+  required.max_abund_high,
+  doLight = FALSE,
+  LIGHT.thresh_medium,
+  LIGHT.thresh_low,
+  LIGHT.saving = TRUE,
+  doSoil = FALSE,
+  SOIL.init,
+  SOIL.retention,
+  SOIL.saving = TRUE,
+  doDispersal = FALSE,
+  DISPERSAL.mode = 1,
+  DISPERSAL.saving = FALSE,
+  doHabSuitability = FALSE,
+  HABSUIT.mode = 1,
+  doDisturbances = FALSE,
+  DIST.no,
+  DIST.no_sub = 4,
+  DIST.freq = 1,
+  doDrought = FALSE,
+  DROUGHT.no_sub = 4,
+  doAliens = FALSE,
+  ALIEN.no,
+  ALIEN.freq = 1,
+  doFire = FALSE,
+  FIRE.no,
+  FIRE.no_sub = 4,
+  FIRE.freq = 1,
+  FIRE.ignit_mode = 1,
+  FIRE.ignit_no,
+  FIRE.ignit_noHist,
+  FIRE.ignit_logis = c(0.6, 2.5, 0.05),
+  FIRE.ignit_flammMax,
+  FIRE.neigh_mode = 1,
+  FIRE.neigh_CC = c(2, 2, 2, 2),
+  FIRE.prop_mode = 1,
+  FIRE.prop_intensity,
+  FIRE.prop_logis = c(0.6, 2.5, 0.05),
+  FIRE.quota_mode = 4,
+  FIRE.quota_max
+)
+
-

Arguments

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

opt.no_CPU

(optional) default 1.
an integer +

+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
opt.no_CPU
+

(optional) default 1.
an integer corresponding to the number of resources that can be used to parallelize -the FATE simulation

opt.replacePrevious

(optional) default FALSE.
+the FATE simulation

+
opt.replacePrevious
+

(optional) default FALSE.
If TRUE, pre-existing files inside -name.simulation/DATA/GLOBAL_PARAMETERS folder will be replaced

opt.saving_abund_PFG_stratum

(optional) default TRUE. -
If TRUE, and saving years have been defined within the +name.simulation/DATA/GLOBAL_PARAMETERS folder will be replaced

+
opt.saving_abund_PFG_stratum
+

(optional) default TRUE. +
If TRUE, and saving years have been defined within the Simul_parameters file with the SAVING_YEARS_MAPS flag, -pixel abundances per PFG per stratum are saved

opt.saving_abund_PFG

(optional) default TRUE. -
If TRUE, and saving years have been defined within the +pixel abundances per PFG per stratum are saved

+
opt.saving_abund_PFG
+

(optional) default TRUE. +
If TRUE, and saving years have been defined within the Simul_parameters file with the SAVING_YEARS_MAPS flag, -pixel abundances per PFG are saved

opt.saving_abund_stratum

(optional) default TRUE. -
If TRUE, and saving years have been defined within the +pixel abundances per PFG are saved

+
opt.saving_abund_stratum
+

(optional) default TRUE. +
If TRUE, and saving years have been defined within the Simul_parameters file with the SAVING_YEARS_MAPS flag, -pixel abundances per stratum are saved

required.no_PFG

an integer corresponding to the number of PFG

required.no_strata

an integer corresponding to the number of -height strata

required.simul_duration

an integer corresponding to the -duration of simulation (in years)

required.seeding_duration

an integer corresponding to the -duration of seeding (in years)

required.seeding_timestep

an integer corresponding to the +pixel abundances per stratum are saved

+
required.no_PFG
+

an integer corresponding to the number of PFG

+
required.no_strata
+

an integer corresponding to the number of +height strata

+
required.simul_duration
+

an integer corresponding to the +duration of simulation (in years)

+
required.seeding_duration
+

an integer corresponding to the +duration of seeding (in years)

+
required.seeding_timestep
+

an integer corresponding to the time interval at which occurs the seeding, and until the seeding duration -is not over (in years)

required.seeding_input

an integer corresponding to the number +is not over (in years)

+
required.seeding_input
+

an integer corresponding to the number of seeds attributed to each PFG at each time step, and until the seeding -duration is not over

required.potential_fecundity

an integer corresponding to the +duration is not over

+
required.potential_fecundity
+

an integer corresponding to the maximum number of seeds produced each year by a PFG (it can also be specified within PFG succession files (see -PRE_FATE.params_PFGsuccession) -otherwise this value will be used)

required.max_abund_low

an integer in the order of -1 000 to rescale abundance values of tall PFG

required.max_abund_medium

an integer in the order of -1 000 to rescale abundance values of intermediate PFG

required.max_abund_high

an integer in the order of -1 000 to rescale abundance values of small PFG

doLight

default FALSE.
If TRUE, light interaction +PRE_FATE.params_PFGsuccession) +otherwise this value will be used)

+
required.max_abund_low
+

an integer in the order of +1 000 to rescale abundance values of tall PFG

+
required.max_abund_medium
+

an integer in the order of +1 000 to rescale abundance values of intermediate PFG

+
required.max_abund_high
+

an integer in the order of +1 000 to rescale abundance values of small PFG

+
doLight
+

default FALSE.
If TRUE, light interaction is activated in the FATE simulation, and associated parameters are -required

LIGHT.thresh_medium

(optional)
an integer in the +required

+
LIGHT.thresh_medium
+

(optional)
an integer in the order of 1 000 to convert PFG abundances in each stratum into light resources. It corresponds to the limit of abundances above which light resources are medium. PFG abundances lower than this threshold imply high amount of light. It is consequently lower than -LIGHT.thresh_low.

LIGHT.thresh_low

(optional)
an integer in the order +LIGHT.thresh_low.

+
LIGHT.thresh_low
+

(optional)
an integer in the order of 1 000 to convert PFG abundances in each strata into light resources. It corresponds to the limit of abundances above which light resources are low. PFG abundances higher than LIGHT.thresh_medium and lower than this threshold imply -medium amount of light.

LIGHT.saving

(optional) default TRUE. -
If TRUE, and saving years have been defined within the +medium amount of light.

+
LIGHT.saving
+

(optional) default TRUE. +
If TRUE, and saving years have been defined within the Simul_parameters file with the SAVING_YEARS_MAPS flag, -pixel light resources are saved

doSoil

default FALSE.
If TRUE, soil interaction is +pixel light resources are saved

+
doSoil
+

default FALSE.
If TRUE, soil interaction is activated in the FATE simulation, and associated parameters -are required

SOIL.init

(optional)
a double corresponding to the +are required

+
SOIL.init
+

(optional)
a double corresponding to the soil value to initialize all pixels when starting the FATE -simulation

SOIL.retention

(optional)
a double corresponding +simulation

+
SOIL.retention
+

(optional)
a double corresponding to the percentage of soil value of the previous simulation year that will -be kept in the calculation of the soil value of the current simulation year

SOIL.saving

(optional) default TRUE. -
If TRUE, and saving years have been defined within the +be kept in the calculation of the soil value of the current simulation year

+
SOIL.saving
+

(optional) default TRUE. +
If TRUE, and saving years have been defined within the Simul_parameters file with the SAVING_YEARS_MAPS flag, -pixel soil resources are saved

doDispersal

default FALSE.
If TRUE, seed dispersal +pixel soil resources are saved

+
doDispersal
+

default FALSE.
If TRUE, seed dispersal is activated in the FATE simulation, and associated parameters are -required

DISPERSAL.mode

(optional)
an integer corresponding +required

+
DISPERSAL.mode
+

(optional)
an integer corresponding to the way of simulating the seed dispersal for each PFG, either packets kernel (1), exponential kernel (2) or exponential kernel with -probability (3)

DISPERSAL.saving

(optional) default TRUE. -
If TRUE, and saving years have been defined within the +probability (3)

+
DISPERSAL.saving
+

(optional) default TRUE. +
If TRUE, and saving years have been defined within the Simul_parameters file with the SAVING_YEARS_MAPS flag, -pixel dispersed seeds per PFG are saved

doHabSuitability

default FALSE.
If TRUE, habitat +pixel dispersed seeds per PFG are saved

+
doHabSuitability
+

default FALSE.
If TRUE, habitat suitability is activated in the FATE simulation, and associated -parameters are required

HABSUIT.mode

(optional)
an integer +parameters are required

+
HABSUIT.mode
+

(optional)
an integer corresponding to the way of simulating the habitat suitability variation between years for each PFG, either random (1) or PFG specific -(2)

doDisturbances

default FALSE.
If TRUE, disturbances +(2)

+
doDisturbances
+

default FALSE.
If TRUE, disturbances are applied in the FATE simulation, and associated parameters are -required

DIST.no

(optional)
an integer corresponding to the -number of disturbances

DIST.no_sub

(optional)
an integer corresponding to -the number of way a PFG could react to a disturbance

DIST.freq

(optional)
a vector of integer -corresponding to the frequency of each disturbance (in years)

doDrought

default FALSE.
If TRUE, drought +required

+
DIST.no
+

(optional)
an integer corresponding to the +number of disturbances

+
DIST.no_sub
+

(optional)
an integer corresponding to +the number of way a PFG could react to a disturbance

+
DIST.freq
+

(optional)
a vector of integer +corresponding to the frequency of each disturbance (in years)

+
doDrought
+

default FALSE.
If TRUE, drought disturbances are applied in the FATE simulation, and associated -parameters are required

DROUGHT.no_sub

(optional)
an integer corresponding -to the number of way a PFG could react to a drought disturbance

doAliens

default FALSE.
If TRUE, invasive plant +parameters are required

+
DROUGHT.no_sub
+

(optional)
an integer corresponding +to the number of way a PFG could react to a drought disturbance

+
doAliens
+

default FALSE.
If TRUE, invasive plant introduction is activated in the FATE simulation, and associated -parameters are required

ALIEN.no

(optional)
an integer corresponding to the -number of introductions

ALIEN.freq

(optional)
a vector of integer -corresponding to the frequency of each introduction (in years)

doFire

default FALSE.
If TRUE, fire +parameters are required

+
ALIEN.no
+

(optional)
an integer corresponding to the +number of introductions

+
ALIEN.freq
+

(optional)
a vector of integer +corresponding to the frequency of each introduction (in years)

+
doFire
+

default FALSE.
If TRUE, fire disturbances are applied in the FATE simulation, and associated -parameters are required

FIRE.no

(optional)
an integer corresponding to the -number of fire disturbances

FIRE.no_sub

(optional)
an integer corresponding to -the number of way a PFG could react to a fire disturbance

FIRE.freq

(optional)
a vector of integer -corresponding to the frequency of each fire disturbance (in years)

FIRE.ignit_mode

(optional)
an integer +parameters are required

+
FIRE.no
+

(optional)
an integer corresponding to the +number of fire disturbances

+
FIRE.no_sub
+

(optional)
an integer corresponding to +the number of way a PFG could react to a fire disturbance

+
FIRE.freq
+

(optional)
a vector of integer +corresponding to the frequency of each fire disturbance (in years)

+
FIRE.ignit_mode
+

(optional)
an integer corresponding to the way of simulating the fire(s) ignition each year, either random (1, 2 or 3), according to cell conditions -(4) or through a map (5)

FIRE.ignit_no

(optional) (required if -FIRE.ignit_mode = 1 or 2)
an integer corresponding to the -number of fires starting each year

FIRE.ignit_noHist

(optional) (required if -FIRE.ignit_mode = 3)
a vector of integer -corresponding to historical number of fires

FIRE.ignit_logis

(optional) (required if -FIRE.ignit_mode = 4)
a vector of 3 values to parameterize -the logistic probability function :

    -
  1. asymptote of the function curve

  2. +(4) or through a map (5)

    +
    FIRE.ignit_no
    +

    (optional) (required if +FIRE.ignit_mode = 1 or 2)
    an integer corresponding to the +number of fires starting each year

    +
    FIRE.ignit_noHist
    +

    (optional) (required if +FIRE.ignit_mode = 3)
    a vector of integer +corresponding to historical number of fires

    +
    FIRE.ignit_logis
    +

    (optional) (required if +FIRE.ignit_mode = 4)
    a vector of 3 values to parameterize +the logistic probability function :

    1. asymptote of the function curve

    2. time where the slope starts to increase

    3. speed of slope increase

    4. -
FIRE.ignit_flammMax

(optional) (required if -FIRE.ignit_mode = 4)
an integer corresponding to the -maximum flammmability of PFG

FIRE.neigh_mode

(optional)
an integer + +

FIRE.ignit_flammMax
+

(optional) (required if +FIRE.ignit_mode = 4)
an integer corresponding to the +maximum flammmability of PFG

+
FIRE.neigh_mode
+

(optional)
an integer corresponding to the way of finding neighboring cells each year, -either 8 adjacent (1) or with cookie cutter (2 or 3)

FIRE.neigh_CC

(optional) (required if -FIRE.neigh_mode = 2 or 3)
a vector of 4 values -corresponding to the extent of cookie cutter :

    -
  1. number of cells towards north

  2. +either 8 adjacent (1) or with cookie cutter (2 or 3)

    +
    FIRE.neigh_CC
    +

    (optional) (required if +FIRE.neigh_mode = 2 or 3)
    a vector of 4 values +corresponding to the extent of cookie cutter :

    1. number of cells towards north

    2. number of cells towards east

    3. number of cells towards south

    4. number of cells towards west

    5. -
FIRE.prop_mode

(optional)
an integer + +

FIRE.prop_mode
+

(optional)
an integer corresponding to the way of simulating the fire(s) propagation each year, either fire intensity (1), % of plants consumed (2), maximum amount of resources (3 or 4), or according to cell conditions -(5)

FIRE.prop_intensity

(optional) (required if -FIRE.prop_mode = 1)
a vector of double +(5)

+
FIRE.prop_intensity
+

(optional) (required if +FIRE.prop_mode = 1)
a vector of double corresponding to the intensity or probability of dispersal of each fire -disturbance (between 0 and 1)

FIRE.prop_logis

(optional) (required if -FIRE.prop_mode = 5)
a vector of 3 values to parameterize -the logistic probability function :

    -
  1. asymptote of the function curve

  2. +disturbance (between 0 and 1)

    +
    FIRE.prop_logis
    +

    (optional) (required if +FIRE.prop_mode = 5)
    a vector of 3 values to parameterize +the logistic probability function :

    1. asymptote of the function curve

    2. time where the slope starts to increase

    3. speed of slope increase

    4. -
FIRE.quota_mode

(optional)
an integer + +

FIRE.quota_mode
+

(optional)
an integer corresponding to the way of ending the fire(s) spread each year, either maximum steps (1), maximum amount of resources (2), -maximum cells (3), or keep going (4)

FIRE.quota_max

(optional) (required if -FIRE.quota_mode = 1, 2 or 3)
an integer corresponding to -the maximum quantity limit (either steps, resources, cells)

- -

Value

- +maximum cells (3), or keep going (4)

+
FIRE.quota_max
+

(optional) (required if +FIRE.quota_mode = 1, 2 or 3)
an integer corresponding to +the maximum quantity limit (either steps, resources, cells)

+
+
+

Value

A .txt file into the name.simulation/DATA/GLOBAL_PARAMETERS directory with the following -parameters :

-
    -
  • NO_CPU

  • +parameters :

    • NO_CPU

    • SAVING_ABUND_PFG_STRATUM

    • SAVING_ABUND_PFG

    • SAVING_ABUND_STRATUM

    • @@ -630,62 +451,30 @@

      Value

    • POTENTIAL_FECUNDITY

    • MAX_ABUND_LOW

    • MAX_ABUND_MEDIUM

    • -
    • MAX_ABUND_HIGH

    • -
    - -

    If the simulation includes light interaction :

    -
      -
    • DO_LIGHT_INTERACTION

    • +
    • MAX_ABUND_HIGH

    • +

    If the simulation includes light interaction :

    • DO_LIGHT_INTERACTION

    • LIGHT_THRESH_MEDIUM

    • LIGHT_THRESH_LOW

    • LIGHT_SAVING

    • -
    - -

    If the simulation includes soil interaction :

    -
      -
    • DO_SOIL_INTERACTION

    • +

    If the simulation includes soil interaction :

    • DO_SOIL_INTERACTION

    • SOIL_INIT

    • SOIL_RETENTION

    • SOIL_SAVING

    • -
    - -

    If the simulation includes dispersal :

    -
      -
    • DO_DISPERSAL

    • +

    If the simulation includes dispersal :

    • DO_DISPERSAL

    • DISPERSAL_MODE

    • DISPERSAL_SAVING

    • -
    - -

    If the simulation includes habitat suitability :

    -
      -
    • DO_HAB_SUITABILITY

    • +

    If the simulation includes habitat suitability :

    • DO_HAB_SUITABILITY

    • HABSUIT_MODE

    • -
    - -

    If the simulation includes disturbances :

    -
      -
    • DO_DISTURBANCES

    • +

    If the simulation includes disturbances :

    • DO_DISTURBANCES

    • DIST_NO

    • DIST_NOSUB

    • DIST_FREQ

    • -
    - -

    If the simulation includes drought disturbance :

    -
      -
    • DO_DROUGHT_DISTURBANCE

    • +

    If the simulation includes drought disturbance :

    • DO_DROUGHT_DISTURBANCE

    • DROUGHT_NOSUB

    • -
    - -

    If the simulation includes aliens introduction :

    -
      -
    • DO_ALIENS_INTRODUCTION

    • +

    If the simulation includes aliens introduction :

    • DO_ALIENS_INTRODUCTION

    • ALIENS_NO

    • ALIENS_FREQ

    • -
    - -

    If the simulation includes fire disturbance :

    -
      -
    • DO_FIRE_DISTURBANCE

    • +

    If the simulation includes fire disturbance :

    • DO_FIRE_DISTURBANCE

    • FIRE_NO

    • FIRE_NOSUB

    • FIRE_FREQ

    • @@ -701,79 +490,97 @@

      Value

    • FIRE_PROP_LOGIS

    • FIRE_QUOTA_MODE

    • FIRE_QUOTA_MAX

    • -
    - -

    Details

    - +
+
+

Details

The core module of FATE requires several parameters to define general characteristics of the simulation :

-
-
Studied system


-

-
no_PFG

the number of plant functional groups that will be - included into the simulation.
This number should match with the +

Studied system
+


no_PFG
+

the number of plant functional groups that will be + included into the simulation.
This number should match with the number of files that will be given to parameterize the different activated modules with the characteristics of each group (SUCC, DISP, ...).

-
no_STRATA

the number of height strata that will be used into the - succession module.
This number should match with the maximum number - of strata possible defined into the PFG SUCC files.

-
Abundance equilibrium


-

-
potential_fecundity

the number of seeds produced each year by - each mature individual.
Maximal number of seeds produced per pixel + +

no_STRATA
+

the number of height strata that will be used into the + succession module.
This number should match with the maximum number + of strata possible defined into the PFG SUCC files.

+ + +

+ +
Abundance equilibrium
+


potential_fecundity
+

the number of seeds produced each year by + each mature individual.
Maximal number of seeds produced per pixel is limited by PFG maximum abundance, meaning that maximum fecundity per PFG per pixel is equal to \(MaxAbund * \text{required.potential_fecundity}\)

-
max_abund_low / medium / high

abundance regulation thresholds for + +

max_abund_low / medium / high
+

abundance regulation thresholds for tall / intermediate / small PFG within a pixel (in `FATE` arbitrary - abundance units).
+ abundance units).
Each PFG is assigned with one of these 3 values (see - PRE_FATE.params_PFGsuccession) to be a broad proxy of the + PRE_FATE.params_PFGsuccession) to be a broad proxy of the amount of space it can occupy within a pixel (herbaceous should be more numerous than phanerophytes). These thresholds help regulate the PFG fecundity : $$fecundity = min(matAbund, MaxAbund) * \text{required.potential_fecundity}$$ and recruitment happens only if : - $$totAbund < MaxAbund * (1 + ImmSize)$$

-
Simulation timing


-

-
simul_duration

the duration of simulation (in years)

-
seeding_duration

the duration of seeding (in years)

-
seeding_timestep

the time interval at which occurs the seeding, + $$totAbund < MaxAbund * (1 + ImmSize)$$

+ + +

+ +
Simulation timing
+


simul_duration
+

the duration of simulation (in years)

+ +
seeding_duration
+

the duration of seeding (in years)

+ +
seeding_timestep
+

the time interval at which occurs the seeding, and until the seeding duration is not over (in years)

-
seeding_input

the number of seeds dispersed for each PFG at each - time step, and until the seeding duration is not over



-
+
seeding_input
+

the number of seeds dispersed for each PFG at each + time step, and until the seeding duration is not over



+ + +

-

The other modules of FATE can be activated within this +

The other modules of FATE can be activated within this file, and if so, some additional parameters will be required :

-
-
LIGHT

= to influence seed recruitment and plant mortality according - to PFG preferences for light conditions
(see - PRE_FATE.params_PFGlight)
+

LIGHT
+

= to influence seed recruitment and plant mortality according + to PFG preferences for light conditions
(see + PRE_FATE.params_PFGlight)
= light resources are calculated as a proxy of PFG abundances weighted by - their shade factor within each height stratum

+ their shade factor within each height stratum

To transform PFG abundances : $$abund_{\text{ Stratum}_k} = \sum abund_{\text{ PFG}_{i}\text{, }\text{Stratum}_k} * - \text{shade.FACT}_{\text{ PFG}_{i}}$$ into light resources : -

    -
  • \(light_{\text{ Stratum}_k} = \text{High} \;\; \;\;\) if + \text{shade.FACT}_{\text{ PFG}_{i}}$$

    +

    into light resources :

    • \(light_{\text{ Stratum}_k} = \text{High} \;\; \;\;\) if \(\;\;\;\; abund_{\text{ Stratum}_k} < \text{LIGHT.thresh_medium}\)

    • \(light_{\text{ Stratum}_k} = \text{Medium} \;\; \;\;\) if \(\;\;\;\; \text{LIGHT.thresh_medium } < abund_{\text{ Stratum}_k} < \text{LIGHT.thresh_low}\)

    • \(light_{\text{ Stratum}_k} = \text{Low} \;\; \;\;\) if \(\;\;\;\; abund_{\text{ Stratum}_k} > \text{LIGHT.thresh_low}\)

    • -
    As light resources are directly obtained from PFG abundances, +

As light resources are directly obtained from PFG abundances, LIGHT.thresh_medium and LIGHT.thresh_low parameters should be on the same scale than required.max_abund_low, required.max_abund_medium and required.max_abund_high - parameters from the core module.

-
SOIL

= to influence seed recruitment and plant mortality - according to PFG preferences for soil conditions
(see - PRE_FATE.params_PFGsoil)
+ parameters from the core module.


+ +
SOIL
+

= to influence seed recruitment and plant mortality + according to PFG preferences for soil conditions
(see + PRE_FATE.params_PFGsoil)
= soil composition is calculated as the weighted mean of each PFG's contribution with a possible retention of the soil value of the previous simulation year @@ -781,13 +588,12 @@

Details with $$Soil_y = \sum abund_{\text{ PFG}_i\text{, }y} * \text{contrib}_{\text{ PFG}_i}$$ -

-
DISPERSAL

= to allow plants to disperse seeds according to 3 - user-defined distances
(see PRE_FATE.params_PFGdispersal) -

Three modes of dispersal (DISPERSAL.mode) are available : -

    -
  1. packets kernel :

      -
    • homogeneous dispersal of 50% of the seeds within the +

+ +
DISPERSAL
+

= to allow plants to disperse seeds according to 3 + user-defined distances
(see PRE_FATE.params_PFGdispersal) +

Three modes of dispersal (DISPERSAL.mode) are available :

  1. packets kernel :

    • homogeneous dispersal of 50% of the seeds within the d50 circle

    • dispersal of 49% of the seeds within the d99 - d50 ring with the same concentration as in the first circle but by pairs @@ -801,51 +607,61 @@

      Details
    • exponential kernel with probability : seeds are dispersed within each concentric circle (d50, d99 and ldd) according to a decreasing exponential density law (lambda = 1) and a - continuous decreasing probability with distance

    • -

-
HABITAT SUITABILITY

= to influence plants fecundity and seed - recruitment according to PFG preferences for habitat conditions
+ continuous decreasing probability with distance

+

+ +
HABITAT SUITABILITY
+

= to influence plants fecundity and seed + recruitment according to PFG preferences for habitat conditions
= filter based on maps given for each PFG within the - Simul_parameters file with the PFG_HAB_MASK flag
(see - PRE_FATE.params_simulParameters)

+ Simul_parameters file with the PFG_HAB_MASK flag
(see + PRE_FATE.params_simulParameters)

These maps must contain values between 0 and 1 corresponding to the probability of presence of the PFG in each pixel. Each year (timestep), this value will be compared to a reference value, and if - superior, the PFG will be able to grow and survive.
+ superior, the PFG will be able to grow and survive.
Two methods to define this habitat suitability reference value are - available (HABSUIT.mode) : -

    -
  1. random : for each pixel, the reference value is drawn + available (HABSUIT.mode) :

    1. random : for each pixel, the reference value is drawn from a uniform distribution, and the same value is used for each PFG within this pixel.

    2. PFG specific : for each PFG, a mean value and a standard deviation value are drawn from a uniform distribution. For each pixel and for each PFG, the reference value is drawn from a normal distribution of parameters the mean and standard deviation of - the PFG.

    3. -

-
DISTURBANCES

= to influence plant mortality and / or resprouting - according to PFG tolerances to these events
(see - PRE_FATE.params_PFGdisturbance)
+ the PFG.

+

+ +
DISTURBANCES
+

= to influence plant mortality and / or resprouting + according to PFG tolerances to these events
(see + PRE_FATE.params_PFGdisturbance)
= defined for events such as mowing, grazing, but also urbanization, - crops, etc
+ crops, etc
= filter based on maps given for each disturbance within the - Simul_parameters file with the DIST_MASK flag
(see - PRE_FATE.params_simulParameters)

+ Simul_parameters file with the DIST_MASK flag
(see + PRE_FATE.params_simulParameters)

These maps, containing either 0 or 1, define the impact zone of each perturbation, and the user will have to define how each PFG will - be impacted depending on age and life stage. -

-
DIST.no

the number of different disturbances

-
DIST.no_sub

the number of way a PFG could react to a + be impacted depending on age and life stage.

DIST.no
+

the number of different disturbances

+ +
DIST.no_sub
+

the number of way a PFG could react to a perturbation

-
DIST.freq

the frequency of each disturbance - (in years)

-
DROUGHT

= to experience extreme events with a direct and a - delayed response on PFG
+ +

DIST.freq
+

the frequency of each disturbance + (in years)

+ + +

+ +
DROUGHT
+

= to experience extreme events with a direct and a + delayed response on PFG
= based on a map given within the Simul_parameters file with the - DROUGHT_MASK flag
(see - PRE_FATE.params_simulParameters)

+ DROUGHT_MASK flag
(see + PRE_FATE.params_simulParameters)

This map must contain values representing proxies for drought intensity, like moisture values, in the sense that the lower the values, the higher the chance of experiencing a drought event. Developed canopy closure helps @@ -853,69 +669,81 @@

Details severe) is determined based on thresholds defined for each PFG according to, for example, their moisture preference, as well as the number of cumulated consecutive years during which the PFG experienced a drought - (see PRE_FATE.params_PFGdrought). -
-
no drought

if \(di_y > \text{threshold.MOD}_{\text{ PFG}_i}\), + (see PRE_FATE.params_PFGdrought).

no drought
+

if \(di_y > \text{threshold.MOD}_{\text{ PFG}_i}\), the counter of cumulated consecutive years of drought experienced by the PFG will decrease : $$\text{counter}_{\text{ PFG}_i} = \text{counter}_{\text{ PFG}_i} - \text{counter.RECOVERY}_{\text{ PFG}_i}$$

-
moderate drought

    -
  • if \(\text{threshold.SEV}_{\text{ PFG}_i} < di_y < + +

    moderate drought
    +

    • if \(\text{threshold.SEV}_{\text{ PFG}_i} < di_y < \text{threshold.MOD}_{\text{ PFG}_i}\)

    • if \(di_y < \text{threshold.SEV}_{\text{ PFG}_i} \;\; \text{ & } \;\; \text{counter}_{\text{ PFG}_i} = 0\)

    • -
    - then fecundity and recruitment are set to 0 for this year, and +

then fecundity and recruitment are set to 0 for this year, and counter is incremented : \(\text{counter}_{\text{ PFG}_i} ++\)

-
severe drought

    -
  • if \(di_y < \text{threshold.SEV}_{\text{ PFG}_i} \;\; + +

    severe drought
    +

    • if \(di_y < \text{threshold.SEV}_{\text{ PFG}_i} \;\; \text{ & } \;\; \text{counter.SENS}_{\text{ PFG}_i} \leq \text{counter}_{\text{ PFG}_i} < \text{counter.CUM}_{\text{ PFG}_i}\)

    • if \(\text{counter}_{\text{ PFG}_i} \geq \text{counter.CUM}_{\text{ PFG}_i}\)

    • -
    - then PFG experiences immediate drought-related mortality ; +

then PFG experiences immediate drought-related mortality ; and the year after, fecundity and recruitment will be set to 0 - and PFG will experience delayed drought-related mortality.

- As for the disturbances module, the user will have to define how each PFG - will be impacted depending on age and life stage. -
-
(DROUGHT.no)

!not required!
= 2, the + and PFG will experience delayed drought-related mortality.

+ + +

As for the disturbances module, the user will have to define how each PFG + will be impacted depending on age and life stage.

(DROUGHT.no)
+

!not required!
= 2, the immediate and delayed responses

-
DROUGHT.no_sub

the number of way a PFG could react to each of + +

DROUGHT.no_sub
+

the number of way a PFG could react to each of these two perturbations

-
(DROUGHT.freq)

!not required!
the map of + +

(DROUGHT.freq)
+

!not required!
the map of drought intensity proxy defined within the Simul_parameters file with the DROUGHT_MASK flag, as well as the DROUGHT_CHANGEMASK_YEARS and DROUGHT_CHANGEMASK_FILES flags, make it possible to manage the frequency and the variation of - drought values (see PRE_FATE.params_simulParameters) -

-
INVASIVE
INTRODUCTION

= to add new PFG during the simulation
+ drought values (see PRE_FATE.params_simulParameters) +

+ + +

+ +
INVASIVE
INTRODUCTION
+

= to add new PFG during the simulation
= defined for events such as invasive introduction, colonization, but also - new crops development, reintroduction, etc
+ new crops development, reintroduction, etc
= filter based on maps given for each PFG within the - Simul_parameters file with the PFG_MASK_ALIENS flag
(see - PRE_FATE.params_simulParameters)

+ Simul_parameters file with the PFG_MASK_ALIENS flag
(see + PRE_FATE.params_simulParameters)

These maps, containing either 0 or 1, define the - introduction areas.
If the habitat suitability filter is on, - suitability maps will also be needed for these new groups. -

-
ALIEN.no

the number of different introductions

-
ALIEN.freq

the frequency of each introduction (in years)

-
FIRE

= to influence plant mortality and / or resprouting according + introduction areas.
If the habitat suitability filter is on, + suitability maps will also be needed for these new groups.

ALIEN.no
+

the number of different introductions

+ +
ALIEN.freq
+

the frequency of each introduction (in years)

+ + +

+ +
FIRE
+

= to influence plant mortality and / or resprouting according to PFG tolerances to these events (see - PRE_FATE.params_PFGdisturbance)

+ PRE_FATE.params_PFGdisturbance)

Fire extreme events are broken down into 4 steps representing their life cycle, so to speak. Each of these steps can be parameterized - according to different available options : -

-
Ignition

Five methods to define the cells that are going to burn + according to different available options :

Ignition
+

Five methods to define the cells that are going to burn first, and from which the fire will potentially spread, are available - (FIRE.ignit_mode) : -

    -
  1. Random (fixed) : FIRE.ignit_no positions are + (FIRE.ignit_mode) :

    1. Random (fixed) : FIRE.ignit_no positions are drawn randomly over the area

    2. Random (normal distribution) : ignit_no positions are drawn randomly over the area, with @@ -926,45 +754,45 @@

      Details $$\text{ignit_no} \sim \text{FIRE.ignit_noHist} [\;\; U(1, length(\text{FIRE.ignit_noHist})) \;\;]$$

    3. Probability -(Li et al. 1997 Ecology Modelling) +(Li et al. 1997 Ecology Modelling) : each cell can be a fire start with a probability (probLi) taking into account a baseline probability (BL), the PFG composition and abundances (fuel), and a drought index (DI, only if values between 0 and 1, given within the Simul_parameters file with the DROUGHT_MASK flag - (see PRE_FATE.params_simulParameters)) : + (see PRE_FATE.params_simulParameters)) : $$probLi_y = \text{BL}_y * \text{fuel}_y * (-DI)$$ with $$\text{BL}_y = \frac{\text{FIRE.ignit_logis}[1]}{1 + e^{\text{FIRE.ignit_logis}[2] - \text{FIRE.ignit_logis}[3] * TSLF_y}}$$ $$\text{fuel}_y = \sum \frac{\text{FLAMM}_{\text{ PFG}_i}} {\text{FIRE.ignit_flammMax}} * \frac{abund_{\text{ PFG}_i\text{, }y}} {abund_{\text{ PFG}_{all}\text{, }y}}$$

    4. -
    5. Map !no neighbours, propagation, quota steps!
      +

    6. Map !no neighbours, propagation, quota steps!
      Each cell specified by the map given within the Simul_parameters file with the FIRE_MASK flag and containing either 0 or 1 to define the starting - positions (see PRE_FATE.params_simulParameters)

    7. -

-
Neighbours / dispersal range

Three methods to define the + positions (see PRE_FATE.params_simulParameters)

+

+ +
Neighbours / dispersal range
+

Three methods to define the neighboring cells of the cell currently burning, and to which the fire - will potentially spread, are available (FIRE.neigh_mode) : -

    -
  1. 8 neighbours : all the 8 adjacent cells can potentially + will potentially spread, are available (FIRE.neigh_mode) :

    1. 8 neighbours : all the 8 adjacent cells can potentially be impacted by fire, and propagation will determine which ones are effectively affected.

    2. -
    3. Extent (fixed) !no propagation step!
      All +

    4. Extent (fixed) !no propagation step!
      All cells contained within the rectangle defined by the cookie cutter extent (FIRE.neigh_CC) are impacted by fire

    5. -
    6. Extent (random) !no propagation step!
      All +

    7. Extent (random) !no propagation step!
      All cells contained within the rectangle defined by the cookie cutter extent (neigh_CC) are impacted by fire, with $$neigh\_CC_y \in \sum U(1, \text{FIRE.neigh_CC}_i)$$

    8. -

-
Propagation

Five methods to define which cells among the +

+ +
Propagation
+

Five methods to define which cells among the neighboring cells will actually burn are available - (FIRE.prop_mode) : -

    -
  1. Probability (fire intensity) : a probability is + (FIRE.prop_mode) :

    1. Probability (fire intensity) : a probability is assigned to the cell currently burning corresponding to the concerned fire intensity (FIRE.prop_intensity) and compared to a number drawn randomly for each neighbor cell

    2. @@ -983,16 +811,16 @@

      Details
    3. Maximum amount (soil) : if the soil module was activated, the cell(s) with the maximum amount of soil will burn

    4. Probability -(Li et al. 1997 Ecology Modelling) +(Li et al. 1997 Ecology Modelling) : a probability is assigned to the cell currently burning taking into account a baseline probability (BL), the PFG composition and abundances (fuel), the elevation and slope (given within the Simul_parameters file with the ELEVATION_MASK and SLOPE_MASK flags (see - PRE_FATE.params_simulParameters)), and a drought index + PRE_FATE.params_simulParameters)), and a drought index (DI, only if values between 0 and 1, given within the Simul_parameters file with the DROUGHT_MASK flag - (see PRE_FATE.params_simulParameters)) : + (see PRE_FATE.params_simulParameters)) : $$probLi_y = \text{BL}_y * \text{fuel}_y * (-DI) * probSlope$$ with $$\text{BL}_y = \frac{\text{FIRE.prop_logis}[1]}{1 + e^{\text{FIRE.prop_logis}[2] - \text{FIRE.prop_logis}[3] * TSLF_y}}$$ @@ -1002,11 +830,11 @@

      Details $$\text{if going up, } probSlope = 1 + 0.001 * \text{SLOPE}$$ $$\text{if going down, } probSlope = 1 + 0.001 * max(-30.0,-\text{SLOPE})$$

    5. -

-
Quota / spread end

Four methods to define when the fire will stop - spreading are available (FIRE.quota_mode) : -

    -
  1. Maximum step : after a fixed number of steps +

+ +
Quota / spread end
+

Four methods to define when the fire will stop + spreading are available (FIRE.quota_mode) :

  1. Maximum step : after a fixed number of steps (FIRE.quota_max)

  2. Maximum amount : when a fixed amount of PFG is consumed (FIRE.quota_max)

  3. @@ -1014,105 +842,113 @@

    Details consumed (FIRE.quota_max)

  4. Keep going : as long as it remains a fire that manages to spread

  5. -

- As for the disturbances module, the user will have to define how each PFG - will be impacted depending on age and life stage. -
-
FIRE.no

the number of different fire disturbances

-
FIRE.no_sub

the number of way a PFG could react to a - perturbation

-
FIRE.freq

the frequency of each fire disturbance - (in years)

+

-
+ +

As for the disturbances module, the user will have to define how each PFG + will be impacted depending on age and life stage.

FIRE.no
+

the number of different fire disturbances

-

See also

+
FIRE.no_sub
+

the number of way a PFG could react to a + perturbation

- -

Author

+
FIRE.freq
+

the frequency of each fire disturbance + (in years)

+ +

+ + +
+ +
+

Author

Isabelle Boulangeat, Damien Georges, Maya Guéguen

+
-

Examples

-
-## Create a skeleton folder with the default name ('FATE_simulation') ------------------------
-PRE_FATE.skeletonDirectory()
-
-## Create a Global_parameters file------------------------------------------------------------
-PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
-                                 , required.no_PFG = 3
-                                 , required.no_strata = 5
-                                 , required.simul_duration = 500
-                                 , required.seeding_duration = 50
-                                 , required.seeding_timestep = 1
-                                 , required.seeding_input = 100
-                                 , required.potential_fecundity = 100
-                                 , required.max_abund_low = 3000
-                                 , required.max_abund_medium = 5000
-                                 , required.max_abund_high = 9000
-                                 , doLight = TRUE
-                                 , LIGHT.thresh_medium = 4000
-                                 , LIGHT.thresh_low = 7000
-                                 , doDispersal = TRUE
-                                 , DISPERSAL.mode = 1
-                                 , doHabSuitability = TRUE
-                                 , HABSUIT.mode = 1)
+    
+

Examples

+

+## Create a skeleton folder with the default name ('FATE_simulation') ------------------------
+PRE_FATE.skeletonDirectory()
+
+## Create a Global_parameters file------------------------------------------------------------
+PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
+                                 , required.no_PFG = 3
+                                 , required.no_strata = 5
+                                 , required.simul_duration = 500
+                                 , required.seeding_duration = 50
+                                 , required.seeding_timestep = 1
+                                 , required.seeding_input = 100
+                                 , required.potential_fecundity = 100
+                                 , required.max_abund_low = 3000
+                                 , required.max_abund_medium = 5000
+                                 , required.max_abund_high = 9000
+                                 , doLight = TRUE
+                                 , LIGHT.thresh_medium = 4000
+                                 , LIGHT.thresh_low = 7000
+                                 , doDispersal = TRUE
+                                 , DISPERSAL.mode = 1
+                                 , doHabSuitability = TRUE
+                                 , HABSUIT.mode = 1)
                                    
-## Create SEVERAL Global_parameters files ----------------------------------------------------
-PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
-                                 , required.no_PFG = 3
-                                 , required.no_strata = 5
-                                 , required.simul_duration = 500
-                                 , required.seeding_duration = 50
-                                 , required.seeding_timestep = 1
-                                 , required.seeding_input = 100
-                                 , required.potential_fecundity = 100
-                                 , required.max_abund_low = 3000
-                                 , required.max_abund_medium = 5000
-                                 , required.max_abund_high = 9000
-                                 , doLight = TRUE
-                                 , LIGHT.thresh_medium = 4000
-                                 , LIGHT.thresh_low = 7000
-                                 , doDispersal = TRUE
-                                 , DISPERSAL.mode = 1
-                                 , doHabSuitability = TRUE
-                                 , HABSUIT.mode = c(1,2))
-
-
-
-
+## Create SEVERAL Global_parameters files ---------------------------------------------------- +PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation" + , required.no_PFG = 3 + , required.no_strata = 5 + , required.simul_duration = 500 + , required.seeding_duration = 50 + , required.seeding_timestep = 1 + , required.seeding_input = 100 + , required.potential_fecundity = 100 + , required.max_abund_low = 3000 + , required.max_abund_medium = 5000 + , required.max_abund_high = 9000 + , doLight = TRUE + , LIGHT.thresh_medium = 4000 + , LIGHT.thresh_low = 7000 + , doDispersal = TRUE + , DISPERSAL.mode = 1 + , doHabSuitability = TRUE + , HABSUIT.mode = c(1,2)) + + + +
+

+
- - - + + diff --git a/docs/reference/PRE_FATE.speciesClustering_step1.html b/docs/reference/PRE_FATE.speciesClustering_step1.html index 961903d..2c6e113 100644 --- a/docs/reference/PRE_FATE.speciesClustering_step1.html +++ b/docs/reference/PRE_FATE.speciesClustering_step1.html @@ -1,70 +1,15 @@ - - - - - - - -Create clusters based on dissimilarity matrix — PRE_FATE.speciesClustering_step1 • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create clusters based on dissimilarity matrix — PRE_FATE.speciesClustering_step1 • RFate - - - - - - - - - - - + + - - -
-
- -
- -
+
@@ -210,63 +144,73 @@

Create clusters based on dissimilarity matrix

choose the best number of clusters..

-
PRE_FATE.speciesClustering_step1(mat.species.DIST)
+
+
PRE_FATE.speciesClustering_step1(mat.species.DIST)
+
-

Arguments

- - - - - - -
mat.species.DIST

a dist object, or a list of +

+

Arguments

+
mat.species.DIST
+

a dist object, or a list of dist objects (one for each GROUP value), corresponding to the -dissimilarity distance between each pair of species.
Such an object can -be obtained with the PRE_FATE.speciesDistance function.

+dissimilarity distance between each pair of species.
Such an object can +be obtained with the PRE_FATE.speciesDistance function.

+
+
+

Value

+

A list containing one list, one data.frame with +the following columns, and two ggplot2 objects :

clust.dendrograms
+

a list with as many objects of + class hclust as data subsets

-

Value

+
clust.evaluation
+


GROUP
+

name of data subset

-

A list containing one list, one data.frame with -the following columns, and two ggplot2 objects :

-
-
clust.dendrograms

a list with as many objects of - class hclust as data subsets

-
clust.evaluation


-

-
GROUP

name of data subset

-
no.clusters

number of clusters used for the clustering

-
variable

evaluation metrics' name

-
value

value of evaluation metric

-
plot.clustMethod

ggplot2 object, representing the different +

no.clusters
+

number of clusters used for the clustering

+ +
variable
+

evaluation metrics' name

+ +
value
+

value of evaluation metric

+ + +

+ +
plot.clustMethod
+

ggplot2 object, representing the different values of metrics to choose the clustering method

-
plot.clustNo

ggplot2 object, representing the different - values of metrics to choose the number of clusters

-
+
plot.clustNo
+

ggplot2 object, representing the different + values of metrics to choose the number of clusters

-

One PRE_FATE_CLUSTERING_STEP1_numberOfClusters.pdf file is created -containing two types of graphics :

-
clusteringMethod

to account for the chosen clustering method

-
numberOfClusters

for decision support, to help the user to choose - the adequate number of clusters to be given to the - PRE_FATE.speciesClustering_step2 function

-
+

One PRE_FATE_CLUSTERING_STEP1_numberOfClusters.pdf file is created +containing two types of graphics :

clusteringMethod
+

to account for the chosen clustering method

+ +
numberOfClusters
+

for decision support, to help the user to choose + the adequate number of clusters to be given to the + PRE_FATE.speciesClustering_step2 function

-

Details

+
+
+

Details

This function allows to obtain dendrograms based on a dissimilarity distance matrix between species.

-

As for the PRE_FATE.speciesDistance method, clustering can be +

As for the PRE_FATE.speciesDistance method, clustering can be run for data subsets, conditioning that mat.species.DIST is given as a list of dist objects (instead of a dist object alone). -

+

The process is as follows :

-
-
1. Choice of the
optimal
clustering method

hierarchical clustering on the dissimilarity matrix is realized with the - hclust. -

    -
  • Several methods are available for the agglomeration : +

    1. Choice of the
    optimal
    clustering method
    +

    hierarchical clustering on the dissimilarity matrix is realized with the + hclust.

    • Several methods are available for the agglomeration : complete, ward.D, ward.D2, single, average (UPGMA), mcquitty (WPGMA), median (WPGMC) and centroid (UPGMC).

    • @@ -274,16 +218,15 @@

      Details the input distance and the one obtained with the clustering which must be minimized to help finding the best clustering method : $$ 1 - cor( \text{mat.species.DIST}, \text{clustering.DIST} ) ^ 2$$

      -

    - For each agglomeration method, this measure is calculated. The +

For each agglomeration method, this measure is calculated. The method that minimizes it is kept and used for further analyses (see - .pdf output file).

+ .pdf output file).

+ -
2. Evaluation of the
clustering

once the hierarchical - clustering is done, the number of clusters to keep should be chosen.
- To do that, several metrics are computed : -

    -
  • Dunn index (mdunn) : ratio of the smallest +

    2. Evaluation of the
    clustering
    +

    once the hierarchical + clustering is done, the number of clusters to keep should be chosen.
    + To do that, several metrics are computed :

    • Dunn index (mdunn) : ratio of the smallest distance between observations not in the same cluster to the largest intra-cluster distance. Value between 0 and \(\infty\), and should be maximized.

    • @@ -304,104 +247,104 @@

      Details small s(i) (around 0) means that the observation lies between two clusters, and observations with a negative s(i) are probably placed in the wrong cluster. Should be maximized.

      -

    - A graphic is produced, giving the values of these metrics in +

A graphic is produced, giving the values of these metrics in function of the number of clusters used. Number of clusters are highlighted in function of evaluation metrics' values to help the user to make his/her optimal choice : the brighter (yellow-ish) the better (see .pdf output file).

-
-



+



Mouchet M., Guilhaumon f., Villeger S., Mason N.W.H., Tomasini J.A. & Mouillot D., 2008. Towards a consensus for calculating dendrogam-based functional diversity indices. Oikos, 117, 794-800.

-

Note

- +
+
+

Note

The function does not return ONE dendrogram (or as many as given dissimilarity structures) but a LIST with all tested numbers of clusters. One final dendrogram can then be obtained using this result -as a parameter in the PRE_FATE.speciesClustering_step2 +as a parameter in the PRE_FATE.speciesClustering_step2 function.

-

See also

- - + +
+
+

Author

Isabelle Boulangeat, Maya Guéguen

+
-

Examples

-
-## Load example data
-Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
-
-## Species dissimilarity distances (niche overlap + traits distance)
-tab.dist = list('Phanerophyte' = Champsaur_PFG$sp.DIST.P$mat.ALL
-                , 'Chamaephyte' = Champsaur_PFG$sp.DIST.C$mat.ALL
-                , 'Herbaceous' = Champsaur_PFG$sp.DIST.H$mat.ALL)
-str(tab.dist)
-as.matrix(tab.dist[[1]])[1:5, 1:5]
-
-## Build dendrograms ---------------------------------------------------------
-sp.CLUST = PRE_FATE.speciesClustering_step1(mat.species.DIST = tab.dist)
-names(sp.CLUST)
-str(sp.CLUST$clust.evaluation)
-plot(sp.CLUST$plot.clustMethod)
-plot(sp.CLUST$plot.clustNo)
-
-if (FALSE) {
-require(foreach)
-require(ggplot2)
-require(ggdendro)
-pp = foreach(x = names(sp.CLUST$clust.dendrograms)) %do%
-{
-  hc = sp.CLUST$clust.dendrograms[[x]]
-  pp = ggdendrogram(hc, rotate = TRUE) +
-    labs(title = paste0('Hierarchical clustering based on species distance '
-                        , ifelse(length(names(sp.CLUST$clust.dendrograms)) > 1
-                                 , paste0('(group ', x, ')')
-                                 , '')))
-  return(pp)
-}
-plot(pp[[1]])
-plot(pp[[2]])
-plot(pp[[3]])
-}
-
-
-
-
+
+

Examples

+

+## Load example data
+Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
+
+## Species dissimilarity distances (niche overlap + traits distance)
+tab.dist = list('Phanerophyte' = Champsaur_PFG$sp.DIST.P$mat.ALL
+                , 'Chamaephyte' = Champsaur_PFG$sp.DIST.C$mat.ALL
+                , 'Herbaceous' = Champsaur_PFG$sp.DIST.H$mat.ALL)
+str(tab.dist)
+as.matrix(tab.dist[[1]])[1:5, 1:5]
+
+## Build dendrograms ---------------------------------------------------------
+sp.CLUST = PRE_FATE.speciesClustering_step1(mat.species.DIST = tab.dist)
+names(sp.CLUST)
+str(sp.CLUST$clust.evaluation)
+plot(sp.CLUST$plot.clustMethod)
+plot(sp.CLUST$plot.clustNo)
+
+if (FALSE) {
+require(foreach)
+require(ggplot2)
+require(ggdendro)
+pp = foreach(x = names(sp.CLUST$clust.dendrograms)) %do%
+{
+  hc = sp.CLUST$clust.dendrograms[[x]]
+  pp = ggdendrogram(hc, rotate = TRUE) +
+    labs(title = paste0('Hierarchical clustering based on species distance '
+                        , ifelse(length(names(sp.CLUST$clust.dendrograms)) > 1
+                                 , paste0('(group ', x, ')')
+                                 , '')))
+  return(pp)
+}
+plot(pp[[1]])
+plot(pp[[2]])
+plot(pp[[3]])
+}
+
+
+
+
+
+
- - - + + diff --git a/docs/reference/PRE_FATE.speciesDistanceCombine.html b/docs/reference/PRE_FATE.speciesDistanceCombine.html index 54262e2..71b7515 100644 --- a/docs/reference/PRE_FATE.speciesDistanceCombine.html +++ b/docs/reference/PRE_FATE.speciesDistanceCombine.html @@ -1,68 +1,13 @@ - - - - - - - -Combine several dissimilarity distance matrices — PRE_FATE.speciesDistanceCombine • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Combine several dissimilarity distance matrices — PRE_FATE.speciesDistanceCombine • RFate + + - - - - -
-
- -
- -
+
@@ -206,150 +140,142 @@

Combine several dissimilarity distance matrices

species, combining several dissimilarity distance matrices.

-
PRE_FATE.speciesDistanceCombine(
-  list.mat.dist,
-  opt.normal = TRUE,
-  opt.weights = NULL
-)
+
+
PRE_FATE.speciesDistanceCombine(
+  list.mat.dist,
+  opt.normal = TRUE,
+  opt.weights = NULL
+)
+
-

Arguments

- - - - - - - - - - - - - - -
list.mat.dist

a list of matrices containing dissimilarity -distance values between each pair of species.

opt.normal

(optional) default TRUE.
+

+

Arguments

+
list.mat.dist
+

a list of matrices containing dissimilarity +distance values between each pair of species.

+
opt.normal
+

(optional) default TRUE.
If TRUE, all given distance matrices will be normalized -(see Details)

opt.weights

(optional) default NULL.
+(see Details)

+
opt.weights
+

(optional) default NULL.
A vector of double (between 0 and 1) corresponding to the weights for each distance matrix provided in -list.mat.dist. They must sum up to 1.

- -

Value

- +list.mat.dist. They must sum up to 1.

+
+
+

Value

A matrix containing the weighted (or not) combination of -the different transformed (or not) distance matrices given.

-

The information for the combination of all distances is written in +the different transformed (or not) distance matrices given.

The information for the combination of all distances is written in PRE_FATE_DOMINANT_speciesDistance.csv file.

-

Details

- +
+
+

Details

This function allows to obtain a distance matrix between species, based on several dissimilarity distance matrices combined according to the following formula :

$$\text{mat.DIST} = \Sigma (\text{wei.i} * \text{mat.DIST}_{i})$$

If opt.normal = TRUE, two normalization steps are applied to each distance matrix before combining them :

-
    -
  1. a non-paranormal (npn) transformation - (huge.npn function) to obtain Gaussian distributions +

    1. a non-paranormal (npn) transformation + (huge.npn function) to obtain Gaussian distributions for all dissimilarity matrices used

    2. a range normalization to bring the values back between 0 and 1 :

      $$\text{mat.DIST}_{i} = \frac{\text{mat.DIST}_{i} - min(\text{mat.DIST}_{i})}{max(\text{mat.DIST}_{i}) - min(\text{mat.DIST}_{i})}$$

    3. -
    - -

    See also

    - - -

    Author

    - +
+
+

See also

+ +
+
+

Author

Maya Guéguen

+
-

Examples

-
-## Load example data
-Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
-
-## Species traits
-tab.traits = Champsaur_PFG$sp.traits
-tab.traits = tab.traits[, c('species', 'GROUP', 'MATURITY', 'LONGEVITY'
-                            , 'HEIGHT', 'DISPERSAL', 'LIGHT', 'NITROGEN')]
-str(tab.traits)
-
-## Species niche overlap (dissimilarity distances)
-DIST.overlap = Champsaur_PFG$mat.overlap
-DIST.overlap[1:5, 1:5]
-
-## Species functional distances (dissimilarity)
-DIST.traits = PRE_FATE.speciesDistanceTraits(mat.traits = tab.traits
-                                             , opt.maxPercent.NA = 0.05
-                                             , opt.maxPercent.similarSpecies = 0.3
-                                             , opt.min.sd = 0.3)
-DIST.traits$Chamaephyte[1:5, 1:5]
-
-## Combine distances ---------------------------------------------------------
-list.DIST = list(DIST.overlap, DIST.traits$Chamaephyte)
-sp.DIST.n = PRE_FATE.speciesDistanceCombine(list.mat.dist = list.DIST
-                                            , opt.weights = c(0.2, 0.8))
-sp.DIST.un = PRE_FATE.speciesDistanceCombine(list.mat.dist = list.DIST
-                                             , opt.norm = FALSE
-                                             , opt.weights = c(0.2, 0.8))
-str(sp.DIST.n)
-
-
-
-if (FALSE) {
-require(corrplot)
-list.DIST = list(DIST.overlap, DIST.traits$Chamaephyte
-                 , sp.DIST.un, sp.DIST.n)
-names(list.DIST) = c('overlap', 'traits', 'un-normed', 'normed')
-
-par(mfrow = c(2, 2))
-for (li in 1:length(list.DIST))
-{
-  tmp = list.DIST[[li]]
-  tmp = tmp[colnames(sp.DIST.n), colnames(sp.DIST.n)]
-  corrplot(tmp, method = 'shade'
-           , type = 'lower', cl.lim = c(0, 1)
-           , is.corr = FALSE, title = names(list.DIST)[li])
-}
-
-require(foreach); require(ggplot2); require(ggdendro)
-hc = hclust(as.dist(sp.DIST.n))
-pp = ggdendrogram(hc, rotate = TRUE) +
-  labs(title = 'Hierarchical clustering based on species distances')
-plot(pp)
-}
-
-
-
+
+

Examples

+

+## Load example data
+Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
+
+## Species traits
+tab.traits = Champsaur_PFG$sp.traits
+tab.traits = tab.traits[, c('species', 'GROUP', 'MATURITY', 'LONGEVITY'
+                            , 'HEIGHT', 'DISPERSAL', 'LIGHT', 'NITROGEN')]
+str(tab.traits)
+
+## Species niche overlap (dissimilarity distances)
+DIST.overlap = Champsaur_PFG$mat.overlap
+DIST.overlap[1:5, 1:5]
+
+## Species functional distances (dissimilarity)
+DIST.traits = PRE_FATE.speciesDistanceTraits(mat.traits = tab.traits
+                                             , opt.maxPercent.NA = 0.05
+                                             , opt.maxPercent.similarSpecies = 0.3
+                                             , opt.min.sd = 0.3)
+DIST.traits$Chamaephyte[1:5, 1:5]
+
+## Combine distances ---------------------------------------------------------
+list.DIST = list(DIST.overlap, DIST.traits$Chamaephyte)
+sp.DIST.n = PRE_FATE.speciesDistanceCombine(list.mat.dist = list.DIST
+                                            , opt.weights = c(0.2, 0.8))
+sp.DIST.un = PRE_FATE.speciesDistanceCombine(list.mat.dist = list.DIST
+                                             , opt.norm = FALSE
+                                             , opt.weights = c(0.2, 0.8))
+str(sp.DIST.n)
+
+
+
+if (FALSE) {
+require(corrplot)
+list.DIST = list(DIST.overlap, DIST.traits$Chamaephyte
+                 , sp.DIST.un, sp.DIST.n)
+names(list.DIST) = c('overlap', 'traits', 'un-normed', 'normed')
+
+par(mfrow = c(2, 2))
+for (li in 1:length(list.DIST))
+{
+  tmp = list.DIST[[li]]
+  tmp = tmp[colnames(sp.DIST.n), colnames(sp.DIST.n)]
+  corrplot(tmp, method = 'shade'
+           , type = 'lower', cl.lim = c(0, 1)
+           , is.corr = FALSE, title = names(list.DIST)[li])
+}
+
+require(foreach); require(ggplot2); require(ggdendro)
+hc = hclust(as.dist(sp.DIST.n))
+pp = ggdendrogram(hc, rotate = TRUE) +
+  labs(title = 'Hierarchical clustering based on species distances')
+plot(pp)
+}
+
+
+
+
+
- - - + + diff --git a/man/POST_FATE.binaryMaps.Rd b/man/POST_FATE.binaryMaps.Rd index ea8682d..b0ecf48 100644 --- a/man/POST_FATE.binaryMaps.Rd +++ b/man/POST_FATE.binaryMaps.Rd @@ -49,7 +49,8 @@ Two folders are created : \item{\file{BIN_perPFG \cr_allStrata}}{containing presence / absence raster maps for each PFG across all strata} \item{\file{BIN_perPFG \cr_perStrata}}{containing presence / absence - raster maps for each PFG for each stratum} + raster maps for each PFG for each stratum \cr (\emph{if pixel abundances per PFG + per stratum were saved (see \code{\link{PRE_FATE.params_globalParameters}})})} } } \description{ @@ -79,16 +80,18 @@ production of as many maps as those found : \Leftrightarrow \;\; 1}} } -Binary maps per stratum are obtained by multiplying raster maps from -\code{ABUND_perPFG_perStrata} folder by corresponding raster maps from -\code{BIN_perPFG_allStrata} folder. - \strong{It requires} that the \code{\link{POST_FATE.relativeAbund}} function has been run and that the folder \code{ABUND_REL_perPFG_allStrata} exists. \cr If \code{method = 2}, it requires that the \code{\link{POST_FATE.graphic_validationStatistics}} function has been run. \cr \cr +\strong{If pixel abundances per PFG per stratum were saved} (see +\code{\link{PRE_FATE.params_globalParameters}}), binary maps per stratum are +obtained by multiplying raster maps from \code{ABUND_perPFG_perStrata} folder +by corresponding raster maps from \code{BIN_perPFG_allStrata} folder. \cr \cr + + \strong{These binary \code{raster} files can then be used by other functions} : diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index 722e0e0..2a8a7bd 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -13,7 +13,7 @@ POST_FATE.validation( doHabitat = TRUE, releves.PFG, hab.obs, - validation.mask, + validation.mask = NULL, studied.habitat, list.strata.simulations, doComposition = TRUE, diff --git a/src/libs/iostreams/src/zlib.o b/src/libs/iostreams/src/zlib.o index b882b7784ab33a1f4ecfdc0e9c0bcf5b710e54ca..136ee47a373188c3ea88a26fcde484b7fe4173f2 100644 GIT binary patch literal 184896 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 literal 244552 zcmeFa2Yg)Bu|Iy#?rL}CtFo44S;a-J7+mBkcU!hxBzNP2E3(Sk7RZt;$%SfD3k~JqY=QPy+scXXci(yLUlO{POZXf7ZVDoSFH~oH_m6 zbBnf2oi)v;lwtprF~M*tn`;V;qk0I0)MtM;{!0aSX;W1jkSu!*C48F#<;k4#FOSqaTh#E&4Eo{c#Mi z=z$0a;V8D~2?!_RD7EOx2+OSabSo@JSYgFySm8{Bv#j`RE1ZLHt`(nWh4T>}Zp9Z^ z;X;IqtoUMtM_BPC2$x#%WeAsB@f8SHTJa+hR$B3O2pe$dzrSX7`({zw-xid&eOrFY zC;e*6w+H%Ad6c(J>76&^!9NqWyzNirZGR}=LFLAR@V-EADm;;HPa%HW(M9E_JQyi& z^OtXjYHj&2F|;96e#)c$malw-!8<8EO6-9qTmSL0FpT=k_m!oQ9fUa)E`MZaX7^Hq z@a5YlmT%kNn)V^_GrMo4;E|oHGrLbQSYFZgWcl`t^6jDWL!T;d3%AV+S8VTCap?2q zZS(S`w3QYCJ1DdJsiemENk>dtJZVuy+utTFtY~|Ak(Kvw`S#+iTf)Yy?I+;akk?vp z>XREv%eN1m()PunUu-`v!Wn2Qi?o%cm2aPlAO}Iewz9n0Z6_GB+lKc>M#IZjPFgW( z<)kAgRX)Q0m2WSZ()P`vUwr%<*I(qv%8HY}7@8g0KJnBSTk}g#{jzl&C%msLLjEs* zMC?GYAv;#U>BX{E?~JFZJSQz>{(|sq0Zg9z13LKQg(HPT8+E6XW;51Uh+_97%Dv|(oX_A)dRYvBE?ylpC|vVI8rZ7&<#HWe*O+89+^ z+ah#;d8M$v7IB#4&H${8TxfH%cB~8UpO^RX_2g?s+oQAEKC@dkhjwR1ej=ADSiV;I zZ&JqN6qS(^^K#rGBwP( zIbjqsWM@UPPwkAej}chQKB%&C=pmTbHg%q@^MlzRY*hogT;9hwTUjR`#KmyZBDjb0 z{jl=yQ^to}1a>KCb&Rns5;Ks7?MV~Wjv}@^tY|yFH`jYbTcEe?HF8>kX;!azqN(OH zPwj<1#KQ!NjM_j8tWD{SNLfFM^h5nmLa4WZing5Ib_=LzTi82nS^F~c)CgM(mbZPd ze;!JDK=1NxpJtwV4l;x}4AbvtD4X)_!`UC-5`(w=IpBif%yBkW z?%%&(hUAKUSi8VE68U2lJC5%yt)Q)}ReYwml?s|}O6+2#g z%(sd2$q7zpcV{XZy!9idxMb8j;POHj`;nQ_$zDwH$$F5=@A>5A_qtuA2Qu+H>AA(J zt*_fxo)~Mq*Dohn>zAnqT)dvMO7bxkGmSQ;?x@Gu$&l!PE-=-n@o_9rku=E3-xig6yxqFk|9!7P7#FjNLinHJjY#F!?+v% zr^elK8k#s=ExEE%afFS)Ar(yMkHiQNujmWvUafaQY{m@n}R{rk- zSPS_79x%50XbisJ=f6B)yx{X+7cl1R!*4uv1%gk>}Altsa#&#f@$B>QYt~r{E_l~Q_DH- zy%yXzbsh?Ke>oP8wEp6}q8)KB;1#R%Cc7A%BujZpEN|OazOS^1?e8n?N8!FwswvE! zY=S+&)sC|WJp!Y@wFsRqi%@HZD|co%Z;nxs`~oInyEiv z!`)Fc44-Y#3Vq>NhL3j>VtGV)r9WpuaX|hIPW0tJne!F3d;EUP$Ik`! zqqrZlW6Rr{?(;vAuH8NYF#IjB|G@Dbj&E>$j|1}-{|INtsR==}KUL$++G`kO!!W`Z zF-{2P#GWozEv>c2660pn^d$_<$WxO}D3EIXiVB8)lE@7?w2^D2 zcw-j9!NQ zePF>5>BEln8c1*fFZOv~+(@k&QAJr0=x-THkQZ>gMo>0X1Y<_(t1SH(+%HMOPO=vw zev`!=Q0AR*XkxMbM;4!6Wf%h?9!3z&$@f`GS(pjQa>qb_DhVx-4;j}8`34(fNb;Yv zqz&9%wnP-mVbn+sDwbal+1)X6^$WULAfWC7dKQ4UJwQ_F%X@tenFDM{O*JiuMh_pg(dDQ;*k&!hiD9mxfn!yjEE`23m~eo#herM7TwYiPp1fQ zGXOhcL8L24F*(~cc@2J-CZ$Kj}*+ekg2OsZk&)5vjElz zpbPrFH=s!X;~fC|%XJ_YP7%P74uE5HvRZ8Ql;2Q`74+$XKFdLKj7}E1HvBzq?pWxv z1bvT#<~-1e?;aVcI|cBT1E9Q1X?6q1NWDM+`*4%#TLQ87bh7H@N|8rWQ1v*Z7mP=3 zTk@+#J{YnYj@&V#6{l45^#ZR1v@sEGbfJV(e=Q)j4G3C#Nqehf*?pGm3h-~TWim#J z@}m~uVF;gb0PL{sjQ0LeU$w*q~~f@TD9XAXy(d##)V`nd%i4cLMtP~AS+(Kpzi_pxdSbuN|nq^ zw*W!-n2SU5U42NbnVA-901)FG6qU4|*jx*BI1tqiiW1XN7g(sxK%8QueAFrpR1w<5 z9syL4-W~w1vq6fhRzQzxpoal?#sP7c7tnJW=mP-0a6sH?1@w*vN(&oCXB<)rTucJ$ z?uRPwg_2K002t?hsHiN*CkTi;{bB%C*dR+jP2}9oH$rwi$$11_5^bn8Qs-#;7ecn% z(HHq*P5%za?sN2elXI!72QTXf|HR`;0TV|37fv4%xcge*1RNbv+0Oy56)<5`Bo!?I zhj=E&)@#NFLsmj^HYW08HT`*zEsoPaMdY0GCdf9%$+v6roseA^C*P&nzXh^8;^ccY z`SXyy8YjQTk|X!(bI87oli#GtBOQ=`9D4q5)#QUAD{BZnU2YV;Qk0;)!w zjwKDJh>Rswtww68X7zl?FCz@)PpmR#k!I&k$RCciBkn~t(C+~FlMS-++N34)50YV> zuGjT(ntm6^dXijg>!)e@;~<+Hr@u?&JXEZJY>ln24_|vU&`AKC<$$!|>lzJo4FI<~ zAZ_^iwFY_`fL9$*wL3CBsewKR;9Ccz4XJNxpib$AaTpG%B_8NMq|QSps~q3g;8Ory z=)j$+%|iZFLpA_)ObpTj1ybA*_Iv=YazLE0mg5W!c|TB3I7rU1bcYH7skb5g&;e*x zmS|S|8HSO8!_BSb)LISH4}f6~NShnBXrS2uR68Kgayv(Z9}nnR4m`fxF4vIP0Cj5& z(sJ<@4fHesZ#kg&a{H}@{2Ne4rk!K0rTAzeU>H1y6#!7|fOznZw>eLPR{**+7OojS zT9Di=T7WvmL9$J)9iFTq_W*TgB9hyo-WV^@bY1}d{aBs&lD$cT8`wXJ;BW^dSv1RN z@~DP945*2*$oTSjRp4BFO98EO;1rYVH5nkAx>14pB$e!t+xT|^xYtqR1n6W{cV9tr z0`~y*n1iGQEYN5T^arp%ivcM{N;jD$NR`sjFiaf!Ko`}V>I2yrTke*=KBOI|!50GB zk_7H*Ti)){6n8-BS`Wqe3cOjzg9_S1piokYjVMXm^-*}d20sGOH4faF2`$G~YRHp-I@dwQ7g>Xb zyb-8-9VAWWdg{k%$X9@R&q2ngzDGmu2P!Amt_CM{-SCYXd=Q`$65&?1?$eM5AU&P^R)fC<=*Nk0%h}8h+<3ThQ#xbM4~I0ycxSt5 z$YP)-I>>luCuzu~Ks7i>PiN<9@G}6tI1z3+TcaU=1=Pb1GTzzs8uE8Qec~YFo!zA& zgSatRh(ij%)7dLE_y|C!Cc-UeZ_|*KKs7qZcxNBckZnL+;vnOleP2WV8mK26q^Gn0 zPlNvn&@U3ASqi` z!l!AVZ2F!Hb3X^67zcw ze`fYsM)oc>XL7jPT+87ea~Fq~nYVDb*WAb9mF9;WUS)PzPI^B#`*L`VIh(`l&1Md7 zGB4)v7v}vO-eSJN;jhfEIsCO*u!8h&Ge>ZEyE%`;JItdwywg05!@JBYIK10zTuJzQ z%xxUrYhJ_QedgmF-fzCo;cv{WBMJ9_IgrDL%()ysVjj)mqvm!FA2V;_@Nx4Q4)>X# za`=Q9s3iR-%|0AHWlrVrX*0^7Uk3Wl&-FPwSyFsFeGs z^;Q{Fy#3SqsSHs+RAq?zVJd^vvE_l$D6qUkN;kteiip6GbQNr<@4KR}*8pM|!FLev zL$N@00R;peM3MC2>HE7|cX-3;bIrei2(w^*`rdJf%w=K$F14^10R{N8_<53~(+1ftTP`;QM1HEsuZQe5TdpWsW4B^Q=#tqmc=z&I0A3~#7ttP( zU&02?Cr3YWWJa(r3dNsZ5eXGR+n+wVfEY)Usr0E_wNw_0nEV2kkr7l~myAV52@*UE zLasBFIXZ1Sh>|Thk7E-K-z5+l#gz-2*!Av} zl^Zz%+ruGtY<~cvJ+`k0?im~};qYDVZCe?^oe(>=%X+xBQ;;>=@9)RN`keyYp*V_h z_^z%pEWZiHep3W~e*)WmDVE_=1;pDzMCSde=@KzJq)XC~Ssl)TDei`mS^Wv6wvp}6 z$hHw&gZNfO(a78pgjD>Vo6&J-y4d%tF7Lzsc!H>`MsqqQ)pbiS(HM#wO;(#Unzy0* zE)L(oYRE)%mWaLvI{-Bn$sMZ86xqu<7Z=L7;D?4%-GMkP7KLd8po_)ISX(A7)(Fz9 zE}6_8+F?DUoXPB=JgTUp2!4GvSqRQTTr!zGyqJ&*4}y_I<@8i(8+u!Jc60j64aZvu zaT`?CJvd1@=k!)RXkif~;mInx-pDByG_|}xkp_W9<78FeYUB)a<-T4qa%$*C&RCJt z+U*dMKS=s~Do4)vbw(p+o`8rm*#=pnWg;StHMU3*P@IrN)(YrxfX*TiMG_LR?zfZd z)})}#uBBHU5GO}PV)?XHK*aeAfqn~?}ayZgFg2U0~OOr@G#{2_^W6h5_9B2NW!wKf)jGJgaj&|bjG|9Y*`IF6G za#&{G%i$DrABWS-8pcmI8#ydDkK?eyJe|Xtrk}%EW+sQT%|Z_6n1^sU&%BYt`R4f) z=8iC5tfsKU%&Vnvq&bYj&ei7ow4Cc)V?M`p)SMP2dbPQf!!>3jhilE#IIJ`GaCnsY zYYvY#pW?9I{Di}GX4-1fUvD15VY4}#!&Y-0ha1eZIoxR8!r^9fABV@7Z*zFO`4NXF zn#LNkbBdYG;i=}K9BwnGaCo|T1czssjT~+_FW_*8c?E~(n0F$y`f=XrDvfrd`chlS zJ4dC{HcvMu?vaME0-`n?>SJiBO9?38GgUf`Bta4i=Q5Q}?ON2IaMhoI*i~0VS{fIt z^pnttMstEMi^jJOnv#|j#pG84ARlt!v-%4>vQkrWkDPoN;)p;XQ)j7k8i0g2Po>jE zk`OnmFy#RM4TB;wSV!`U%|-pJyU_V11%y{T+vbnSYJ_pQKb=1!k2|qQ#%8xc5-x(& z$d{S)PB8Z2@XcKd0~Aw)v9w2uT^{fAC-c;+K7{x)TSJyG`O`yWk`z@KPG#WmErLNy zJd?$2{1Au+I^qVpVV^(O6;Fd`mLo2%oGSf0NVU)F1wQfgS}?cA6r`|MQPQe#b+ALQ z37S_hUDy;XJ`R*J{9P93qzyw;>~ciTP_Sz^&kt#BEKSE|azLyh3{oadn?OOwYC zGlqe67a}|tMS|yb7=4&w6j7SNT`3fN4dH$qzSV0%6%i|V5lgZU#X~SSSfcnUe!Yv? z%b1anPfv=umNCmAud*?+4hsH?C1mp$NVbr46WQifQt)<`QeqcDvL{Y@7fUA~AGbqt zZ=Cc#me%&e3J8)n9O+eDu)zm-u}?$y7Z82rh$}ebkLQw0hF_?CT~@(`u)mRIySbVN zBH129H(FF-c@T^E>b94?eigRU#gYnhIk z4|BNI{5^+t=3fz}l8J8BovuT@2LB36{_f+<_feJp?&HlbIGkWUvx4YSa~m3izxyQf zTn;CjH*r{IKEdG>^IZ<7n%{6Z&CG2g{B&~wLRk+M{VMIE0amI-cl4)J*@L1x`_sH2 z@w@u>Kxc;tx4^-)Rqs!G(NTR!Q+>EU4WOd>NdH4@Rbm=E>;*}WuV8yTlYR>#ohyeJ zJznZb3A_eT=gOf*kC&Oeou$K!9FvSTPBALHhTOHt1h(1Vs;CD{hsMeq2buuOy>&?&;G!4 zFQMVuJ4_E08ovFJ>5)Rixj!*INoaWYUrf&w8t%Qz^dh0*-+N3~GCdUjz0Y)=(D3gA zrdx!De}88B1fk*IhfJR?H2nLB>2rmKe;+e_snGE66Q-{g8vcFC^e=^mfB((&-9p2^ z&zOD$v{k!Qqf*cJII%~a*t1UTbtm>GC-#XG`_hTg=Rw3?2Pc-}#JV}Leokzt6C3Bm zra7^BPHdSIt94=xPHeLiJK2ey<-{&@Vtbv~4NmMfC-xh}c-Sxp;|T*EHUi^hgj9cq z?5{X{g*!2P{H+B=$QN>f(E z$F?#Glu1{?*9+A}O*%0Hr+cB}H=i(N|MhiK_FezW%$-F1cTL%Eeb1Er)(^~e%>T1V z`>r>r+PaN~`3gHg2Ko+x17k5t^9lQT-}C3U4alIS zo4?n<3>xBK1IoR|m=6xMroUeEctTaj!t_=gzRYSsc+%^&KtvaSz1J40POf~Z$Zv=2 zL0c|Y-n}WEUK0!WPAUdA5Gxo=IQ2eGe<6iJ*isaem13_+1vK6&(Jqx`qiD;{+V`a0 z8?G1Q5N(QC+LH@t$W@}S+D&c|AW>4FtSO2qT8Ud);|r*OWTT?jCdtw1z+B{Lu+O9> z3(;P(1;%!60rw69Qch(1h!)^zKJYXR5x6@(jaH7h;f)lh<otE(UmatD4is~9aJ_E zKajlZ%A7)qbMm}k5Ho#-cA%%_{Rpqa6$OQLR`{kB(#6c+**HnR=~KmN(#6K>a6tBS z=#>!c>DG3n;QtYuH+Ja6-zPHqtSKZBtqc#RNS~wP=yh>)Qyksm&^!4=tw*hgu}Ot zv|`)O9PpRgGPQ~TPW!pfEi|kB5K^6l)9IwL+C`)N+~T6G_Cp}`OPp?Z&@t_&9{TDz z$X+2iI~UV__-&fcAp6Fa>s4>HA2yOR$}kFW=tive!$yWfHqn;rMy&S3`C1At2F-4InK6*&_cm6 z^V@&c%%~VM+0GPqfZj|V>v}UjZJA>x`D{wJPbA}W3MtN+;`<_2rpBjT(GD}WV=lMN zP@K*1V1KicG9I5M^5m{ut+_&Rws9FESfGWutpsVB=ZWW#7dn%ZJ;OZy2PyswRTMeo zaup#wt^Bre!iVft=|qKX`U;gvrG#f^Eu%dn0aE#;z;96g4kW&X$)2*u*HbnUd&(2x z>_ERhowfkiQw2#`SbaMm36{hUDIm{#%9FA|iVMGAm#7mzG>6s=!8>reRRvP&8d{AY zaRhl-F0ErEJ|N%8dB1^$lw08s&uVcnBeG9(;w9PVIq{J>H#qT8*_4>*j?Q`6iI2&C z$BB>4`NWBrXSwNCXz5<8`EyC3i`lKk_ZATUS9n@9ObsDObGlVrL1Ks(&$1<23mJ!+ zD(4;u)-%!Fm-!BeP?+?D)k9cVOJF76JA(ugAIdv65+A9>$8x-95nmQq_1rVS)!j49 z!HV#Ju`H#hR>EX>z_?Ui&6PDr*BJxGGZ7GH2TVwr2-u3qfHKy()X|xeL3uSqXDSn6 zOJ`b2i|EXDE>0w!IoZ@MEuFbc*gEr4u7yr0Og$+`!xop-z)?eGEp{F!UO3x(I=ypl?W zt_b+DK&}l!;nTtRwKBQz7WlZx_Y#IqP`n1gZK@lKm-wE0<=m8?vLG=ZIa-z%`xAUdqfQ9}Hs&abB zh*Oa1P!c`X$8C!qJkk?g?9Zecr{|D3bv_xQ@-si@o1RT#6;9A#uZFH?;(ikGi7Hg& zq3|Q%_LPrA1t~7p_J}olvMF9=HkacBe|lCag!FiYt^tj{^u7e)#(bLa z9+Rz2v9V93PbUD4GUDlg4VhF2)x>2dsRLS~TRKvKpBy6++nmV(xABehD?Q|rrHEwd zZW~~g%})0I4dPOFRve?epy51)>`MtQ;FP{`P*hS@Q7!X#qw9UzB+#z+CCv?rS+sGW zwUPl(eb*_s{zZrdW6QfcyvwN8$tX`#JGJ`K3;LNPfVH4=w28#D^6OcjChf zW;yXOxio)?&2iaOxe_0*#V2U->3PoNHe8zuhR<-Ph2b-`_%baX?X<*6cXe(p;@G*v zL+ZKap2Zk?N;b&dXHljE^_~pQ&l?Mw(eiDO{LTQWL`?;jm!#P zH$}coY>dp!q8?`5xf$7+sj%F~8JSl|H>V>OmCsa;zle%#WY8$Ov8ym$}yck*6;F8ju@pQ-(-Zvg849A9Ezd z3yl{+7{R-dzh*KJ_Ft7stna27#$AdygIaM~Kr#Q-PVFdsMu=b;faT-xwE)F+ZQQNc z>1(jPhWw(N0jmf*2DqsX_82mIk7}U|URyv|;CzIu2Bw*CoZ~HIn87Us@?U3T{e(Rm zxLvkQ%ggH-LHkp%7$DSbK-?3Dx`A)AQNDpnA=H~dyyKvja%#WeLi7EF*yN7=)Dq$E z<>CqZ&rzv9w5um@(DcARbqJ`TIDGTT{}q&3S2zWN8Me?k-7RKyAIJUYE9_0{IhA@#X#Be4ugc!+0rVwM8kU|+do{5ekDP>F&6 zR51@k>K>Eiv~Ke~0{N3R#!`R01FOFc_E)w@?WPS4UXWc$FfY(tONjlFt>8O1WJrgIUPa%k_>V1elal}Q$S1)#;fHtMN zg35)x_>>Y&nT->bv0T!rSF<$YwJZXL`65ico<&~L2Q;AZ1``n>-przs$r0i$Ci0A6 zbp?RjUDN=x7n142nxhY)fVzy(W8&zsarC%2x-^cS5=T#sqo>8uGva94Z`b{q6-UpG zqvyoYbK~fFarDACdQlv`*rB;AsUvydk-ILcnhEKp3J=nu)MPYJwP|$I$AU$L;Krba zWYP0}5+C02HdMF7i#7g;$V(1?a7S8%2RovMtksul2tNs`2n<85=UzGm^g0~A?bLjz zx;;;B>z2lFylfB%ae{0R2yr44R@2(Tgw?dRGGR5XlRA(|t7)CgL?F*qn|pn z3*Caf8YBEVeo{oabJkZt`62M7(Uj1c6kK5^2#OrxLo{DJqR3=;8V9Nb!buK5>-sym zIJpRxLB5JGUCH>PqCX=@X4Gcz57m^>n?}Gs|+!1(ZTrrvb$Cm-QL&3Wq}nyu;zd0UvWXYrvNr zE*+rO5pMN>G=zxsRH9IGQ5K%tWY@$%j z_=`$?7xE|A)frLQZ2dhrzU7UCVED{InZss|?nFTUtbP&4% zPIKWYSJS+nna3mUpP$!-YSlkKzspX9#<0UrWDqMYNKNN(VJCCg%<)~wPo@_YWDZ-@ zyVGtM7&d!e7xI(&i^pb4Z}-2Qavy~2aTnL^$$tRnBOE?n`K=?QzCO(bZTSD3N`cRW zW=9;1HJ;>B^dDk9uR*4L10fw{BcxAaQiX*gwS<=5VWE1cZnVmX2-Q=lJfV82PBc0c z89}nxOxjC)Yl>;r`rQIlFhz{P)X&3%2{HIcg3#{X$SqIkLyWf5DO6 zPBPA#Z(|2i^}i3go@f3&_ z#)#Fv7N~&*6X)Pwj=T2X2rpV8IF|4)5sD7O_p>YtaCM*vaUrC;9mJVj6=7dPn*Sv* z%bF?;UsEm>gV9J@zV%#z`$(bJMx1y+Hny`G0odk85dV!Jq$rT$sRoazK}xfkkfW@+ zz;}o3x}c|lq%I;(d6z82_>aT)Hf*U6adPY8aPU_;vd>5sQy0yUo#4n5>*74f_Bir{ zy0{gh`y4Sp$fVcB^ANogBTilyKH%sNG6iw?z9mQL(CdN_Jt6JqAhf!u1kz97gW+lkmRl8NBD?AA)3ES7i0dWh@s8jcR~9RvM>=?HI)?H zUjGUN?>NF>tndp6zOseJQ`|_77|P9$uMKe7em2Ca0Ld2N@XaKBZE9xeaFS5kR$~hi z7h@7KK$`?dQ_OcIFW>4xTP;m--_eC_BBZR#Q)y4LL%zQ0WI-M2g^1ukp7Y7dCYZ#t zJB*LT96Z3?a%)}BQrsSSpi`APD({grzSSv(1d)*{N1rx18%rKjfh0Q|D&Z66sTZNdD zNn6Ax3o$o*KCGQ0#Qe;wK%61O()8nDV5bnvJO2Wbi-lN`e>aG|LabzWt`p+ORI2Y= zgsAL5JN|bGv5I|rP>AaA12FKYRLPMct`d{#yK;h3)FkMoYFUs9`&C~iv5SaJC)`F@ zfNEsns;!FerqGMma$AMhy9^WUcSW1t$X0ZZK}?A38@aPZZ)wY23Qcdk$mq>&R?$3K zwN&n4(c9c2d!rQlZ$>u*zsjxh>>2q%uX*bLN%b}Hh~E7cXXH*!C}y{Rr9vSq!%PIn88-eZ@V5P+oW^K%2}OG0W1Qfe$) zRWIhJlZ;ykDJmk7NxBmum2$hA2E7v>4;5Qu#n#0H;JbQ`t71@0-RGLRpO9}*f_&ms z#nfssMK6MDGBI$sYk=Or*W{vmmsAM7lP@>WxY=@b@<6=mOZJZ5)fYD@ak*Jlc@*EW zq}TUdzbw%yETT91<3+13W;^tfe}ch#xqZ=l|C$#2SvlFyvVD%4TU^9L`5(0si!If< z&KOlC1djk!soH2;)q#nCNK7WeLZmSv7u!|oOhiPI!Gv6FSIK0MCz1$Dx(Lyci6S9# z(&&PzpAelgsZ9?yg3rKFF4W4mIWzP@1J0<`dP6e2(Dx18qLCZDqPC7pmA-r6mX9W1 z^#H5U_YmUsrLzAam7R7Z0DQNUV|wqS+Lg2(U>E6I3T|3f$2>;yZQcq;MlsX?HDjr* z6BXa$q%SXsHY3FF8yyKpA7jv*WVz_xr1(xTeWk(G)ds}MTjd>V`g}t?KvFoJU8m1F zXem%GE$^mCf1!l~cX5mDlYy%bv8f#7#CmLZ)gf$dD#^LWGDwq8VRkgB{3ModbL8CB zT)$Q=5WndA5}dX@$e-%=VfyBTB^!?X7?oFarGPx4&rpcVWLKUHuDrZ6iOE~~I)%8c zi63*cb645gWXsZi#L*r>Vkgs<_(n&2p`)!CzuwVqakMq@^N#j;j<%NevyS$yjQ;fP+w&uET@-pq?3~JEED)2!-hmSBN=5y4M&AuYskkb8+IWiH@U8 zdRBi}$1!<(!GoNhr_T)2TMHpRXY$imnfxpfo+tt+GfbZy00;3YG>*)L1%3!f$@5gz zmuE}_i!TDI_wt}6@hiA%OZ*xQ^9v30OUA6Hyj5pOm&_VDjJ%-_2V`yJq;KK#{WxRT z;j?hQ?`18ySJey)=V9r`LvW2OtSv$o)-vwbOyJe84D~frn2^!EW-1dhpw~=eLI(7j z=}Zh3)8$Nz5~6~MQXytAQ7*(xF2#95%wl4`5ObI~T!?v0ED+*wCKd{@kcmY?EN5b| z5G$BCLWoKxmIzVB#8M$@m{=x6l!@g+)HAU{h;>Y?lv$wWSbmU+UR~#QFL#DlQQg#> zkUJ4&G1hJ>{&%-R`$>6NMR^^GFzi- zCVr5Nt`E3Pk?a@{7VLUAdK;pQrB~@0n=K3d=)x>=BuIR2Y|*xiPY@6BRhV$jWRAqQ zyYbZp+!Uw$aXh+K6t_UECcV0_AQvR#S9f!p>+0^zpG|p){>_(8Z}BTiOA0Xh3Pb6g ze&O<{6aBla_vsaXo&B}2>5YHg#+z*8*A7=~yeEBwJ_n%NcuQ=&Z?hG}kaH>vp2pVf z=eeIg8z74SzKGFW{!&VVQ1E3i4@D}nQnZMP?qXd1+K)3w9lnH&>K-jCGXRIye#w)r1m@{px z0fO&?O3j7*>Dk2MyfQF)9+x_O1wrTTVlI6X!Ofc?Htz7H(>D?%VPOqc>GZh-$t5Qz z?hh9~=@SaBOnMlv2iWcC&0LP+&Ar?a=^G2;w%FMs1@#iK-D*69GF>;&eZIAB5XUpa z^d*DL@P&M)_jEGD^KcG@=R;wdjOTQ7fj;}q{V5Z&DUgN2^p|}y`Oh_HhUvSRncFsgeH94xU`_$Lb zgA1+zJ`{c!ErUzpsLQ$Z>78b|Hlw$mMRfE?zHFn{oJB-0Mq8rcSw#GmMbwtX)}EtS>jy1kQf@xofV4&Rox8L_ zluexa{1m$H(uw2p+!aml$3ngphmQ;z-6tEwMB$K0xrqWJ&;E#!2Oxipj6AFv!Do)n z2Jd;k6*j)d*o|aYt?L?2rATRBCz47$KbMkwK{ZqvYI3f1^QNJHc=}ORz8VImP`>=^ zeZxEssELgNU;+-`6P)lIek8ze1cTs-_pq`f&PpKO%83A+N>=7J!n5vTC4g@O=~iBf zv*L@l@+bgLk(CO~3SQ7VPq$KdG#)YIxyH)UmUuHJ5FK&&o~119P~y!NeBnU1Qw0Rq zxMfEE0FzcShXb&L%)G3X4639EJf~!?jkA&x@65#jTt-&Pv^=Gt>*!WKjk6MrxAHas z?~;{8nw6jtyvecBn33JN-Y%KO2v_zNH}B$bWB2Sa0CBb(doV^>m8GmaH4e#M=|F}t z#AiW#Hl!?js{@%T&B}uKZOEeRs~pG?3<)@`B8^(FYFv~3xC5zUNQwnXk;TFEN~0_z z`wL`odgTzKtZRfaI2|wgmmLhZ|*6BIMTe$`MA{{0JpKy|Tn8 zJ2FDaq;Z;P%1hS8R(mMEs4D=Ni^Er?6?Mo6b_UO}a#@@e`JA)Xc+LUf0?_vIA;-XbgOG%Klibwamt7@DWvkUPX%DFmPg4quCArGpXtP`A<)XT^-S zQU$;ovT}@O#WaG&;5p@TN1TIdtklr$KZY zOQ&)=Ey5`jrd}Ni(|8r)u`d*+9*Rp7+!(}bp3U4y6S#DO#`liKrff=JfFl!)|b6|<%w3H5o z>HW$~=49=ig@U`8PWRAhADv#tDHNthSVG}ylxdJ2iOUSr%$&(@@Pxwj_*^DGgA)qh z1L;b-13*vg$WrucSWt9=g*U)GTYcon_~|XIdho*M>58927ott&(On-Q&Qy8yB8w0^ zRW7Luaf!+!3#nva{rs$vxHt`k$K$-R8%~RHD2k}RQ-XA|poeEwgB=R5#rbS?jDsVv z^ns@JizDN412)X2kI138MJ&l6OLCdIerZMkIuU^_&m@>!rmkPXup*JHWLUqlbuDX* z^_AJQ;v6iJRYm2{k;~Ne)sg;`G-!pA%<*&N0wL!h>~Agg8TVP7!g2>YTyE8K!eei8Ea1%p=YS zowJHKB|4{xIAe6qR^p7+IXj6nPUD2abe}#*_wT21G&4-kOoYPp%tvOJp7YAIcADtv zRJCxoOu((6da|G!@luY&*{!OF!i@;@u zXCUE$)Gj6!;l!~$h$)VJ?eJb4YV+PkY~ne*qWcI@`>`ZmM1Ki}{pixxEo@@`Fk5jY zV%=Y2#X3ZxxP_HAfFPyKjBmt1)prwIG44QYxZ>z{h)&67c@f44adQA-rOaD{m^j>m zScrLB5Zf|@>||qJ;K+M+WZ=;jrv(v4-{@Gl6R}KA^by1?7oS6P>(&$5%H_6|rFP_Q z+g$H;mffT5$Sro{OFJ?S^9|cM84=F^$8+^Ulal=kJiBB=VR|GbNKcrA!t_K$kREU- zEJJKMBEDrfWl%LRd569jf?M6bWJxhfpP<5+?4nJzQ1Uqkfjp%I*de{{z{vBrdA&I4Kkwj2A% zjkh#G;6;rvM)ck3CdoQPW?DRCql|JsUIPV&Yed1H_PFY}u#f%WV?u$iH9+8VjWCR^ zHoAjaz{Y3?9f5YcL`VHI%wSCXc(n-3b%q! z){h6Hnx6mp2q61+qLmZ1o7zG`3mMylr@wRg5gtopY;Ck2?Xn{^a{K4ej?OK(?$`rv zM;{z>5Zh5fLOUXN?dooCn<}JS9B7-0SOtSNbrI?R8`{+Im~L_kFSBa|U7n)8T_tl* zgabn~L7@o(fn6E^lelLR9o^z7`c?aqa8weKh{+gDl(l-5 zaJKx{sJxRg5+$#OUV1ffzWhH_^f`JlNW+=daWcVR~H6R-L9s$&&oN8^YPGQvxaYI^qfOpx~PzpaSc{n1I* z{PYGa*}t3{&#m@VL!{-=cDT>2b&1QT<1(Y4pp~A3b0jzyoMScybkX~EL0^d)U)$WQ; zgy&AhYb+!jZ3~~NLTZH_F$+!y%eUO-lPlw*czLWhzwVMx3iPW-NDH$F!6WmCJIX%? zZlBqOR${)3aN<<6uz#gQ*Ivk72idQ2LYO zv_lmkc;qSK7Si`mXiv(QeWs;N`5Ctv-_aS&Up&dT_q2KgP>dr&@Ca{eou`Ik*OtDO zF&P5iEZf8p@Z88JF#T2{qzkMFfs8`>S_)mr`stE_{pA#!kmQs+{^&^|edUC@v;Q(3 zV!W*8E@q2-+GR!6?rY9+UM)Ibp~SD8%%x_@bL@h5RDD->sf0>^%iiJ!s5b1M$HMuf zLAP#)%74Pbd1IIt&g^WCUC2anFZZ1^=ymdx3){)i*V?9q4eLo|5JFe8f4%e#5nc}Z zKQ4@&kBtKAO7<^cN9@pdN@{@mmHi8RuMt`Yr?u`TByWulr8b_{y2Sgzb*?dr-haeG zUu7`1H<57GqmmEx2^7#gHG!mVzRZMp_>``6owsNMHdf#?b4*mxUD zv%t0SsF+!n*5KZ?;UxZ!mUw3;@%`~;^;}AQuA!GSRUvK#;7!dZie>(~ax%{%p)fIm zBrJRZF}h*kE5nI%-vbeOb2Hz*_plJGN*obFMtEa*6um5q!fpb`cM49dOZz2m5k^Ue ze%Dwb#plooKehYrb5Vq(Z~t;^yv?8lu1Y|8wY*9~{X1LU2))-i{@M}veI9uf)AM5M z9_`S(?wwNm9W2^o8(Q z9!LBA{`EMqA$xT-eju-|3hCQ$TL?jO1|vSIB2D^a9bIVnZm{{J$pE=8YR@tELQeB` zgfB}9ABP@YLHpj&^^AObYX1oA&k4?#*>mV~Na5YVtX-59`QfsFe`@z1!R3vhi<&GR zg7a0A5#Cck-$=55Ln#v8(^DQ@749y@-%dJwmwY~HiC`|5KND*yj%_i*S7t72wnzjh zWmvkAzXM})olA$eF!kUbll(SbtSu-049QDyHs*F=2X(kF+!U)}%%RUvh0hN9FR%@{ z-z-2k0XHGT;`!NK^xRpz{yL9xvoj_)j^}@ae-7c>s}Y$x(FKGthd!niZcDjZ^Dqs3 z`l;RTB=@kbD?cTSFv#yEXr zEZh*R)QsEprFQqcD5>#=-n$c8Q$q=TbIkrpc{fatFYl(9@|M=Kz9T=uZK(>c+2rT# zEgs(r99z9%wGnPDB){yRfRD-H2gfX?Q*V>^5+@YyIveFkUs?;-24`r>eJlmF`!;Z2 zQAAS5QHx&Xc(}GNWm7Gb%+!w8-4tH5C^6H~p}OnT$kxPU+8A{v`BJSOBVS&}i3bTv z*&QtwkxTUZ)(R<}Pbd7;?p$=H0P73K?KHI9Ge$L13MqFXFv44R=jnD>kx<0%JPI3zw_m(r zD;8c@$bR> zw1d@TX>Rn`*#gffEJANMA0XofoVc&iRmKIxS_wgW1{=Q9`7F7m@Yx?9}kctDZ zs3JP*XE@;|CEdmJ`H&2k?`xpsuPNr+H$bx?>xLsj$Ozv7#lze1BuL!MF#R>N!YZ8T z2B&WpPHd?+BJ#J)^6h)0E5V}Dix4uxH%Hf;21P#=5-%!4FBbBmvXK51DdpXF3r=j$ zT6k{6pC#qL?;x!pcu_I^$x0*NF6W1Ur@zIlFTC_pV-N_AN-2o;-4O|i4ZcvPkXI-Z8}`03z!R46?W!Se|m=VLRC%i(tBGl z_i(eIXK3i{<2Tfss@Cvdq5AQjo>53&u%$~I-|;wc!n7>sh^%Psi=!QopN}KL7ee_q z`{!WMRg16-J06aYynysQjT2j;YXr@leTDNo$p1uod}Whwv;PVf4bs9c{1R6kG&K7< z;^de!^6hcFC)oXPM0osOLmw>i)089yS4e+Uk0v7DJe*jau856%d)BG~o93nn&sK%> zxASNw@@=uU%worCg%GyAWRu;He^&}4>iZ-*Jdlgf_>RWDAO=aG8RrZ7zisB*`rL{ z#m?0j7#hwzSsA{(b0_EF{ZQR(MfSLSGMJXF{28Vxju0c=L9tCiWu(BWc%2!slE%CC zLxAs?4beDcCu_Jd9dx$t(Zqa`v>q zorj~$#8bkH@jNi8s4Q>0pbYW{A-#qSDX{`4@s27hB*~*c4^q>!YqN{klP*qzT;6Vi zUE5Apsv~-Gw^)Q-c-;rXp^9uuv&ccfkp>iqq~4>L3l48g_eQZzF#YHby&RA1;=rF3 z)2nSL?DcW8<>s}og@T(@`G>d|yEOX6mp=J+DAkMP;s;YLHobk6Fa(P%PmgDFN9Q3!aSq6^w4wZS2R#oCpcja@fZ;~q9fYxTq96O`sQ z!YLuQ&Zxqey*=jg>vHB_2+(PSP?<~m@j7t~n&V4e>(M`OW!Do-(UIAz>!A2I;o1Cg;7Dzayp zv-7$-W5Yrz&kH8!Q@t;;J9>;0i`@dnXY2ILn}EgyOex6tw^koVtgcb#lm-_Rm4;oHM>!&= zlD8|f7TG^KR&u&R=BZff?X1(dMUA~4xm_p21vRz?*3*V|w*xiCnxw+SMT65R;T$Ef z-pT>DXPFn8^@*)3cb=A5Td^&x)v<|O34TMY+vF6!Q%UokUF*1UXVn=N|C>#<2($Mw zP7}oros3adb3INZ?cJ5gL%USOXqNyojRodK#DCr2`e? zNy^EnRTw8Lr#Qipjzx)_B7AE=ZpfhgaK(`je^&)ANWY*4O4Witv7+(LP$rEOrzxl5 z!n!ov(-}x=H1^S#F{Pv%GG?JD?eUPlM2)G#s%A>`OxJf5`cSUQ?Qyzx_c_aU21sc{ zUC>W=z6=<< zBpyjlbez?+L6(lV;U~~fkc`K z&_nErg+RYl`lJdWzRIb_qFCu~5?af_F#Q=nTFlemdFzY0{Wku1!tJh)R)LejJ6vDD zbw<}aT{gQYOA`9yHq?EkR~q=EHO{DDp<>6sd*bA=(tDLNJR)fs9`AG0fprdIi&-3oaOKnKC7`%pX&Bf-P*JZw5X;@ac<>rL-b z4;dBsV{WdYLtlZ%XoC`^k!PPBM<;#4)#s8Jmw%?>iR#HX0S$+4_MdX4F;(Iso{mFM zA^cWrd*tLZM2GDei8#;N4Ue6EPW3>$dY-z0)#mBzCg|Rd4!yQYZIH5tdq*fd9zIcM zFC+cw*)glDS9tWJYe514Tpj-@uTJKSe}bYtSLC=TFh6 zsAGT1SP;wbraWLCtYo_L;swfZwh?TR2g?@eq2FkUSo;_*+mq>v-I(9l=%ww!bamVq zlF9Q*JCW(?xG|)i2=rv~9SBzuF;5uph{eD>sw5GFZ6%{U6%u2a3FJSVkbf`O#zdGe z;Z}jCH7}TlwZufdIqhrk0RxZWBxKB^F#j(Ay$gaWeMP*;vmJP#5?C;l*hVeueq+V? zn1y410b}sxiCj6yb4+VLGnvujy~e-xGQEt# zC9h%4gXyh(yqC5I)8|XtSR3->%q>p?D7JWZ+@VPU^?P_IIB4BZD+`^z!Oc9?O(u;AH&&Avb=woKRt2+44qLxFPIv&;+YpBb zYAqT5pv-E?jK_G)ghx#!=Fm9F?a7Bc@LGg%%b zdC&HmaGCDIN+i2Lfq1i&l6}*m1Onq3HD?rvyhDM zX@r_h{Ld5U$&A-bK58fS!enkH+9Xag^nvnmyA%K7e+OzM89uT4lOp3?NSeb0AQ@dM2cr*tp2w zg|<_6=$Th<-S(uK*oi%AH4)U_I<>vlL*yWF1ovd>T6P>8kzU#!Oj|<_k<6%s*~62m zYuRz6?WOI(Ol$_8y0&3ARL)zRF0Y-j_Vw-m3pcwI4)WkNthSe#%;@o6M%&BuG73MvhF}k- zxAyT~+8)eyZ}ukIOSW=L#>WD={YDN5{6CjsV&S%P^_HmkFSk9A5XBO1DXIY>OuFe%cm|FD1~46DRQ! zCU8$yqPb*)XEMImgzs8SrgfnFgEIanOVoAOj(O~OCg#%vWb3=32dG_^lI_MN%frFq z|G>6r;%x0%EA3|{%cGQ@XM0U()m}T9?bGx?c^OOQWc#z(e&mmCizZe7c_+7`9n2X>0m*R7rjcn#+VT4#8H5=z6a!}!pnPCdqH50v-b8O$eCtcS8k67h}^?Kd_g7M*8#CecnTQD`SJ z6I`~;A^k+Krv*=>T$CkR@N$+i>(P}w%FYw#Rq6>-w|Bapy~3lzB8!_7wzQMBf`qh^ z)TPH&a)K%tp5^PgM{|I6>9L{X>FI&irN>^QM_r25#1Yi12#Dul!lNsBpxR$g9?Bj` zcsb~y+5TkzXL6S?j(hmn&fK9@L=u`m`eGu}t8Ad1=cS#<{DJnH2uAu|Opmmrr6qzB zFJ{|W9!V=KVd_ENHz(x1{VgL&!FxP)cmM_O@zCx87QBZAPfwE;yoa|Qt+V~*Bo&fy zAD0X)OelVj8@}E^4=Z{-A^|4k{AHy-)^SGucL28HL*FFYRJpq#gBnD@~Ap|leQ zZ;yNJlyUb1+oUkL4 z2=eTyltSVx>ZRZPDd=4k8Iv>g}zEZo7f))=gX#2Mm0PF`;r2U*7P>CEBCH z@Wy&N>6JJdLfp^WAV3L7k2d6yZsHc3-)Ks_@(U%r4hmS}H5cAAiTinYfseh#{k$`k zh(nr*I|}$pT-?vwTq4fXTtYdbWD{oNL=d^)ovw$vH!v<2Alp5w{A+#Lqthq)A{@_= zI$=95g$AKsMe#=3H8J^&Aqjmj-VkN?hj{HX+f zDuJI$;HMJ!sRVv1fuBm?rxN(71b!-kpGx4T68NbEeky^VO5i`N1b$aYPg6aGPsu9v zZ3eywfgfDNp^Dcwu8S70tJ++*E?OI{Z)&b;Ek3+>^Qe-_lHmiJY6jNVHEh^Ca81Jo z#HyRCnvW?SK4?hsSS`;yRp~qgCr#idWaww?>go@=-GgPYsX5xz+*lKBX=z03Li?|x#)c{m@gVr`R(Nx{Kwz+Xrr8AQ; z8~0OU8F&++A-W0o+jKs?G}3ZROKWspWwf~&O$cI4*p<~)Ezv=1Ny_QeARW9l3T|CZ zC8qPbhBYFsX=$ykjjkp&ioz$3ZmL;Z#kL$5$Z1QZfB%}shShas0iQhAIvJ_9wANQP zRn=Koo*03{y`rl6`o@~7)<#RRp(WZ<8ONhUt>Mr0uCAe#T_WxJsO@N7BgI%hMx(Bw zuC=bJzV6s)b7g&93#&?gS8u8nqpcg7#7+y6i`G)cwBoASR6{u;>rGhr&;)Av(u!pi zu_=c#4>XEmrG{F)p`nK6hpKwE0Q=F^bqz?RvB|Q7I*iG8DyntEx@a>hMn--l2VPv( zz|qqcm!$3x$9LFlAMh!-4GqXRWfp(K7By9~p}9GV#w>Q%L|Yr1S}Un9g8{VEx`jp| zA6?lB0^hZ#{MvDVP!9ufH0W9bSJyXI5!|seu(~B$)m*by=Q(jQu_49=2uR6KcCxk7 zENFMD!ktx40WIEcK;WXVoJOj#@$pQDjouht(}=uDiV&@?s;R1tx~8MJ%y^;swVF7!mp~`sKYP>RHA!SZKy{h0HOdz7hO{)wTjD{ zbsJi%s_R{M7zBO`dQ=r!FI2iGG*P2wpe*mCEW3xFSNYm_QeWn@|gOf2pio zSB)}hbvjTSSy?%)V%AhNee^R7Pa9C9F2jxSFf1K}qeJbk#2T&Z!~k_v0m&FC@yg28 z7{F|Ys2eqk0MiA{6*M}6Aw9RQ(J?JJuz_i?RAfvXXuAWUaKs?BcP>%1{f+1gcHC+A zo0_Ydn%o{#)hwL=P5fAzNyLiGhx~W9Q@R+b&f^6BHl>1UMtBe|Z9N3R8Nrj^ov{SM zfG?$3*L&Z9-aP?-N=nfGpn5kYE0|3X{{ttsf|=!iNR3NL5Bl3qJlQ`$rF07Bq~y=I zDS$MB{{AX>6JuANObmaoq9wiu@QUt1N&{XLELq~a+_IRZdwmJzHYM^@O4j0(NKZK0 z*?;MlC2%I#xP1WuX-gN7CLlU2TiAtdZlkDxpkP78Zj54yHBlob z_8Lp9L5(f8*fp`n_OV94=iGD7%$?WFdtoQZAHT=r_kM`8^PDs1ec!j7d+xbq+J@Jp zz1KFJ5x$BtZjbQa*1i|cOiS;Y(S1RBdU%t2mM-D@*G=Ud3O^Lu);`lc@@ebn_Tl{Y z+bFB1HBRNf(@3O8_&%46L&Kfg4`sNaO&7_K#N^+d-evTHwi%@Mg)~X}jcXHC*Z1#-z+As?i1S?(Qv>_qpK@be+j z^g-HG0W27%T)Jspqx&KWaQ0C?y+A+1`QOQv_awF1X&GO-a@sS^vSzqnvS+!H-S_77 z;q#}a@B6h&_q8rh_YJ=)MLR!v(Jn|u#;9or*gV|pfiwnxS};fqd8+b6tFTKYa2-M6N94Oh4u zbY8$!Fulh@Pd5o{qtsFw_vA#ShXp6lo3Q2zdG{k+_zbvJI&gSJ`yNwjBd?nrT~Od6XTNz5&6 z=#kuwo;@)w*l4D5lc?Q%TyAwH*Dt(PVtb&L5N!}_Fy3@|yl?nQF&KV>h%*>sUW)$% zgCT~*GZ^Jj%OVCtO#kzt1q&p@&0zfhZ7r^0|9`Nlxvn51JR~in{hm}s_uWU@Tl@4h z-(Ek;{=<7z>)K_6UkZImJyAGZK-Q^S`tDS-sj_B-Cy-5O+dh3n_?C4{GD+_71rGdn zU1NH?_6yrj&CE6}-rqN$r)5ncY?|c}&8ak8%y=Yc((cvgSkujVTtafVpfeYzti_<&N*VCFZ)4LLF6JFDp zp1X+n(NTWD(zv&Uh5olP!rRx4%t*VD*jJp+jxW()?d#GzbmOjWTY4W#>nRKF_LCca zf-4V+Nf~yZmfknK+tl>#;pPnlN4X5^kTEX38wquwE^|*Oyiqffq$T}B)6*%zZRrbz z?v*;C>D#uI)BR?r7lxasrtfj>-IR5U()XB5?^g|_#pBMe8{p*}{nk7urT2O11Lmdo zKEPSgr<|4T9lmYtNXmlnVsSgpVvo{&9(Bt$h-M$hm-T3UPV(uamGT$k3! z&qiMJ(D33$PIc2%E)YsI+lm9(H>Ey?2v^yR@URW;wS8lo_Eef{Tp)--7B$N<-T!D* zMFFF#s!(ZJLt}(SzciUuQx=LWnvw73>#_@CF*}L>=483hV5G99yp|&zG;vW=R-fBY z)>t%q+Kf4o?5tQMY|4x|S&{5~?+YABEGlx6^hp-SnM6|&-*Zl5VI)$rYE^c2c8(ui zoKiBUF+1Cfy-X=7no~^wE6QtFSzK2%drn>?w5;02)Oc)MyRxJqHGSE6ks~5BN5cV> z2+ed9RaaM+u&QZ!G*=PH<5BsP>6JM}MJ3IZMa`ketXQaJWOmkQxodWQBo=?=F@AP# zEF0o+P9Et~(MyiUbInmz;YUaG@6C&fL&m2F^1?lQ3Fihn_#ggK~7M9ez7#Zaj zOd5=It$)ObXl)oVf~$i}3%InVlDCB}Z_6v|DRcS3G=o#DB3V>LDCS0Ssnm-*zn+s+ zL@j};OOVOS&Y_lCQrkprpBk?-YRRo&QPG4vsd=g@i-T3t^ry^e9KN);RHF4o<8!D) z=VV7hODiP?>!SP|a&4xVEfp)pc4JAxMwwI5R9;?PTob9SsiFp{rO5JVrSe^X8QW~` z=nzNFd9H%0G!;dCHQmzUm8>U)(kE}ya#>fsbJ zzA3l_MMb5~9>n!h`J+SCG^<%v)?8M_nJ1+lYggG>G4nPdN2+jKTgvq^m^yEnex#YW zlH$7J5}FsFQlbjQc3wJ(|IL$Ct}1fLPoa5c7f}18jJLGc*2_~)(*}?avtb1#wN*{k zHSt2b>J8F;f_yx^9r<^$DG+M2WDT<-p(<*|)EBwdL3KX9@rHaWY#mh0uM+ok#0byZ zjJl_zLr3|YCzVx0ZOQQAk>-Y#4JE}j<TuGYQ>D-`)nyrgHi zt5)pwTFMVAztEuYIgeA|u)zf+@GVS~&viO1D`425% z67Pikg0-G`%X378Vr^Cp$nMCso0s5ck5N@`kdq6;hj8oBcV(jWigcEM7x}D=LOZZIHPt z%j#>J>ZtC=9w*!P1Zk9iF2VWPLMgRk_*O_!84CZnN|s7Es(^Ybjy_P#D8IC_ zS$shn_(fZ>csda`{1Ru8>|UvwYV#^RbFGNhhS55Q)GAA%hO>%x-ukH*DJq&XJThZW zL1fCT5_vn0A~_KrE>hP;Q4wdbDYs*$BquMS^H-xirLt%k)ym3cv;?^*#5qGQ9d!$_ z(u!3x2{+}%_xNP=T$isg#}caQ1+;RUf?zEU^5T-0{a+keQ1LbMNPoPYwCrcZ zh-i%*$%DJmndAiVV2_qa(RyCGa|+fL$DVZWuH1X6XRXe>p~*jQW1|4VsPl}*`3L8#?CA`qjTlHJfs9at&MEsC`lf5gfb zoCnw9k^C1*AlmcAY92+#W$382VTudDmlS1ZQTf-_L^Kee9hpOBnd^ZYKC?4dMG^C* z&ZS?7gwBu^59zt1>wQ$bjnTNr#(pu@=s)xL89$ zEY`qtGZLn&tF0sFm%=BtE6BH(<+P!-$p*Wj5*O5UeN4Q~PN8PxX-LmKhL>y$R;5zg zlZGOwXlT)FHAPXHd1W^Z_uS|@M8!nyys@Iv`Ep)4=f6N!RnwFnj0Zl>{c=kxd8bSc`vPJXlMP&#`Ypp6d7`7~4<;vMQH7(`>m^M* zPZTMsFX0Z_0vhF7!*m*Hb{S2g@csShvkgLYa&ZnZ&z}mT-i=xl3&tv03$6`WzHH7w zmjty!l-4A{2`OU>PPmB`*QwDMh(>UYwKQW%Aun13OY4NGl27MV^L4dVl_je~#pP}+ zL{G($Q!H_A z#^=$rMPX$v8|sqgqN2HC+FNfZb)Lof6)Dx6?A*8sK(2rkhOMvXIL8r@I&J|JSjrj3 zfJvRd6k}{v9DC)h7h+k<$r&a~)+jT#;)P(o`<=8*R8Yd}%a-yKqjRK4A-mSY z{Jt(5L$S`VksDM&`xSP%el`?E-9Ze^xk^-4FAiKl%kXC_*r-*MQP)E=fUwc^P##AP z4HPc-knUHTRYxrx+DT34JBKocaq`w*~YH1jiicO-yw2PlNtt@6o=eVZgDzTbUN0Qk1U7uwn15RnM?G9oa)M6ytJ9BHAo zl{FkXdm{B1hw}bpobGk5ebib?_`CJ3T!Ldkqrs6>@0{k=D~|RX)zuQ~bOZia5wu{Z ztI1cu?vx9ckP6Xt?ts{#VTyLS4(ge;@#G$T*S>iX*v0T zG(P0@Br@esv>-38rTbAqzlUaDX6784bWEv67w`rN@=K*!Qy)W9Ib^!17ipmBWw*+Q z^lClylh8t7(s>SF%Wc4-q)CS2Wesky zP+LyXPuc>M756SX?j4OeCVL6AuCA6zueh-jS3h&L{)acFTxg7VL5j6; zgApnUG9J84DYkQqW8ySqRpl%Wu9pB(9oqQ81=aAwx%5hL8ZklT`|8!Oqu`s*B= zi!wOVY4w)>iRYp`F(755@zS#9N?OLZlord-1dG0U0%+7JkL3KZEZX}1M zug&CE7B8!*ZD^$J*1R5;mQiuwf^&_&29s9>TEbkuWG!WYT1`bpu4_{RC60!YWpy05 zV5{kzS*@42R)Ty#o&l?;88qq4gB<*Z3Yr+qbk2xdX)9ZIHF@|{*|a#Sl3NOI@0LdC zo+Br_uClD8Y-J_&uCdrgqZ^b^?UkpZT0qNJtKAGz>kSF%^4z*4IiBN!eDpyBclN9(3bMsYO^!xkWsE-N-dM!O^xg7bD#tDf-&kIgzo>;8jq7y`INAB~&;R z)OEoI7ryW&s5p_l65E!yaDXj&3?!Sa0EgE6#eK9%yt1l^W)2!?x|~c?9W|`Pw%J0# zXJ?g`#g+XmS<9KE66Lu{#0m5w9FinjBvI_OxR}E#uGnxZu{dwctBTxjQBDN3*O|Pu ziEiP>1KPedFz1$Jxp;z%45BMqL)A;`d8r6#weHC`iE)Dk>c%PbPdQtl29~}Q^DF*~ z9v4kpRfTNDR8rHJPpeU*E8?OV;-|c&F$NE7HGe|QE9+>%cVju}Q-@Mby;u3@XtDaT zYFbg`cJ{|Fo{hRBe9TF-Y+@ywI27^LpMt?JpqfJsn2uXnL0;P`SMcHwl<@2=t>35a znx|)}Zsk`OuOPF`y%3qp(lWA!Hl(CfwB9yS4@W0uHPf6jKSh#iT}X+G&?bSU>S5N? z8pnFF)waGuzSi35K(_#&+#Xt{sxtuA2JF#vy=^RScXf@6SbG`V#;R!x?r&8rl(o}} zo%#|Mw}7to)lPFIwTP$d;>j{{ zZxB%VhLJ18AM-em0}V7jjkdMc)d%WeVwT)f(&EB8>2;`SI!~4+%BU&0zE2~;yHvd@ zF|0u6v_w}RVfQ=Sbs}o$chRm{g_r$NILdGeQ-~*I7r5y-w^KzoQ*hTu9-*wSlHf=V zg}B(y2sN&8Za<9>8fY4n96H+Z6>=7;q}eO9n1PIoUAy=%4YzstT1T;NZ$XH3TyCvl z)Ol9-acsNucG-F=o_(KlK&dwY@G;e5D=8)>frkb)*%gShzVPy%u<@2CM(Sepj z3i2|CEOYN^d*JTbVujB8O(;lgk6+>u-xsgW6we=)S>7(z` z6wd0J5}Gfrp%K`SrW$T2L!2|w(9*X=T?w_k3R6m>KKe#Ux%Zw!#SLZ7a3(PqaSogq zi0nv^fsmt8WlR_|Y1s)g;IUDV8v(g$5ZiP#p4}@w0?L()r;3_N!tRUII*sZ|*`ko- znOkuE3U%G5s{>R#x_CC0!j@K;mj$gb*EA${A-g_$Bb(rY_fnZfVQv9TpK$zwPBWE0 z*-nb$A+5La&xK9hW=Y+5Ra#b3#iR0py2j&RX=_Qn2&y15b;@%RI$4-Pmm$kqWvCs3 zFcvpeHZkDdX)INma)6JMZ<`1u_7We@;Ak=LZlp+o^CoE&OdWkat)DC9kjyeZwaXpZ z8nqRi$!^V1bi^oY;-bBa^E4{lRH&A4J`_m&X1k*1+0$s|x~_@#(z;@x{cjSDb-Tc* zua6}`6vleNgki$4Rl2l7g3l?G0tL@x&k)tA=qN?%|E@I46g^x&de586T0J;(!z0_b z><)soSX+w28aK$=)wy3t-2Jh}yoYzrjyUbqN?j)gW%AoD@< ziOw-fp~@D>k}~blTc1m@_4HoFOEAkhS;<07MYFiyh&FyQgdE(a4Be$NFY%{NbSNFG zL$a7T7E$2+y=+}pmDg93d$P1XnNg6Wy!wM`e0d{ zL@KGsO5OBjpp;bG>Kk4R!-GJcp5#p8@P`{**Z|E1<0)k+ z)WCd-^th##vMtebx#NbJac)(dQxTVP>X7_d*zEXu;_Uc|;_UdD;_UdT;_Udj;_Udz z;_Sls8x4zpJDm1`#6^o-2N2NB=1K;nWJ<{Tm#m=XYeAfv+6szzMn}1U5nFF6HEv3W z{CN8)x_Hpva7#U`3rg|q6K~9+vo2_N4}~pc{}oNcaRA$2zn5CIum!lbh)R~V@niBN zN|D=I!6#sPX2&12$5j`Z@o^(t*_x43PNy)4q{Vk*IlLZSL*IG>Yf&hkTuLz~-OM3H z~_OOkvAZqvN#!(4`#F_!p??e9y)51^PCyUsAdhODv1R|!6%6#T zEp7TI10FMDwM&=ND3DBX+~K$Mkd3uEedP_AoYUiH2lc;^MWt1|1}tdSEn%{o+k!jX zwX%j*zw;oI;#CcFOmnEbyozRCZR(OVQ`4sk1l}x1eRneS_bYf}2eIqJoqV^4u4tC7 zJ<=}6Z!)_wh~r#yVK^zN*7`j<+M!bCtpwDK@WP2$IGX?+#X$>T^e`M=7!Y4)sgtfI zds9q@!uuy>a7C_LTE7BUza<^MxxShVlt0x^^=_A(4q1EY;x>NWp$)^)1CRJ*A8V=! zgRVkN{kVL$XOMG~mMxJ(A~W~YfyR=Nbb2SnF+v?Lab>TPH`~}o8(M23Ygtlh-c*$0 zy-H!07gy2DM5w|1Z)wOule)Qu{<;P|X^Kjp);vciqABpA<{`R(+Rt*@WgIhGwnnKT zVNjRv7FxU2(6kg<_CdDZ2uH(SD`_7HFHq&^5`|&8iCgt;;7VBTPO9=Zmh17#IxIC| zPP^|SuW*z?%Ym!TqoGPzdWkh%9 zN7pm18Y6aEmfgp&3p*+ceT^k{H8JS%#VdTJ(yW-?2hgHP*Y_>^#VO?2ysxG2$D z6X#yGVEm+;!db1Ptuw{I+7xP>abBxio)b7K*ma{Xabvc%Zh|o(1Z1nknxkhw@M;BK z$Qs-GmRd2`2$$U!wmT!UDMA+AI&IcYt8}yr7o$QR0aY)G=+G1}Z|YC6#OSeQxh(0a z2je<40sP9L6!CI0k!YdcQbix)k;&USIe2%l%?C6kT@a&T~gVgpLQ)by_r<s zVT`xf7R-0vb1H5_G@_i+Lq?qr9#l9Nl81*b*5g*3a+R=dC!rN2KiMr7SAmmk+~(mb zJ|U2g>4WkjW0ukb@Lp_N4@zTM@_wb0a%Z`)d1T8+%QDhh#$#w9~+A58p0+?)ul zSmozxecgvcKB-{1;KV=o@@6u^SfkyyQT$)aE zauy)IicplMri?bW#~(|98Ai)!U0m7KTqw59=d2R27@7Yi&OiO`Ca*=8DC9?1ach zp>9Q@D&r30_78q4S~_dutY+Hyy?lAmtcm;w{a2(L!CUDd<>Ztm-3yWWnJDgj7I)$W zWoYXQQfe8U3m$JMDF(;Cgy!~g#&=r@(G}6abB(w*#_d1KbUw-;-G!vbdyN=D6CTlD zMNDb?5}l|cLnN`uR>piHdhCQX7kH=LJ$xz@`GI^+PW*FH;L8>_#6Kogx`dTJDfZ58 zIk+nvG{3Z9dTs1LRgJes$MrF!C-cH9xkS#H>B7LF4As`qCd}$E8pQ;nsa5 z#E5e!^p!O2w4R#!GT!ACoxXK)u~`o{I9^I;IEL2h;#b;n#AiBkpF@42%;?IR67yL7 z*$RL6lsO)U93o39KQ_qnYIS&j}`C$h)Vsy(!-NsgL^L(h;CK{=#P?cZi#uJG>KQGY5nVIOm z7&kQ|`()j;7Dpqic%fcpLn-~dXDlLXyM&@4>*80@rsRa`bS^H}9~nIodBli> zrusiNJY;&nze=t3*=(d7eZzia%i|-mv~G`#Tlh87O%jZby5vKKa3W%1q83I!vQdEN zT`Uk4(=sadXZ+;M{x^pZz>SyPbWZE}(DwMAO2XfoXBEH#P%H&r4?UT>cdD#jUTVQzhI8OvWv05(9^VX0%hdTfRFZ zpKjB@`(C5XnvT-qNoM|833p5uWp|Q9v@p#PH#%*W;ba+kLM{l<`hGe({jaBF1$7e1 zqjKJHow}4X=>(DN+oGPBcR$zEMh!t`z;1Qfpd)4|Zj-uew6f$*R*t1)`2VxuCdUS8 zEflYAnJpU~;+is@tsRZX(c`J{*d^;(65D1)JIdXDOxGNxOkzzcVT>_c_I}g7sJ@Bs z6t~GF6$j26>$s6#>;PeZy>RTgk}QVjR?a@Ow6zrKri>$H`38IsFsLQEHF|TNRllpopcN8Gkmm+tf1N3YE#Zj1SxUk>RaP7}6xToqA zWKI1Qqtwt`SK#+Zv9>PVXKtKdKtV*?XB<8G(-{gX94}^QCs|Pg3u* z^P@vAJ{mb9J`r!RcOI;(jI{q>4o!~L?G~NKqzC$WPsH)M7JgdBH71GGTh7Og+4K1L zo>YyIXAhlU7M)nZktA#T zA2-30OTXPnaTc1TuB?_{&a*q{zOZybdTi&M_>va=6?0g#X(o#D5g@)p0IxvMAJns?VrO;e0ICc^B zKu1!9o7XbSSv)E9x86~$vRx>y!O+>%`pcKxfyBpssaD98Z0pNX@ef8~fZBBx z|C2yF6N11IdGYtw1PEZHRf#jIEtoa`W1s!_{n>TbDfJkjGd zq$^3tO`5Nbda#@xvGEojqQrBnYdN$=lg}=+#%9qS*yv6nbhfJXr3CZl(mE&=I)6#6 z&$VTBK>YQ3&7rm4d^5Eu3xr7D%dzLCAiQZz=ALNkiGtX=6B=kI!B&LOqZ=xf;V$Fc-LTfueT5+kf z!*r^ZbZzD>(?Q$hGSoNZWJMj-5^_oPyf_**(?n9@5%n&RL3WnnLZ*<3mpw|sSd?NXW?5J2+R*}WJ6Te4UTlM(Gm))r7T!6a z)bQxMDjh4@(OXpYT~UbvZmsqt2g>;;e43;9tTos1^;mAq`pHF68Nx=ql!X4OqCQ#ZSc_Hfb9_^^4Q z=@c?Uek?ShG5Y5$#T?_f)l+W9NVbB<2TplVCr+vhj5x1}|3~+yk)vzY9GJ7k1~e67 z8(~{pwWCoPZv@a{9AtQedL8|dF*%IY1(Dqdx?B#ZNU8R_@{?1)tt+t@ls~)F!o*H= zg|jnxJZh#6y^gkEaD|GEZ?c1e!+a)L+|TRq*ob<7wNW=PB*z7_zWDOWx8ky;?JmTf zu#GaIE^>(37JIq+N}E*O?|Hjpsv@DnXq++?gF@|dCIzo(OBSh>Gb^d8J6F*vu$w=M zI*(GH2WN5>x3wj(cF%^y&iRP?8F9y;*n*Ei(P^qfT#mKg+2IJZ8=tdJWom~=OXPVW zIgHmnp!FrRa?Vwu5dG>1c?wTN*`O~(W+G;S=kdk^AhX6o1z$|J{Ro^<2~|r zGIC{uY`O@+50vwb66*_%f1;1EE3FyUGsHqTt#5F#^Eq}F22ORoCC?tw@Mu{z{pz7R z1tBhYMct?cuPAG@jVJ7H^5_2irbm@6A%goK{1loEY@PL?{zttE@&KFt9geuTJv-Z* z&Y;FsNwG35Xy{6_bNmCoS(G;>#G)jjCUzh~P1E`R95vu5sq2;~b5dwo(RfbWPtdf! zmK0J0Ww(?95d2{OSxU%s$RdQ;p+e$J_#33%VT_aoe9i{@?7Wg8T0vsIbwd9$%znXx zb}4X*HMu*BIOb$Y@GR>0xo*dLv7LRJsw8hMaXU+-;8V4$o)v1zB`E|Xt1a__vfyYZ+iiq=*js)FqJ zQ(@{Jl{hOq+y*P;ow+l;#l!jZ0=9bG;%NHLHF0TNRyC7*K~g0Ai;~=H#jiA%**{&> zm-Oqjzdm~CNQ#j(@@n0{mlslqyURQ2Xi!s9v4VQhN~-D+Jx!X5IOgjGg$Bsv#~-dC zeE}vvq5p9$o@xcJ%3wRC^IrU`G*dKM&vnd7A;HrLSTh}AK zvAk$;ossvO=eAGha=gx+=TpOHAV$MEUUYL19YjU@etD;UiuH?v z=iV#?|Clh1jc6vA=lIFha~*hGBc$FgVPKx(c%zz6oQc{mHG0@=CigCTZBEwEth}Mw z2Mc#!Y)k*|LjTg~ADt#1Y7+`Q$Sh3%`06e%p|mx#LusqJr1jdpUHh$R@}1uL+1~ov zlU}p*@BaKcy;GM>ZRT`pPk)+h+L~F@ou+obW{F;=k3VN?etDr z;Sp)Ob;_L3spr^EUB-24KdmFD={xtWQ0N$XjeW0(zc;BD5^7_PRUf1d8$?}T7&t=G05&d)ba^G)^=H)H= zd6j)mdAWttBI%y#r~7r4U175E>7M1MyFKL}U(x(<_iZ!X%6OoD9#jV=>*sSxM=afK z#`E_l5UbG7uc24>Ii)*!xlZpiCVVWFSk{D21E~n9=*PK|m?Y(*bIgDwu zU^iF9R1jl3hPcl6*T!;)%5H2^=JJu-)foM3AO4IkW^} zSzH&-Y+KZ6;Fk0Wow7D%jO{dRW4lS6#M==lW=`$?=mwKI$vcsaoyh-p3*5Z z6yo;umg=gczb)UjeN5=Iq+YU&S_NfUh`!>kZu(UxcujjgD$SJZ9`xDlI-(v$Vw~z({QL)JkWRHsCA3~ zI2_t99z^B)1n|BA{D1)7KY$+;zy}2Ig9G@W0L~?zc!fhl5+RXr=+FS36~J=>cy0h^ z3!Qj{L-~o2NH|mwzzYNT@Bls{fFBmXM+Wdw0eo}-9}~dG2JmqKd_n-{UL*e6jo!A2 z_Q296O(GncWPirJhMi@LL+(vDG&KQvAnZ=S9Y| zhv{uU&wsFQen>po)Bc-y-%!lXnD3NHRWO}sa+9VDMD{fCl#Kl+AUcHc6`E!8qsWSKLa z{+PYB`~H!5GX0MS==qo9F+b`lO+X(q4axKjBi`5R7qV2oBtX8=$;bMs$twTr0Qu_z z_`L!ADdPP?gH)@GfB6mj_sP=VpLpL;tRJ-WEF|8~^Y?gM%-5y>JzE0!WloRfii`_c z&y&iR@Ef|UprfIXc>tLd!01O2gjveXgPEcH3gt!mdc5N?f6scX+m+X;+zNgD zQu$l@p2x3z4J1>`^>B%MC$S6uvsC$m$}I;kcRc3T@i>*QSBdZGjXSJI`I&gKa@m_q zc``nYcrrb;%I&!hk=uqY4&Z-Meuc`}i0GpL-i;bK-}gb4KTuzfQ2sA{@5ZB$M;qXPKk06vd+ztA{I zB>n$<{p(!foNxcq^j9jsJ3!A4m0zOuX0yrz<*Lc7Pvr)qh7XEcA3ENJl#vy-g! z_49Y9*vh=XM9}yA-G}aPckZv3hWI6z-YmGUwS{KpKd4)-lv_{JYqUpH&WE6rE@+O^ zHIt<2Z9%guq@8ATbhkldQO!zT{fa9L7R{}w85Y@TUdeBEu`n%~-BsybmD^RV($04= z!8;LdA!lRHnwLChcK+RDPrmbS(UYZc$N%6#)o>=OMJ=S(t5f=%$!)G4yG#qElI?VJ z%MpUiK7CcD|Km=&p=RCk4YTE(XPi1uSn)#Yy)6i_)F%pR4((K9yoGE*qnXH^YAleS z*mJ*?#H-}!$Lk8*65XAC6MtkW`UB%T{bt-sRQYMYWO<&DP5y87+F3bC$|~V|vEvqW zNjfccr=N3D75uA$%K1;FA`;b}o!Uwm%nE;Xh52Fp=zg!V%SD&kSEEwFKWiJVCM6Yyf^hRUQ5F@*{#$d1H6rQc`!1zVKthS{`UxH{mD{+HR; zuqTf5j9RdubU){9)HTt@_vXs_#-?Jou9X(42R(^*s1-@YTr-bjotE>caGhdOsa^O% zA^+^+q&ocNrqSjZs{^tm)&G68L}RWgjvceb1!MI-T2(|U7tTrx6ihsjD{1l*elRJi zl%7Nsd^7)d5Q9@cEVlSGP!(BAw;P|2>~`lcx1dgKhy2o0(u8LZH+8&Boz}_n`Ln?q zv#I-`pTpTT*(@j9P*=u_X#<}pkIr|E)2HL)OmDn=D*gS_FLDi0&f%)VJ#G3q?7km6UkN?W! zZ+rX_kB`*29N+hJk5BM8&m~$qr+D0du1l2j%NS4oR!{zNk3SnA|2I$m3Qztf`kOCK zX9@kY^sMrp>pYL2>hTLa-o?9bUysiNzJWgD>j5u4m(qK?@4Eqy%J&y@a z(DR1JeLcT;JVJk4`fX+j^I^NnBk$ss`w~x2JC)~ja@#chLHTo3fP9U|{q$ewabN#^ z9{1(PDd+T8(LYPixdHs`0Pe>{c@D<(^iz3$-a3!xd;B<$Pw=>(PMgPKJ-mv+^xJ$S z<|ns7zL;0jKa=OS!xukq1N}382Sd6rA080wcq9F7@{f7E$>Tjd?&re+$}t}bJ^7WM zp3$DXUk};l@Wp!k{9zl&7jw=NOXo!l>0<7;qgx#n&T}Uwe~)s^pI1G;#FKx=<9_~p zJySgHw--M>OGw7*`N^KVuitMk{_|S;IiG#~ zPpBTw&n5KF^na+F<$e7>d)(LG$E%;d{wyzlme6-ie}(Gd`}+CzasVIh>G9Hc zSg4%S@3+V4p8QJs+tM?~llRlV&g1^`-s^EcfBxd-gU@?;?Z|JZGd%9MuX8-UgzjbO z{FTT3e7Mr%zMd|cPR?iFp6{m|^Jl0h@8{3qp1g0L&9A_Ea)KxC+vlyGe!u?;d-A@W zv=vn?rQh#QCwh8(JFvv#et*hoEXJ>7xT;LpXL7shIBFa^Z7JK#g6&)=Mv>C zzl8poo~u0W>rw1h5dMbVSozufFITTCO~`v?9wdLYCue`M{PpxNnf_}{$orh-Z}7PN z$^3WpFPWa-nh?GA)W2E&8jlb3_>CSP=5fm_?_-xgLHD&W4$JS#eXsKr2i@1te?L9; z-27ZOdwTr)`rOy!=fi)Z$4|enr#K)zOFf>ce{=eO?{Pn!+`jDc^{+Naum1CzZ_giR z^8e^!{oL;C^2>d#L3;J`-@oq|m0>;W^>2Qetba2%o5N_9$8XWU`DKaz&GNqf3Xhx3 zXH@6$6ZLO?@$+Yu$Nl`-p4aL<`+MI zPV%^)KYqC{Q5n|b>-Y2LWKWNuKN~!rp?~j2ulXL|!{a09@2m8I_5^--rU0}5tcT^_ z=_`C+c?mIR6%Te6aFEA`>)-sMw{h~U=lHJjVUfH<`PG{7vw_zX2q+8S>_hTp>HK_% zytjVAxarv{A4lcKXyrBAW%4iXFM>9%G5+Z>!k+^@Yeouq19(vf0UrVXK`Yn4fqy$l zz@A(Y=(77RJX+qHzi0fSV}<8}e8E`ZBY@A)jF}C5)?kre2)te$wBvxAev4mOdJfVG zcs9tN!JRE#7Xtq#Teyw0OwVqkgx?MFrsq$gvR3pQ2K+2`a_L$O{E9<`mjJ(r9UHoufj7(*ej@NI zv~#!s_)vC=>ADQ~pkBi727ayTe+c-A=ZXBAz>m=Me*wJT8j=4u@P{@CPY=th<@1qh zczOdjdD|0e@{ek}I}GFtGDYur;Q8#d(p3sPT|1>J;EN6x`Llq}(9Z2b;CGxP@^=8A zIYamZz|z%#Y} zOa%V*0?`uz{ts=BRlrS8Bk^z~9Ui{WkzN z`R%~ZRX=kFaFc%t_?V+a|5w0G{zu^dI$Pv>s-3X>H2F;6U!E-TqkwlmLUPDi+${VW;PZ=x-v_+ueBn<5U%OuTUxB|>EBqtie^e zU$R!-_W?du(_aXDZ|(OF2Y!f_<1xTZz6|(&hs%A}0>4N7#ZACZ(ec_Pz)Q14&sD%@ z+V}$aFJnahA>eDZ++PF!*({NN7x+8s7kvvntmB1WfKNP7^z7YHUisq@^zZ$y^1eUt zd)2-V2L6)zCsTl5r2fEs;HKwT;48Ji-T?gIev+PRf$yPy-NV4YIZfoB0-mFRfp>wM z{KvotP7^)h-R0HtXS=4eBk)JYi`)UgP5uzzS8I8V2Y##iSJQwGI79ST05|zM;NLG7 z`7?o=`~|>Ym@e|S0yp`)fzMI@@MYk8%@jRv0^fds@SlL2eA_+bbIa%aQ|0|3z)e0E z_+kxG&I4}p#{j>&Nc67(Zt@#|=PVWZtAU&RjllQM75OKC_Z%erIpCQ~gntBl$|1tP z1b)Xx;hnXeS-zS4-oUR@JD&&KwIaU=xXCXCe#}IXKN+~m zZvlSo2_kzqWw`4&yxoA$8!d7pfSdktz@I)+eA z{}G>e58(eS5dFh}oBpxDAJF&Jz)ijh_<>rlt^{uKHvpg3Thj9^@UK+Q-++H2;-Rp% zKYQN%2Kk^P@ZYMQ0^s{?5c$!-uh#sJ0MFC-s1dOH=z7} zz{l%2>u}(ve-iN5G#*+B{8TN+b`N#M^clzjUHxXFJF ze2Ko_OYdX(bBETS{ei!AmgpY?+~g+%|D=t`*8(^BmB2eRi~O&EoBWl)pPMi84+B4Z zU*S&yKU4kMPl22KH^6^YAbNWDlvm3ilkX3Fvet)*z)gMz@DFr+QV0AzttYF1UsNyY zxCFS#Uj@8D?c@`{P5wFHAFBSZfSde}z%R^~^z5hg-STJ6QNj-dPQTaZu1UZzRXaQr zc>YR}uL5rJjlkDdiTtmDoBVHpr>lSd0&tUm9r(~v(f>VglTXunXZdip>K_Q)9`J7`3LgvH{0qRhX}Nz2+~mIoex&-Hd-Rd}SUH+}ci{C) zMQ#Xii~r{VzpGH>j|6V=3xNNnN#vV=pQHWD3BX^Bi2Mb>%}=`&`0RNie>?Ey+HUUy z{;(OO>ZGvz*(pZ8_S`_8~GpCo*L;M+AlhX6lmipb{!KeSf(IN)h> zgwFt8sP%af@Qh_5UkrR@FX86^Z^#wC4Y=L+x4`Ybw*w!d{n35EBSS;(`V{!l3xxjw z-0qv9?cVa=?z<=O1p`ITKEO}c{&x!S8?>LB4}3{P^hAK4HCA{9@O>8vZwlZi06%oF z$e#zi`*h(~0)Iot&o=m!k+~G{wCqi0Y7h{@b`h6{3pQYsQ;GHPrhg6 zVs>B;;FC@fxkG@@(tOSVK3nU_5x}!`9I+^X7XyE^O76P~`1WqXHv`|WR`>53y(O&|; zNBg6;{Y9VU^8@Oy?hgF?b49KX@E6oR4*iazZ*@F=B=Ci5&ldu}G%WY60&f1{ z%!A_7KV&a?e++P2=eZpC+&q!52fp_v;im()^>3E~xAmVl05|>Hf#0L$wFCGG1LVH1 z0pF(Sd>8m1)js?L+~#@Ps=jFXs=eA9_$?>Oeft34I#qZMaO-yt2X5;;rvpEwkLXzl z{F)`gOMnkPTX-FCTW@wE@Tb-PI~}-{%SFJ~>-geo;PczaeSZgh+hM{V2X5=uUIcD> zUI+fk38Lp;z-`|DTi{P)vw+RyixsxZNNw7h@NYJU#tG) zy}&=3Eb-*i+5891x$4%PmLwDd?r$~B+0Jr*(54`88B7X#Ms}Dyb*K(`_ zet`Nb8-Q`FfN#)o^w+?zJ4T-C811($-xi-Id^zww`o0;s z`5_yC+j^d}fIp_=nu~#-pmy~d;CA2b!0o>G0k``;0o?BUJn*sOw399(x17>|~KU1o&Sz2_FG`j*k1L0Pn2!a{=)ClrICmV~OZr z2|Qoh^=9DH8b$sh;J;FT`#Rv)s@=N-x%%Oc0e^b6=zke_yW@p_2>eX-H@*dak@|b> zw0&6qpReP-y@6+Gdl&#bL(8=g_*Kd$0l!JpaTM?m_5Cv7)3hI43A}H%Jnv@U_ba~$ z_+jclUk7|}zUbKw{70?l_W)l!LF69=evtYV&j8P#B=WBSe^vY6cYz}>VBlA`6aBfsKW;C46!3jI3ZDf0 z@I8bd3H-!P!jA@iUuWTEz_%YMycT$|jw@FKUp!ajHv+$2?f;p;pHh2u5%3GtUR??N zuNnvY9q_Lg%YAPH{<`)r4*>sJ?bs8*|J7UcJRiVc1%8Cuhp&N~o*#kt)q2}i+n?2| zHC^Psy#jbY;Po0`8VTI=(DB~xGX1Ak%KL@DPtkf`62Mmg-&`tsHUmFb$3bTSKUVF= zmB2IAeqIB7(LtjB_rPCSA>88P_FT44wzJ;Hc&YYZV}XzDqxk^5X`}FR;N5f_R}K9B zeMSBx;3j`6@J?C}uLN%6_UnOvrSZ%^0XKhR2k@0@KVJkspia{HC2;dkegSTAhTV1C zWBFq8cBTaP^oxW#QI0Do|}=09+YGc*9VxXl{iw@ec~7X!Dr&E>$Kt`PY@ z1D~pX^7FtCRQvWO@JG}S`55>;=ZgMsf#0TfKAbC`TRvB7Khg>KZ}tV>C|t0Pu^C zlyv?X_{}{s8hu;X8nL-y-}C2mX@QgYSWNSNSycf2}@@ zJX!SY3*7QyAn@O4JeUUhf{!;YCF9O_z$Z8x4?JfAQN4W0k2no;2GfW z9Vqf20=KxtzkvU`S>%5KUZDO=r$YJM^4Z$ae#o_590>fw?(+TNz^xsP1%A|gkzWMd z+EE2?Ye#j!x9E84RN&T*E(C7vXdCd-&61wmfm^${ANapJi~LK#P5$q|A5_2LN8r{j z!ox(L<^R{S3gC}U zk^9~U+}hEjz+XI15X5j`gWH~(rA@MpE3IuE#|=W^hdp5Fnt^xOg5((^EI zOV3|`f3QT-|0-}x&j-LQJzoO1^!yCm(qnei%H7h_UG1y!={kPz3*7EI1i1Ob!-1PW zTm;Nnse*%9*{l>3=+q$eYoqx9SwR4pA1fH(*r@euHss7kt;4dg24P1wP zp(5a4XumKG`0+ZvUIcuLzApy;^km8Z3gAbM6cd6n$osCqpFUjpe!wr$xXr=94__wo`M?X+{}~JXEltl6z$dF8wh;Ky)-MC^S0wj6 z4)~-Q!cPQ#+#2Dh0e?ux*B1aUUnuh1fzRC}{2t&7l|Kf2qn5`Dz~527|3ly>cM<(x z1K**3MM(Q+E5|mci~R1uXJ~!s3H;N8MgAb*S=v7i0sfAT1M`5d(Dry3@HY+={S$z% z=qCIK;IH)-UIBcM4Z>Fee^bXPCjmD-mjYimNc7wY{Im7KZv$?6o&`Qh<0>Blzg+cv z1^kJ9M1R}S@@nOkuKrsW;B(Zj_6Gix#@Pk}U#oGv0^rwadX5JE)ftkG(}BNujPM(P zPa7b7djNj~_;Y)S{Hwsb?k)Ta;I^Ntz1kbgpG|7FX96!;AbQGy@2~l=0r;AgBEJpz zyMjZ12EKHOeDEyr->QB10{BbXuXP_I-?Q}Jp!qNmc#YQQeBfVc|2rA@V!dAp@JE$z z1pe!^kh^{ZeE3Pi?*_hgKjCiy|FA*$kHCj$f4BEo`P`m&j{4z4fzLftztzHP zfY0kA{50UxRnOJHUzs5Cw*WtXo$z~spQZDIj{~>)xfg(U(R_Xj_}dD*qVpu9~17z=x{8{SNSpw14?0@LuV1-!|%3TK=2<4!{?yT{1t^ z+bZ(+0DoQmmWP11)Bf&l;Loak{s-_pwHs}yOgntQ~e%sbvzW}%O;(O>g#`33w_SgFYKYF9+83_D-ZAS&bf6)3o5x9-V zW&?kzzvzzue@}T8@Gq6G1|B+4^lSp&N%?uedn>;J_#ovs0UxIPZs6@p)6iQfaib~n*~JMc{HXLkUX?M838OWtv`@XTrQYWyO# zYeRrP(M#k;06)G!_$1&T=y+i+@S_Kbd@=A-+X-(1{*pfLNx*N}UF1&%erk8&=Kx=? z>AV>DEj>lv_EA|ryszWR>p}iJwLjZ|f3&aYxfA&Q8ejSo@ZTIF@{a>QWs~sdfM2ZX z{2TCn+KBwyz->P1AHdI0zx@l~U#cDa4*1RKqCZXjM9ZH?)IVttyin~%SKu#d`|1gN zx!SS5z+cyTV*aV!_nm4%g`Wz1lIG_*z-u*~7XyD%>%*16)vXKN41CiN zx$ix|7ifKW9QgIeiu{Yf)71XI1N@UyMgBA3McKlC0N(Q);TdX=t)4u1l<+-)Pt*MD z5BxIa*#UeQ@Nc?`{u{HegN*Y5Oa@Ot3GwEk}bey;XwmjM51km$J`xaITx zz_)Y}`M(747lC(Czv6x152#)HH}H{*ME~d`<<;`}51P-jfWLd1$SnbWwzi{FfLC-8 z`7?pH)%tK5@K?)4{x;xeo+SJ};I_|VM*#mT@IPpK`vmwMn*MKroBp4HmuP&T+iXdf z0@udyGFW2_I4R}Yj8+QV?bBZ4TK1BWJ7lGS3=C1(XP5a;Q zTuHCx+ifSy`-6a6{tN=XN!#f-;3hv2cw5zfEO0B|GT@(YlJu+vZt|x9KS}+rZNM!( z*8uOkQS{#j+~gkt-Z)(3-v(~=;UB5X zUfW4AaH|hxz_)0BcQ){&)jnSrz<&pPu|Dr3z)jENz}Itz(DgQOlYbxhjq1<*0Q`Xi zMNis%d9{2ltCsiufFGgdeh~0m)z6s-{QJS8XBO~%+X}b!&UW9U&J|t_@*VmKZv?(W z`nw}-VhisMfR0Dse zzi>PE)Sm0bJmH%`zT+U_=L2sZC;Up_@6`yu8Tc6m!tVk8xVDSOfv+AS@-G4}(ti9M z;Q!Qgeh&Pt-lFFx;6JJT@1XNPmJi=(KfV|6Q?z~_0Q_f_&jS8QzT9^d@cs4uRNxDn zME)q?4`d221%98_pW}c(tNr6z;FoBAo&mgzmdnM!yJ>%L4e*AuB|WzS&pluG1HjKu z6aF;tKkO^~72q3t3jYxJxKo6G1-!rd8@nx(&#fHaQ~N&v_{Yj81HVlDpHkpQXuDev z{HeXiY$32!0*PSluoDMv1 zs_;tS(=~rD0RC^a^V@-cqyGHkz&ES@kAPn_Q||jE@DaNU@2K&0OaD{aKMnx?C+(+Z z0Ka#M=$Q}vZ@q*!03S6=_^*JE*8IN`c)N}we?RcwWeI;3_{kavcn5elt+zh_U!?tY zuf?Lz^5Gb@&jrAbR6Bem@UPXrRRJ$hyRjMgQq^-6@RO%WI$i@_JzV%dfd5h3#W%q3 z(YR9^^;7J*-rqy?bOwIfe!_bLKW3HigMnYYM!4-8GyUT!vmY%t4PaXsLGqv4)2>bvomu}k5OwS|QE`|a>Uh`oV@D0kV zfWKBE>E8nUMwPz__`2yLzXSMz+An_wyqCt)(j)RcOaGv?^8O&;udWw95%{v@!po6s z{oDlH*0XK{K0({*wZK>HBl_Cr1VFc>#QJ0B;07cTmV(#|QB90{A5X{8r$7SBBhmR{(!0fWHyIe*nHp>#f-X z%jfON3)M~--$UP@4t&JEA$Q#j{B&(!w*h~3f04fz_72Ax-Y*9J*>d3(z$fXrpb_|w>c_4JeyjScTY&$d{9@ow%#r&(5Bv&FFkNo}Z_`fr zr@+fb3jYE4GPSquG`}q$`cD=4?!fQSeyT6<9_r^01%A#UqUSK+pEe7h47{j^@Oi*r z*Kx>F;PC^ z`E21Y10Sq$fcJoxREYc+z{ege{Ab`#^%mZ-R9>xo2dF*W2l#cG&OG2p>UeD;@QvF4 z&H;XOo!oB;@K02}2Dq)0uzhuw&fE7BJsUxOgvJei3w-%Fk-rVN{eIiC!1vJj#@oQJ z*Zlkn_-D&R|1ZFMYdSlY$>)~-vvhoS0Prn6Meb1G-Squoz%w*GlYlp?U$F@I{NqG_ z9q_xfUTp@Rul?ON;9J!XvER9}=c=A7`kw;%%@c%w3jAJ`-$UzBRL=n-KM;7&Lxs-( ze!TJ;;O*3ooeliYI)2{{e82|L|2S}4m-Z&`6_q0YG4M1kmo%*x_FThH5c%%FU)1r@ zAmI0FxfB8aTKka&z@KUm{dK^{t3A08c-J-}e--ed>xJJ5+nEd{`xK8qgRWb z)73s%K8&3w{6gSQsXu=`@IP)6`G&Yvm#P~d-3 z{Ud?fIkZ!OU#|V=QNT^U1h}0ayBv6h+JUoyuhx9H6u9ZX9=PfMJ@8AlpLzrMKh{e= zd;omz!NR`;zFzaW-E#Td^7EK#dEXtlrL!;aeY77R2)t~N=s5!T-nGIP0XO|NzS)h$ zM$kXgUjy<7>3z=w{_YCVe+6*Ue-m)i|3~13X`<&9;F|^s{}8z8{~EaI{}K3$Yei3A zP48~>mY=ta$_)i>`VRwc`o{s^Py35y!24)F-U!_Ep9I|Wp9=hx^^*RZfZwC{y&Jgc ze+;Ccd{5w;_YmF(_*flR4hCMJ{qIr0ZT-g@;IFFRvKjaq?Jv#;{*boo zD}md-fSZB0)%-L&ZO{AQ+45YEgS_n*cnA0s+D<+PE?PpNpMa-nK6j`W3A^t$wex!c z|Cc`R0l*JZ|2Yfz5Vd;~fIk_Q`^^OY+@8XhE9dV1Awll{CwTl%9zV}A0EHQ<3D)(ZjbM-^@5-4E|2f;@jE@9d!Kg|>Q4}nkaF8oX2f9xUr7vL|cU$IA%d~W$O zPxEna;M4SZ`vJd+olv@l0Dr2V@Iv5s%@aNr_yL;#(||uYM&#!K|3=535#WP5h789w71;0e`rU@T-6yK1KM=!2g~u{0`t{8sB~p z_=Wq3{1d>3YrXv|@GkoPHQ-OQ6Fu(%|EuQnr@*_MEAsyb{IBY7{{nnLp~!brzs%~} zm7Ro727cGR!jA<0k+!c0@TbO!{CeP~=QQA^=OW;CUfZq8*&cQj)$adKfV}B{&f^6n z*;c>vB5+gncjcyEt4ZiXPo7=p2lWfzdc1&`Hs8>Xz_mGs+OCu@az3;C8hIOP4_wVc zsH<{I&sde;*OTY;WU>HVgFMdqA9FMm8VdYj<%Pi0RnK9-)09t8&Tl#YXQ`eeJb6yf zdX-=3aZb;8<;MU&TzNV0lawz9exmXQ<^0x9&k3G9r{`IfKh@)$p8J$<0e+A2i$TBD zhbujK)*oua{|kk-dz|&Zr+RJ!{*Lndfp=6r4*_qd{AuO?FcwAj)_vz&x=W?<3bqDaLoOCGkg2y{6n;kYg_LaxE zUVWp_`z`P{l&5Rl*UHNbUkBjdsb9Sh@Cr4Q`vG69{2=9=&z$}&`p4Iyp8PO+zg6WA z_c*8j-^#}W|5o{Q;4`%UodtZN@`cJRJ?cJ$mU{A>p0`wgy~jB{x2T?G;J;PA4tRP7 zeTlA(z<*KsEy^uDr6L}>z?0|nWU2g>9_REdQ2o~cpR4?4;1?>t75KTz?@^BFc_e_p z4thRNJ#PVjR{1BO$Lhm3o;*Kqojz~6c6?SpzfeD?1Mtt4?+Lu8#xeE+-d%a7a(mth zn*Rp}@FLJNOZ7|vK12Cz;478S2i~MSq8!sx5y01jo^7h)zRv?6$Q6LDmx1rA{B@7}_kG{v zeBU=!{-3~~QvRiKdtQ64A3b^2AJY1~`|wurH{1)Yyp1T70bD-w~)$=0ozbSte^t>k7ANnGI{}90UK0&_7)sM^l zG96F#0{&~|{gvBu*?oru@FLLjkm{KN{6Xb&LC*wj@5co2HK6B1)w34(`^q;5=-C>; zuLnK5s}po1@ZFUE0rWhgJ?H7+_P9jb<2xSbaw$~(9{@j8`RBlYuly_E z*D3!=xs{`(C;dcDmDm$MJzYJ{>FL|WiG_Lq@1}fz;14SA2mDUuhbYJNvGx92@3fUgGqL%T_Nod|re z^39;f?z=UB-w1jZsGeJZ&sTmM=ozHX^=IHZO%(b&@L#liKL(zodcy1E`K(-;mG=U^ zR{2=vTpvczKN}~`1>QyLNu#GnyE=EnGeF+-oab>a_medrE&{$@`4yl)(`gTdZt&!} zUS(d=I`}_vAS}TQxnOc${S?b!X{N z=wHCcEB_w&M&FKQ8+I2|BA6q!^Z|aC@`1p=S3Vf{H_G#s zV|qq=@|-`Go~a(^^yK#759wUZ_3kAa7dlk=QNU}J9}Rqk@+HbGJ%@?f(22k^m2XmR z`Bta-cA>{Po!6=UOMqXa{5QaND8CN)lghU%w{$KE$#u7KOV2Huo+mxd>1n^0$nOB& zR{6`oC;q?Q&I3NGDt-SKDMp%6iYOw36oXO%Qer`a0!9%<0YwxNN+2kZ7y<@GH(~=t z42l{TORz@SRV+V6ch%^M^0#I!D~cr+7F1-TsLNve-*?XQ;K}10h5eWNVYoBjdCqg* z^PY3d+?m`1zFzn-;Gcpsp2Em2UxG89^hS>V$KwNHbSZp(6kZ12R&+$?&-O{{){N!hZnI0&fs&ZM%Loe%LZNz|Rn$uKPIsc;TJFCyCDo zP9BvA-xqwb@TbA=@pvhitse3CbdNvfackE<@!mg#Zvo%x@eZC((*^E@^_PEK*xTcl z=RCBxt?(T1F2c`g=X^7SPX^Bt{s#Dw!e0O%3f>^LwfOrv`{*Q3pB1w_TsN5Daf`nI z9WY7wB=B>DKMr0f{6X+iaKE}zjQZq5DAPtZ38XFMYzKTtTw)ghjb#aWKL9pQ0{^KjIAwD2tO9N`nePZT~L z{4{XJiI0a-uBW$nY7kGM$1R>);4@qJjo|Zze+^zHd^0%j!^(IT$933pt*1}+qa{3E zZuPjuGj#tXj&2t|7<{GhOTZrx{s-{Y;2O^}0sfBoyaAv0g+B@YnfP3~n~VSJ0B?G! zd!OU&1MFN|3V#p06*%L+9{$~hzw2zHzMhZeIS+Y0#^aXfeGhO2h6!%~ezNeRJ39R+ z;eEizdfcyfqQ|Y?@1V~Y{xSG0aJHB2I?vOae>K{*(BtM`n~495m&L;0OC)i0qsPss z4nB8!+%p(ILArnF0Oor@J`^p#b>jVM?-}7L4S?#d~AD6;QNjV9=GlBB=}DfJ`DUE;m?B? z3V#Z`6r6crJQsU}ro&KCeE= zwQH&HE5QE<&U)#8ho|@bAMm*Oe*vF|g>L{~4Nm`T#9t$v?R_V}zXfN14b5=v`j7Bq z!5c1g?`yry13Uwq{)O=AA$%ryA8`7gfOZ`tyr&Bz8YjFX^wWf&3jPP-w}9U${4Ve( z!7cv7;|H&Q)(C&^Bp1+k!l`ey$i1&VnE{>?;CaFi9^rf!31_`G3(sEW^v?>X{$1fy z8#{e;W$O6b315Kg{KpEX|0%+E!w0is;nZIr;CF)CI7uKxTfOY*Z97_?;ohtfek1rh z;)9Qm(Z`$2srQV0w!u z-7W&fOIPvf+uiZ*!ZX493O@sUfbcQkM}uoTIiB9)DMCD>J#Ou~4nAXrF9n|{{0Z=U z;g5pP0@rxX^Ynf^mw4Rb`362$2>%+qTzLC~T|Soy-v|6AaK_Wu$)iVwb6h>``BVFS{~N+bfxj>Q*Pz~idV2GZTDmvB^SJq61E1}}uLf_35B_Wy{hNa~jXlgi z5B}{uZvKzLXMf?3fFBg_&kXP!@p&IUCkkH=K1TR%hr~g}%NfEW@H}v~7hi)!rNXiM zjIQx`sk2W0f3wG@dwex`)0p|kk!OVS_wBC<-vj5pZ+rfBDYh1MeC_Ei|2@&J?}T>+ z|5r^i7rq$0E4by;;{4v(M7=z{`DeoaD36=}i|{!{ z`19by#lMAB6fdI#e5UxM^>F%gg*O5}Kj3pofG-!H6X0{3@MFMNiqHOt|B(Q%5ueB4 z^QQ0z!QT;|e(>KC;QtQrJ(svV*?6#iY0}fZZY8`C_zI zcPRP)>7L%|?F0Wag?9&^B0jjB8F3yJj%QRGT_`>`z~^G&*MeUu{=m_70sbfPc@qUz z39kWvJmB+efWIp~ZF{-oeIUFg_~+o(9hT=Kojls=>680&@Si~`}@6t!nxlYD*VR<&S-?^-vr*aD)96c=X%65L--rubHt|+ye{(e$^9Pq z5|5kzQN7(8*9t!z{6=uggT={lSS1|0^XOU6$MVx;59j})r{^iSy|cm7TRf$`o&Gc7 zCE#1YEgtj1*LP9>a#zm!(eIZW;r~Vh&K1t{^YewX9~TPWKE%~m4sP+-Pq?Zbj#hel zYuDq5{{i6-g0B{z=iu{3tZF1c1pXmy`<>{^7cHkd?GtTw!-{|ShzXtyQ z_PF_+2A}VRp9^i$*7L^t`9Xrj>l-BTsMrb#@=8 z-ynPv_!e-h&V0CC|J&2scqn%^(eBr}C+x4+;L}X_OW>`+>CfX!hX5ZSJ}vsX`VJSq z2l%nz>VIN@PZ6J!;Zq>|IPfBH`VT`MuJ`o*ex^$J3KV!lIQKK0Cy@KAku6*W&9D0< z&*uN?UhZ`p;q>V({Nu?^f245wOb||=vH-6L@D;+jpL{_0oW(Al8sV(>1K}5Ua{3>@ zE&q1uDi7DMTHyl=^G2Wc9=GxTL|^CAQTU_ay@Y=c-dFfm@PQt;emOVY`5)tPt2h5p z$A<|Y4}PY{&F2c7xJ>o9`Md`G*}`7{FBRUupR0Gi@IK%dd)(^f{A-InZuJ&IUoQL{ z@aw_ZFZ?^i+lBuP{q=zG_rRa?xP8^y%!MDl>2Z^8g#Jt6(%<=X5q=SPcaNLTq44kLaq~IV{)?9^;m3dv6W(&5)1M%` z3HZt2?3a6ypRW@qh6$-{a=< z8uVqt*MMK2dS<8}wDep9FtYcq=oD zmnVep3I4Rl4e|a(FM51H%tt`~s_^5%-|)DhMhoKq;^hO6o6jQXKNfxo_(qRgip!gu zOBDUv)$IU1G2&daqcuVls;2fWs7@z%wS38^NM9;@U z;rJXYoa1e(@F|0x(YYSCI0qu0Il>PGKi}h4FUR3!9ygy7=obku0$<{B^Vu(c*m9G{ z&F6mTZxMbU_)6iQfj=O8Blz`}9^mwO&=rp|JiQ<10O2!nf#4|dDT2=#p5Ed)8S#u4emwXj@xkU5&G+UY!>a`xXdRM~#2I0%WZv(eHbaK|o|EoNGR!sPJh^swr{u2hdLVp#07Wf)) z^?xA1mx6yQK5bk$(NCT}D`s`bTg#iAfaByL`0OqGPvGsr89y%HMqNC; z`6u>tzWqFI`Qd#gvxGM|%IOCS?+HFscz5uVz%`!JJ-x+~hj^xW+~UcH&pE;;gO>#S zFZA^0Uk?989ykBR@VQ#}mEaWt{}loLr1(4xpJ#+W2>w#Q=gk1G6Q6bP`9?VJ>-D4X z=;*l7@zUUrZq0G^6MUM2Yk##0@I%DsAo%wd-W5Dc_{rc$3LgP}95~}CaA8Ly1H3?d z=E7%&@DlJj!fykgC;TSxGH}MTAL6MHJ`?<2j~9d4>QRqVYkZBzJH@XrHyAIk2KeXT z?8iF9xkdOE@U7x=tFw<9Eq70>U3T7{ZU4nfN8vf(1BH(OAL4O~|DvQz{5`2~w(DHs zcSFAn+}d?`{NQ=Zt-^U6c|dsK8P4c2&mSQs-+0~A+kT*Zw$uMZcq{M^g)adAMELpO zo57jSj*(lw_4LX89?mBl-|XIJKYj|IJ%oP@zPIqEgI)Qy!qdPzf@?e(0iG>Bz2P%N zIPaf1LVWmr#AyLuEIwo4KUerD@IMGYAN&&GoR4M+c!M}C*6)nx7U9RZv_+42oPA|) zJ?C-rDTMwTa2=n!-{J)1ht6@Yj}p%De2VZDO`Lv-aO&?AelxB&{*UmU*r|LaoIZ`P z;WN&WI6fXN{IDxsJZB51&urm5AHG;P|E{D$`0EEb|Hp;X|5@SmuMyr3-{Wr-ex+?> z@sf6%TeH3NZ!5e*zSA8iocb}sw;bm5bA?lXsqlfhPJg5DidK#<5AaIi-OqFSI|KY) z;a}qV_MZd%A>l)@gML(a>2a>!zX+edx8qL>|02`z7laSM=cAVbyhiv?)cdCJrGuRR zd%_RC(DB*;|4jHDwxfubF9ZBL;g|Gw`t1SUu+qKH@wR1*d%b&rrwiXS#OYfHcn9I1 zw|DwZ0e-OX*RX%+72pGf-+_KSBEXLo-UHj+aRJ^aa*L&%*Pe~wa2SzeSnc?pS$Rii zvqS$9ULPf#x?#d`IF3#cUWwN@jrf)S`J$))0^!~8`sx6`UO3~a6wcRG!r9&zgb&8+ z*M#G66ul?B60biMPJe!1%KX!xzmp=T|9%GIg}gHPDvAyiPJiB~llqa+4;TFy@Ug<_ zpC_FDysxMFFA_cdZxBxZTZPmAN#Trtjd1FDpH6$bS^VJm*)00L@q4!LKAoy>WhW)^ zLeBAaK!A4>PJiB~lRo6bML*b87o9Df`eNbKFBMMxO##mPZfZQ$0X^?eX?mQ>BqRON z(;H7m{Amr{6Y9y+g`aAIcxfNtorRwUeV+h7On4K-HzdGM6rKkCxB#Cdd{5|S2KXG| z{h+@jz^@X11oSrq_^rYRLI0-!e?<5Y=${Vo7lfY<{aXS4zHr{(Cc*)UB0cNEWoc5&UP&q&iJc@ zGyYY=x1(K83g`Wc)(Afs?R{N1?`!m~aOQ{ie`S6$(Z7xHV>oj5d%AG8tDSJRtFv&{ zdx&t3H{M^C^)jB*L_ZL5P7Ltr!Wn;waK=AhIOD%UIOD%YIOFI2YZ?D&#JO7ZOTpI& zXFRV6zt0Tg`Ze&md2KjDn$K;ew1w{XTYP&nh^{d8IH6c@&h z`vQ_*06tTE=s!m|{dxag`q2MY(bIpWaQZ(ioc_H3uKI5g{aVETeSkOIy?*{Xm|?uM z1h>ye_7ja-oKUwCJ@+&1g|ol91$+((=nobCCF02r@EqaaLO%*z;~yK)=LzS2Z>DhW z_vQ%aalZ^)>%C0$dtpC$weYs!6#<`HM9=eqyMzyd&qD$Jgzz!YuL0NiUkvD97tZm? z`!{oZa{u`s(GRh~7B9Siv+5K0dl7Q(Zx0FZLxt0y_iv^T`B>3&Kgs(ytDf^^kyFq6 zF_Y8(Zt*AQ{g_q%N}{eb`?- ze|}%|Y2}tqF?LM8+{w#KMH65 zo7zQ(c%eV@zqfGazoT&G{~+N!p7sy$LBiQzCkSVMohqFDl`EY6HBC6TuaW?tFP!~& zWq@BRoc()SfZrpW{mc8iGym-0zl)yyJ#gzz|9t5q(X(GZ3-E6OKDs={&ZWj*==lFjr;PW@pbGvv+IM3_;5%5_rdTuYD2zXk*{~dB6Zv^Dt1)^TvOPeiY72Ij@51TjQcUIorkg5XiazKU(x0 zZzq6jznm<3?$1vb&ha@Z;4@A1JgyZAXaAlb@cD!2xxc+aIL{}q4ft^W2hGo8;#+zpApV};k+p9mreF0UOpE+ zx1)ax=kfYy;mi-`KcPSSvD@CR9&+|$A8^eN=Q*LCJ|hD@X9j#YZwY*s(^BmAz7 z@vvRZg|l7zfNQ^VJ{J0LoD2{j^1VM;^+J->ffM^6Ohxtd4RVTPJhm?LLcV;2+`AjSb(1-oc^4TMg7kcJ^dF1_?5!x z&v{qqpFrLo5Ix()c~?~b57Be{e-z-K38z2jU7>#-;z@7oo{%pC?;xDpOIP9CUN}z+ zedvFZ=;?o^aQaUcPJhnRLjOXtM&eTAO~eRhE72-Rh3ZzY`Zw->%2;?EGy`#fg~=l!Aw2|pD+ zLxnRxoG*y^>4*L;5VJA z2xt8F3THfz1o&Tt)Bhde^#4dW^_=fW<8Ru&em)sbn*i@1oc^5Gi2fY^$B3SBa$Y0V zPZ#|W$Y)7_&lgUA{{0#K*^hUL{&Dy}B%I^<3E?lAVZ6K`oa6a5;T+GLH%a5!E_%kZ z`~I#T>KV_T!Wqy0!WmB&;f#m#EYW|lt1cQL`a8hK38()g;q>RcOzMA?=;>b}oc@)< z>CbtY)PKF`>k$8@0Iw51)C}Y0d+?_5a~sQNU1~I5aPr4`d7MfU&i-lzu0HJo`h@Tn zIPUZe@czQvK%Wh+^$rc_M+oO}bDVGqcAD=>YeHoaa}) zgmWAY5YBOUtZ*KehYM%Fj}^{-&lAqi7c&EVj&P2X%L4pr;rx8@#{j=wcsBb{IM3gn z5YF-QVt~Icd@TGw6wdLsQ8+(ed=ub52bB%E3lk?PR zK7SHDk1x#=_4|vwwQ%OYlW^w0yKv@zfN_#toBDM~Kt z(*>M*#?wRi7p{1erQ8a}OSbTzz-I{O_$(35dC>k4;8zIec)k`~+jXPpInHkreoq4z z|9t`ekZ^7nPl9W`&x)Sg#f!o@{@)7tyeoQc7at4fdCQjppRYyF?Pa@gZZC}wbotPB zH3e@Fzip=y++Ow;&h3KpMpDmyKTPxuO%N|b0`(p*de(c2aMpWPz-Nx=8PA2n8P7uD zthZb^>*c(Z+TO=RPyc7ZHP0`Ip7p*aob|pN@Zmg`8c*ZSF5l$*+>s8h@w5>=x1$ci zxm{!ge0qwW+eLrjZFY0~0sgRX=9BZdYCb;{ zJ;%dl;mqeZ!kNzo-Rk!*^SOs`=BKT2w(9`ljGyzwYW$~&p7Bo*&T%qTIL`ym1=sz< zT+uTR7YSz`t`g2XTqm4)s1(lQLX~iilZU}I&efvl_^B4oJiiq1sR`)U31`1>eqHuU z8usT6@Ph<$ZbvPJ^LX7>IP=4Kbm_x>93*=7V-C3Hhx6sC&$NI~alnW3-_nQub-noT z^JZnh=PuE+zy2(o{k1ya^Q7o`d|V@(*9~d{KAd+~+qG4E80Sv`pN1Lr_XE^76V7(E z1=s#+FM77Cvv9VnSHOq!`m$XdCnLm%e60BNSm4^j`FrWZ{yk58_&w}`fDh+oHJ^mD zPX2#~=VQDj-n&1*9~Hg~dd|yA|NX$%i~bsL&daL$-MTvgc_-*w1b7?a^yj>+^yvwo z!vlKG%c}Z`qVEHr83A4*obhm8R`p*V&~si^)junG#`8*mza^aUa9&pR{~@5~ysWD4 zd~p5#Vm!S9e1LGq!}(X~&+(Hh`cBCI*#TZGoc+%ESLx4sZxlT_=TlYvQvp5aM^*g> z(Q|v@{HUsLdPx0zQqTECO>aN>sdcibzo$38mkHt}JHT^lct7}a{!;pX@9d-3MNj_^1AL=!&J&u}!x_@Q1?o)~ zPX7)8-c>mLIe)48pDKF#=LYyR;q2d%0G}_M{ktf@mkMY9a(-0C&vrd6`h$?SmjnDw z;fF&1VSsNG&hfx`QyC9=OY9JobG}pKyJPIw>M&1locp&S0e+(Jk?=Y_z;lIwfbgSqY|H=S=Soo3f=X|R4hb($e^z{EMz`qhsf6k|>{s;7O0g%)G zkN`haIP;$!;5ovX|1kkRUO4m5`C1t#+jW)bk3&8y0=!Z<<5?Nt4-02JoR`(&X@WX! z^@*o9&g1Ns0RK)nzRrsp^meB7C*MOjF4IM=1AKqsxQw%7-dFlJa@9q{M9=t73Gg$7 zbGzU?uIAqu-nJ_9^v3b?>FBBezfQOv+ic-{s^-5}{HTr}wmjqMjenJ-QS@?vzbTx5 z5BOn#Zxqhs)Yk#NU3g0iI9@nkE8~C8;iyZddqU3mdk6SH;f>+X`C93-9{v+VPycfQ ze75i(;KTV^>Hj78&7!COy#fB9aQbt;R`vft^z`R_zNshQDth)W4>Z)1r}e3yfA(*i z0Pi52{mc1a>Cb*SPW0rQuhsN>AeXi(_VmW_GP*FpFB3i(UgZH^A$%zGcLn&Lg%5+C z^T9HH?g!o$Jzhqi2KZ*-+|P49So-5I7q#i@0wAaVfdPK7aQbsTSo+ZabkWm)N`TK0 zJ{b9#7vL8QXa1K2_zl9Df6jMm@!O@kJY27P*3%n0AN-X7e@i&$-{kzL>fi8C*Ff?I z;omaA+Y0AApTG z=Llz)>;q=;^;eIQ_2_PJhlHOMlKk`?Tnnn;>3Z4Di>5KM4K%0lq=_ zL(p#x@b885_t;JPySNxXc}w9h!GHe%?;`wt=z9nFK;a)iKQzEc2>%lLu>qbZd<*nT zgtNbH5MBlSiU7Y?IP?ElfIlt#8Th{#;I9j3{%Zq#lkhj;zcs+W7tZ|eF`)i0^FL1X%zu7>&lJx5Ul8Dz2xtD61o#cYng2Tj{7=G}|HlIS zY2nQCD*^tNaOS@@z&8nJ{#y>L-@n{1wH40%cM{G#cNfll_7l#$9VwjoIZin9aI$c= zcb4!njPtp|uK>R|z!wQ$3_a(O=J=%l!=k?m`lkZ?dExZue9`nTf=`|3&jbHHz#C@O z@5lMja~^5+?BYhn{d|KOE~>G-#Go-!e@-=$ARYwr+hxfox)k~g8}}yaK^JvIJeVU;q2cpgmXLEDxChD=bY`$KzrLAQNO?F&-uow zuXeKNXwlbzpAg`u3f}-d=OI`B`J#Uj`YQtb8sYTk{N(EYsOaB=|1$ypzl77D^O@8C z75ID=(5K_V3-y)Iw-bI3c;^5=MEK*-bN+Dp7lEHH`U}A)1^C&*{|Nn@0KZW9a_Bj) zIOD0aC-L&I=#RkX)~5pedErMx|MviYM|ckO8w31b!cT;Ldw}mY$hCv*Is^Lj0B;xI zoju;kStkEKM0j(&Hz2@|5`H)IoOj*g^!=xbz76!8M_u(xL=SnydC{q-&l94bhXQK@ zyhiwi(60~hPlR6%J?BwpJop+nYIc+pkQYGDdDO|N?=SijpwABQ9O3lmJnHH{Q}oBe zpYx)tewpYQC+9g={a*rl&Reef+JJt?yybCxo#F@UO*`5JtUjEFTzT(+p7W5a{`7#J z^NLf?JYOXG8OSH+6<7T|qUZShp8$VSILGJO0Iw0w@yU6@84vk3(cg;r+h_lhXY68< zptEp(AKELx2MA~01_$`@!q1M}a(aO03NM6yfpFmHO5u$E`T)O0_;eUm1^6oAjQ{BX ze?d6o|5SL#$Ss?N<8nmQbg+Bh`q%o4`n`oy-&;8K1BLVZYtGkAfAaAG{rLgDAi$fr z@S~>j8kba)Bpsaf;$@WZcqhVmo$2w$9?$kT>oPRbDbhwkpGO z<1O&qR^@nZd`~>LRVAJq-z$Dpr$uw-I0( z*5qtMx^TwYPI!B~&h)tDjT|4xBl9x7?y*R{eYvcxv$I?o=B>rh@a1_Uh^phA1V3;;QV_X>K_2l6aAy$1;U>IFB1MVc&YFY zz{`cRy%oaQ-b&#I!KX_2A>gZo_Xb}rocXU7&hv`3!iT}9M)(Qf>x6Urtrb2V`c1;~ zz@vul3Hycl@#lTE{$+mrd7q6lKkdYa`AG<8elmnJKbgXrpDf`=ApUIO$AISuXFf*? zKMDG=!cPS+5}p7r70x`A31=P_3TGb5g)#&&70z)|BRmi7 zT_>E|d#&&V&~FmH47^S_x8H5TUxhv$Ctw_(Y;QZ^Y;Qt1+nXVr?adTkhX&& z=ilA06+R35qgvsNf0J;=Und-2M@HL(^L&y&>b3D>`5BFNr8Ra>$d`iaeAaBQ&S!m} z6^xfm@nQT~!WnYo`*3w#QMZv`(B&fgoC3g>(xWx}VRUlt0V4qh&t z^UG8S=Xj_T&U{u0=Xj_V&i1Yq&i2*_XM5KPZ-@A6g|oezgtNVM!r9(!!r5N_(GkZL z+nZ(=1>%MLL&TF1-T^#AIQuJ8I6sGE39o}sws6j0qw{?;|9PV4_E;dC+hdV%ZjYtH zInK+3bDS>}&h59-<95C1xnvfjs7m-t;H!kc2EJN2e-B?Hd_DB*gntZPD|`d^CgDGT zM``Yfb;myO<5Tc7;a`BK3;!3mKd-m>^h5t%mtEXZ)*$GyZDf{G75@csAm% z5zgc6I^oAbUn_h#_$J|Jf!7Jo1K%dR7@U99Vd=8|IuAU}F80L>`F!^>N(g6uGK4cf znZlW$EaBPk&lb-7^vCquv7H%x97CDbSY+KO4MUILC8^aOR;> zcs6{hgfkDTgfkDTg)V$KgY!m)C{F7f?#Lqddxc#Oz zbprAi?Mb{Ogfo7BUUeI{c_xk*e_nOtj6X|!7=O0##Yva=dYAC!;CaHiUn&qj0r3|J z=XK9g;mkvsaPG$z3g`W5%7yd(FcreHv45)+&ikcQ3Fm&YS~%OgHo$9yGyZh}UMHOX z+l23peDcdAjuRgD)0(*sBA3@S)(FgtNcugr{MgY!jXi9yPB&uG)a731|Kj!nr+W2qY~dW| zIl{U9=6T#g?}+_af$%Qi{yg%g=W)DL^n;);7tZs*3gNsyP$@hQ{Z%ELzlW^$cp|Q| zHR@d}d>`-{;roHF6Mh(Yt?*;PHwm8yUMIW^e4FrVz|&i}C)UhF{9yd;gfsqx@J_CH zlp(w;c&6~4;90`i-fZE+q0bS168K2rSAmZe&h{1wXM0P9v%O`)+1`c1+1_&DJrPfZ z@V?-c!ux|)31@y*31@p(3uk+4gzp${!r87`;cV9?;XM&2Z_L#oew~OP9G__|oq(ME zm@b^(OSTg}-W87$!Y6`f2%ip~DZC6kOZXMw*}|E(oB+=g&h{1vXM2l;)4w#p%RO%U zw>nE;yi^G1??o#;-pNUm|5pjW8}C&MXOXqScgGtw!m%5P)(LL|eVuUnY!gl&{+Py^ zY4Ou1ZBJ)NUIPDwaJD%^cz5VCh4%o@63+3KEu5dHa)cMaCr>!zDG<(hii9(sQsEp| z<-+M-A)Nk|!s%ZX;ME?-sY9}5YlZXgk!w88SggQ0;T&(Z9&e1C*=mz;?r-Znj!Tls zQrm>{d>~49rVZj1$C{Be;oQ%pdwdU9H2Hr!;luD=!sE?6eTMLt@Ls0JvCB-plO=o$ z^w}Oq6O-@c2)_uOGt%RG#mZ{r9ee8J;>e_igyszaMY% zI6i78$tsWg{rkAbcN|+{_MXT6e&676480_2Zi$H(i_;$uZ9I;ll++FJIEGS^kMQ{3 zN#g!1^0?{q@ZLO+W9TK{x!L3XIK0c_7LVkDIYK@Hn0(-_2;~M8@pFJPZ+D z>15GZ;nmF2y{1eo znHcq&G<$Z`YX|L>JMN_ZlL`xGmmD@d{;xQH;>_87GS8neeNt|IadBbskiJ9b?utA! zAn%iTUh%}D{L;z!MJ30dG%7cBZ9|8;`ld~uoa?l?adT$m{}Sb{`J`xOJ%dpthfSI| zC3oVC8HJN~t?Cpdf2nNFtQphK$)7RL!XECj8?JU>aehfbap8HExK!=lC4-%h;Wzr= zzlmrk?S6@GXHwID@{Gb+`TuodekoqB+TZJtA;~eEqANxh%*x8e))MZ|-zwf`!2dz{ z|MxZb38Z=_8=frwd#V17f%03KOP%=t(yZVAJ-1Yy^8YWYUGA{o3hme3mulDZ8xFs0 zZz(pOlsm$mGAk~$1D74z`t<7KAB+6mEH>)?$z3Uq9n%YE{nnvo=-g9>kIqeH`0E{U zta4H0jvGCS2TXT_ifb7PlVqIn1`vzmUH|CgQwo3O(`UdAqueR^B@?I5=#!b;@#RjM zI6MD$ynny&uj&gNdVi-4DbEfpxL^5)ca6W-7XKWf-U0Ua>-mk5v@5mly3J`RJK0XU zWHeCt`#ts>g?Fj%cGUQrdHs#r>h@Jl56UwJ!ZS~LEh4Oz*QGV_&lz%xzdAruKv-#uo5kLORccJ`d z)ZZLyYrp*#WoPT(hH|_nWaU@vLiwnHGw|!beHY5}dXHcKox4!Jb&C4$+J*87l=thu zdl$-Qq^SR%T`14rUHbLkw+rQuOi{o6rfFyUKPN@||GW$3N2aL1bQj8>m7@H#T_|6W zqP*=cceec{Daz;WLU~@#@yGu;yHK9LkMYY--G%b}yH~%wUGLHMCt_wzZ4y8HWfj`b z@0a)<_eXqf*YM0Un*i&Ie>sD(xGrS-54)bo@yu(H-C|}+T=t5ex#dO%N`N)O+Ch87 z&&>0`EpLA>XP!+EXZ$p|RHAC4{RtOF zvi+S>)L(=8ca!`z!hl2?SbZ|q9WUZ1_IC`a;!j8U4Zb*juKBaS*GLur!mZ{Se}9_% z-}PUY__^k9IO^BA5P#k`F8(yPO6LEd6!EV`{Tlxo zy9ggIt=($J|FGNfx@8=YF^&ISl;5&L>7Ho(d&D4B{tNI2B>wn0IJVIIk3;<$KYvI6 zd-2~Nh`$yU@%vpiL&wj(f%q4G=Q8Ezzh{d0KMKS@0bio^XT?}+{=3FIzEt_=?=AU# zGi@~fL|i^q{>%UEYVz}+nIisCs9*DcKMp+R?JpYtwSn>v?PU4a1LYsx$@2ESip8Yy zKe3bLPYjfQYA4Iv-{q$2KmHz(-~Tg*+J9HY<>P-*c07*!Wz{y9X`Tn!{(oTn(Cz2t zK>RK6<*Rx7i}v5XjZ^1uubnJEEl|GAPL_WlP=4Q?EdOJme21MZe{#H^PSt;zD9_(x zFo)WIC&cAb^3beli%G>#czpxzH=0N+izIVzh2yOfx6F;>5$Hd1OQ^q#+mWNRP-Sw#ZuF^+? z;_}J-`!If9i~9Zj_mG%t{imRQ+5DohNSyy2x>cd=pUm=NP=@mUZw{=yj-M+~zM+(_ zKzY?Oe)F+a&-(d#SPW8a|2c?12SOYF zC&dqzr|$(ZNELrJ>hH{ouujP92T)$)FF<+L&tj~fukVXNs`$$he<_3(|0(f<#p&SW30T!zilVuPsHU@#h><*x&Cs$B8z`?{LuJk1nSRjZKb0q zk^KLc|5%=6TA=DU3UWc)jbd$Ov5i>USs_uBezV*FtK*?-Fe^=Cio${pbU*e^y}|NVjT z6V|x$w+?n|{;%cl4a8smCGtPlm9+S$#1D=CsX+ZdG;uZ6v0|*X{>KCLubLcxu!$F5 zN45H=#t*Ik^+5fbcXu^a$^lF3e1I*5>mLR2TI=5ssDCkTgi-7N$S*Fh z_16aKuer+AUxotK{e_LPqS9Jde&65O|JcV2*V9(&m!+Lu*oc?HxPAWOTz?_| E8?fsJhX4Qo From 45409ff084efe14997e469bf4040f096ee9f9fb7 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 28 Apr 2022 14:02:21 +0200 Subject: [PATCH 093/176] corrections in validation functions & in BinaryMaps --- R/POST_FATE.binaryMaps.R | 7 +++--- R/POST_FATE.validation.R | 32 +++++++++++++++++++--------- R/UTILS.do_habitat_validation.R | 4 ++-- R/UTILS.get_observed_distribution.R | 3 +-- R/UTILS.train_RF_habitat.R | 3 +-- src/libs/iostreams/src/zlib.o | Bin 184896 -> 244552 bytes 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/R/POST_FATE.binaryMaps.R b/R/POST_FATE.binaryMaps.R index 883152f..95e604a 100644 --- a/R/POST_FATE.binaryMaps.R +++ b/R/POST_FATE.binaryMaps.R @@ -318,9 +318,10 @@ POST_FATE.binaryMaps = function( ## ZIP the raster saved --------------------------------------------------- - .zip(folder_name = GLOB_DIR$dir.output.perPFG.perStrata - , list_files = raster.perPFG.perStrata - , no_cores = opt.no_CPU) + if (GLOB_SIM$saveStrat && exists("raster.perPFG.perStrata")) { + .zip(folder_name = GLOB_DIR$dir.output.perPFG.perStrata + , list_files = raster.perPFG.perStrata + , no_cores = opt.no_CPU) cat("\n> Done!\n") diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 83a6ded..9691e9b 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -296,6 +296,8 @@ POST_FATE.validation = function(name.simulation stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } + cat("\n Done !") + ################################################################# # I.2 Train a RF model on observed data (habitat validation only) ################################################################# @@ -319,6 +321,8 @@ POST_FATE.validation = function(name.simulation , perStrata = perStrata , sim.version = sim.version) + cat("\n Done ! \n") + } ####################################### @@ -347,7 +351,7 @@ POST_FATE.validation = function(name.simulation habitat.whole.area.df = habitat.whole.area.df[which(habitat.whole.area.df$for.validation == 1),] } - cat("\n Habitat considered in the prediction exercise: ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t") + cat("\n Habitat considered in the prediction exercise : ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t") cat("\n ----------- PROCESSING SIMULATIONS") @@ -365,8 +369,8 @@ POST_FATE.validation = function(name.simulation { sim <- sim.version[i] - cat("\n", sim, "\n") - + cat("\n", sim, " :") + cat("\n Data preparation \n") # get simulated abundance per pixel*strata*PFG for pixels in the simulation area if (perStrata == FALSE) { if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv"))) @@ -418,6 +422,8 @@ POST_FATE.validation = function(name.simulation , list.strata = list.strata , perStrata = perStrata) + cat("\n Done ! \n") + } if (doComposition == TRUE){ # Only for PFG composition validation @@ -428,7 +434,7 @@ POST_FATE.validation = function(name.simulation ## GET OBSERVED DISTRIBUTION - cat("\n Get observed distribution \n") + cat("\n Get observed distribution...\n") obs.distri = get_observed_distribution(releves.PFG = releves.PFG , hab.obs = hab.obs @@ -441,7 +447,7 @@ POST_FATE.validation = function(name.simulation ## DO PFG COMPOSITION VALIDATION - cat("\n Comparison between observed and simulated distribution \n") + cat("\n Comparison between observed and simulated distribution...\n") performance.composition = do_PFG_composition_validation(sim = sim , PFG.considered_PFG.compo = PFG.considered_PFG.compo @@ -451,6 +457,8 @@ POST_FATE.validation = function(name.simulation , simu_PFG = simu_PFG , habitat.whole.area.df = habitat.whole.area.df) + cat("\n Done ! \n") + } if(doHabitat == TRUE & doComposition == TRUE){ @@ -467,7 +475,7 @@ POST_FATE.validation = function(name.simulation } # Based on choice of the user, foreach loop returns different results } # End of loop on simulations - cat("\n End of loop on simulations") + cat("\n ----------- END OF SIMULATIONS \n") if(doHabitat == TRUE){ # If habitat validation activated, the function uses the results to build and save a final map of habitat prediction @@ -486,7 +494,7 @@ POST_FATE.validation = function(name.simulation all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") # save write.csv(all.map.prediction, paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) - cat("\n Habitat results saved \n") + cat("\n Habitat results saved") ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT @@ -502,7 +510,7 @@ POST_FATE.validation = function(name.simulation , output.path = output.path , sim.version = sim.version) - cat("\n Predicted habitat plot saved \n") + cat("\n Predicted habitat plot saved") } if(doComposition == TRUE){ # If PFG composition validation activated, the function uses the results to save a table with proximity of PFG composition for each PFG and habitat*strata define by the user @@ -610,7 +618,11 @@ POST_FATE.validation = function(name.simulation if(doRichness == TRUE){ cat("\n ---------- PFG RICHNESS : \n") - cat(paste0("\n Richness at year ", year, " : ", output[[1]], "\n")) + for(sim in sim.version){ + cat(paste0("\n ", sim, " :")) + rich = as.data.frame(read.csv(paste0(name.simulation, "/VALIDATION/PFG_RICHNESS/performance.richness.csv"))) + cat(paste0("\n Richness at year ", year, " : ", rich[sim, 2])) + } } else{ @@ -638,7 +650,7 @@ POST_FATE.validation = function(name.simulation if(doComposition == TRUE){ cat("\n ---------- PFG COMPOSITION : \n") - return(results.compo[,sim.version]) + return(results.compo) } else{ diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 00ca272..3df70a6 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -69,10 +69,10 @@ do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, s FATE.PFG = FATE.PFG$PFG if(length(setdiff(FATE.PFG,RF.PFG)) > 0) { - warning(paste0("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG", setdiff(FATE.PFG,RF.PFG), "will be deleted")) + cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG", setdiff(FATE.PFG,RF.PFG), "will be deleted")) FATE.PFG = RF.PFG }else if(length(setdiff(RF.PFG,FATE.PFG)) > 0){ - warning(paste0("The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG", setdiff(RF.PFG,FATE.PFG), "will be deleted")) + cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG", setdiff(RF.PFG,FATE.PFG), "will be deleted")) RF.PFG = FATE.PFG } diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index 4a1202a..e61a60b 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -111,7 +111,6 @@ get_observed_distribution <- function(releves.PFG table.habitat.releve = levels(hab.obs)[[1]] mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat$habitat), ] - cat("habitat classes used in the RF algo: ", unique(mat.PFG.agg$habitat), "\n", sep = "\t") } else { stop("Habitat definition in hab.obs map is not correct") @@ -152,7 +151,7 @@ get_observed_distribution <- function(releves.PFG mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) mat.PFG.agg$coverage <- NULL - cat("\n releve data have been transformed into a relative metric \n") + cat("\n Releve data have been transformed into a relative metric \n") # 5. Save data diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index efe4b18..bbb213c 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -143,7 +143,7 @@ train_RF_habitat = function(releves.PFG mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) mat.PFG.agg$coverage = NULL - cat("\n releve data have been transformed into a relative metric \n") + cat("\n Releves data have been transformed into a relative metric \n") # 2. Cast the df ################ @@ -171,7 +171,6 @@ train_RF_habitat = function(releves.PFG table.habitat.releve = studied.habitat mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t") } else { stop("Habitat definition in studied.habitat is not correct") diff --git a/src/libs/iostreams/src/zlib.o b/src/libs/iostreams/src/zlib.o index 136ee47a373188c3ea88a26fcde484b7fe4173f2..b882b7784ab33a1f4ecfdc0e9c0bcf5b710e54ca 100644 GIT binary patch literal 244552 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 E8?fsJhX4Qo literal 184896 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 From 5f8182d3a94e67d9ec0090cc1c2333a4ed2a49d2 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 28 Apr 2022 14:04:30 +0200 Subject: [PATCH 094/176] Corrections in Graphics_PFG for devel_validation --- R/UTILS.graphics.R | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/R/UTILS.graphics.R b/R/UTILS.graphics.R index 38ad58d..b4536d9 100644 --- a/R/UTILS.graphics.R +++ b/R/UTILS.graphics.R @@ -60,8 +60,10 @@ , flag = "GLOBAL_PARAMS" , flag.split = "^--.*--$" , is.num = FALSE) - no_PFG <- .getParam(params.lines = paste0(sub(basename(name.simulation), "", name.simulation) - , file.globalParam) + path.globalParam = paste0(sub(basename(name.simulation), "", name.simulation) + , file.globalParam) + + no_PFG <- .getParam(params.lines = path.globalParam , flag = "NO_PFG" , flag.split = " " , is.num = TRUE) @@ -84,39 +86,40 @@ } ## Get MODULES --------------------------------------------------------------- - no_STRATA <- .getParam(params.lines = paste0(sub(basename(name.simulation), "", name.simulation) - , file.globalParam) + no_STRATA <- .getParam(params.lines = path.globalParam , flag = "NO_STRATA" , flag.split = " " , is.num = TRUE) - doDisp <- .getParam(params.lines = paste0(sub(basename(name.simulation), "", name.simulation) - , file.globalParam) + doDisp <- .getParam(params.lines = path.globalParam , flag = "DO_DISPERSAL" , flag.split = " " , is.num = TRUE) - doLight <- .getParam(params.lines = paste0(sub(basename(name.simulation), "", name.simulation) - , file.globalParam) + doLight <- .getParam(params.lines = path.globalParam , flag = "DO_LIGHT_INTERACTION" , flag.split = " " , is.num = TRUE) - doSoil <- .getParam(params.lines = paste0(sub(basename(name.simulation), "", name.simulation) - , file.globalParam) + doSoil <- .getParam(params.lines = path.globalParam , flag = "DO_SOIL_INTERACTION" , flag.split = " " , is.num = TRUE) - doHabsuit <- .getParam(params.lines = paste0(sub(basename(name.simulation), "", name.simulation) - , file.globalParam) + doHabsuit <- .getParam(params.lines = path.globalParam , flag = "DO_HAB_SUITABILITY" , flag.split = " " , is.num = TRUE) + saveStrat <- .getParam(params.lines = path.globalParam + , flag = "SAVING_ABUND_PFG_STRATUM" + , flag.split = " " + , is.num = TRUE) + return(list(no_PFG = no_PFG , PFG = PFG , no_STRATA = no_STRATA , doDisp = doDisp , doLight = doLight , doSoil = doSoil - , doHabsuit = doHabsuit)) + , doHabsuit = doHabsuit + , saveStrat = saveStrat)) } ################################################################################################# From 173d4509199ba2bcfd3cb3ebd056047216c36273 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 28 Apr 2022 14:06:52 +0200 Subject: [PATCH 095/176] minor correction --- R/POST_FATE.binaryMaps.R | 1 + R/RcppExports.R | 47 ---------------------------------------- 2 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 R/RcppExports.R diff --git a/R/POST_FATE.binaryMaps.R b/R/POST_FATE.binaryMaps.R index 95e604a..c095823 100644 --- a/R/POST_FATE.binaryMaps.R +++ b/R/POST_FATE.binaryMaps.R @@ -322,6 +322,7 @@ POST_FATE.binaryMaps = function( .zip(folder_name = GLOB_DIR$dir.output.perPFG.perStrata , list_files = raster.perPFG.perStrata , no_cores = opt.no_CPU) + } cat("\n> Done!\n") diff --git a/R/RcppExports.R b/R/RcppExports.R deleted file mode 100644 index b5c9df2..0000000 --- a/R/RcppExports.R +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by using Rcpp::compileAttributes() -> do not edit by hand -# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 - -#' FATE Wrapper -#' -#' This function runs a \code{FATE} dynamical landscape vegetation simulation. -#' -#' @param simulParam a \code{string} corresponding to the name of a -#' parameter file that will be contained into the \code{PARAM_SIMUL} folder -#' of the \code{FATE} simulation -#' @param no_CPU (\emph{optional}) default \code{1}. \cr The number of -#' resources that can be used to parallelize the simulation -#' @param verboseLevel (\emph{optional}) default \code{0}. \cr The logger -#' verbose level : a \code{FATE} simulation can render different levels of -#' information (from \code{0} to \code{4}, see -#' \href{FATE#details}{\code{Details}}). -#' -#' @details This function allows to run a vegetation simulation with the -#' \code{FATE} model, based on a simulation folder and a species simulation -#' parameter file. -#' -#' A \code{FATE} simulation can be parallelized, using the \code{no_CPU} -#' parameter, given that the user machine is multi-core ! -#' -#' Quantity of informations are rendered by the software into the \code{R} -#' console, and the \code{verboseLevel} parameter allows to filter which -#' level of information is printed : -#' -#' \describe{ -#' \item{0. }{shows any message} -#' \item{1. }{shows any message, except debug} -#' \item{2. }{shows only warning and error messages} -#' \item{3. }{shows only error messages} -#' \item{4. }{mute} -#' } -#' -#' @return None -#' -#' @examples \dontrun{FATE()} -#' -#' @author Damien Georges, Isabelle Boulangeat, Maya Guéguen -#' -#' @export -FATE <- function(simulParam, no_CPU = 1L, verboseLevel = 0L) { - .Call(`_RFate_FATE`, simulParam, no_CPU, verboseLevel) -} - From 4062e1cd1e1ba90d982420996989e8c253a4b070 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 28 Apr 2022 15:47:12 +0200 Subject: [PATCH 096/176] Display adjusments --- R/POST_FATE.validation.R | 9 +++---- R/RcppExports.R | 47 +++++++++++++++++++++++++++++++++ R/UTILS.do_habitat_validation.R | 4 +-- 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 R/RcppExports.R diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 9691e9b..6a08f79 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -618,11 +618,8 @@ POST_FATE.validation = function(name.simulation if(doRichness == TRUE){ cat("\n ---------- PFG RICHNESS : \n") - for(sim in sim.version){ - cat(paste0("\n ", sim, " :")) - rich = as.data.frame(read.csv(paste0(name.simulation, "/VALIDATION/PFG_RICHNESS/performance.richness.csv"))) - cat(paste0("\n Richness at year ", year, " : ", rich[sim, 2])) - } + rich = as.matrix(output[[1]]) + cat(paste0("\n Richness at year ", year, " : ", rich)) } else{ @@ -650,7 +647,7 @@ POST_FATE.validation = function(name.simulation if(doComposition == TRUE){ cat("\n ---------- PFG COMPOSITION : \n") - return(results.compo) + return(results.compo[c(sim.version, "simulation")]) } else{ diff --git a/R/RcppExports.R b/R/RcppExports.R new file mode 100644 index 0000000..b5c9df2 --- /dev/null +++ b/R/RcppExports.R @@ -0,0 +1,47 @@ +# Generated by using Rcpp::compileAttributes() -> do not edit by hand +# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 + +#' FATE Wrapper +#' +#' This function runs a \code{FATE} dynamical landscape vegetation simulation. +#' +#' @param simulParam a \code{string} corresponding to the name of a +#' parameter file that will be contained into the \code{PARAM_SIMUL} folder +#' of the \code{FATE} simulation +#' @param no_CPU (\emph{optional}) default \code{1}. \cr The number of +#' resources that can be used to parallelize the simulation +#' @param verboseLevel (\emph{optional}) default \code{0}. \cr The logger +#' verbose level : a \code{FATE} simulation can render different levels of +#' information (from \code{0} to \code{4}, see +#' \href{FATE#details}{\code{Details}}). +#' +#' @details This function allows to run a vegetation simulation with the +#' \code{FATE} model, based on a simulation folder and a species simulation +#' parameter file. +#' +#' A \code{FATE} simulation can be parallelized, using the \code{no_CPU} +#' parameter, given that the user machine is multi-core ! +#' +#' Quantity of informations are rendered by the software into the \code{R} +#' console, and the \code{verboseLevel} parameter allows to filter which +#' level of information is printed : +#' +#' \describe{ +#' \item{0. }{shows any message} +#' \item{1. }{shows any message, except debug} +#' \item{2. }{shows only warning and error messages} +#' \item{3. }{shows only error messages} +#' \item{4. }{mute} +#' } +#' +#' @return None +#' +#' @examples \dontrun{FATE()} +#' +#' @author Damien Georges, Isabelle Boulangeat, Maya Guéguen +#' +#' @export +FATE <- function(simulParam, no_CPU = 1L, verboseLevel = 0L) { + .Call(`_RFate_FATE`, simulParam, no_CPU, verboseLevel) +} + diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 3df70a6..b4ba5be 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -69,10 +69,10 @@ do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, s FATE.PFG = FATE.PFG$PFG if(length(setdiff(FATE.PFG,RF.PFG)) > 0) { - cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG", setdiff(FATE.PFG,RF.PFG), "will be deleted")) + cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(FATE.PFG,RF.PFG), " will be deleted")) FATE.PFG = RF.PFG }else if(length(setdiff(RF.PFG,FATE.PFG)) > 0){ - cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG", setdiff(RF.PFG,FATE.PFG), "will be deleted")) + cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(RF.PFG,FATE.PFG), " will be deleted")) RF.PFG = FATE.PFG } From 0045b128d3264f64ed494072d5cd674b3a10fdc5 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 28 Apr 2022 16:06:14 +0200 Subject: [PATCH 097/176] adding of unzip stage on validationStatistics --- R/POST_FATE.graphic_validationStatistics.R | 4 +++- R/UTILS.do_habitat_validation.R | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/R/POST_FATE.graphic_validationStatistics.R b/R/POST_FATE.graphic_validationStatistics.R index 8005814..f475a57 100644 --- a/R/POST_FATE.graphic_validationStatistics.R +++ b/R/POST_FATE.graphic_validationStatistics.R @@ -256,7 +256,9 @@ POST_FATE.graphic_validationStatistics = function( ## UNZIP the raster saved ------------------------------------------------- raster.perPFG.allStrata.REL = .getRasterNames(years, "allStrata", "REL", GLOB_DIR) - + .unzip(folder_name = GLOB_DIR$dir.output.perPFG.allStrata.REL + , list_files = raster.perPFG.allStrata.REL + , no_cores = opt.no_CPU) ## get the data inside the rasters ---------------------------------------- cat("\n ---------- GETTING STATISTICS for") diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index b4ba5be..25b0aeb 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -69,10 +69,10 @@ do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, s FATE.PFG = FATE.PFG$PFG if(length(setdiff(FATE.PFG,RF.PFG)) > 0) { - cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(FATE.PFG,RF.PFG), " will be deleted")) + cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(FATE.PFG,RF.PFG), " will be removed from the analyses")) FATE.PFG = RF.PFG }else if(length(setdiff(RF.PFG,FATE.PFG)) > 0){ - cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(RF.PFG,FATE.PFG), " will be deleted")) + cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(RF.PFG,FATE.PFG), " will be removed from the analyses")) RF.PFG = FATE.PFG } From 2a1b99ba952b5c3a1c81a354896587564d42f187 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 29 Apr 2022 11:25:03 +0200 Subject: [PATCH 098/176] Display adjustments --- R/POST_FATE.validation.R | 32 ++++++++++++++--------------- R/UTILS.do_habitat_validation.R | 4 ++-- R/UTILS.get_observed_distribution.R | 3 +-- R/UTILS.train_RF_habitat.R | 2 +- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 6a08f79..3c28c8e 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -204,7 +204,7 @@ POST_FATE.validation = function(name.simulation cat("\n #------------------------------------------------------------# \n") } - cat("\n ----------- CHECKS & DATA PREPARATION") + cat("\n ----------- PRELIMINARY CHECKS") ####################### # 0. Global parameters @@ -296,7 +296,7 @@ POST_FATE.validation = function(name.simulation stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } - cat("\n Done !") + cat("> Done !") ################################################################# # I.2 Train a RF model on observed data (habitat validation only) @@ -321,7 +321,7 @@ POST_FATE.validation = function(name.simulation , perStrata = perStrata , sim.version = sim.version) - cat("\n Done ! \n") + cat("> Done ! \n") } @@ -351,9 +351,9 @@ POST_FATE.validation = function(name.simulation habitat.whole.area.df = habitat.whole.area.df[which(habitat.whole.area.df$for.validation == 1),] } - cat("\n Habitat considered in the prediction exercise : ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t") + cat("> Habitat considered in the prediction exercise : ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t") - cat("\n ----------- PROCESSING SIMULATIONS") + cat("\n ----------- PROCESSING LOOP ON SIMULATIONS") if (opt.no_CPU > 1) { @@ -369,8 +369,8 @@ POST_FATE.validation = function(name.simulation { sim <- sim.version[i] - cat("\n", sim, " :") - cat("\n Data preparation \n") + cat(">", sim, " :") + cat("> Data preparation \n") # get simulated abundance per pixel*strata*PFG for pixels in the simulation area if (perStrata == FALSE) { if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv"))) @@ -422,7 +422,7 @@ POST_FATE.validation = function(name.simulation , list.strata = list.strata , perStrata = perStrata) - cat("\n Done ! \n") + cat("> Done ! \n") } @@ -457,7 +457,7 @@ POST_FATE.validation = function(name.simulation , simu_PFG = simu_PFG , habitat.whole.area.df = habitat.whole.area.df) - cat("\n Done ! \n") + cat("> Done ! \n") } @@ -466,7 +466,7 @@ POST_FATE.validation = function(name.simulation return(results) } if(doHabitat == TRUE & doComposition == FALSE){ - results = list(habitat.prediction = results.habitat$all.map.prediction, habitat.performance = output.performance.habitat, RF.model = RF.model) + results = list(habitat.prediction = results.habitat$y.all.map.predicted, habitat.performance = results.habitat$output.validation, RF.model = RF.model) return(results) } if(doHabitat == FALSE & doComposition == TRUE){ @@ -475,7 +475,7 @@ POST_FATE.validation = function(name.simulation } # Based on choice of the user, foreach loop returns different results } # End of loop on simulations - cat("\n ----------- END OF SIMULATIONS \n") + cat("\n ----------- END OF LOOP ON SIMULATIONS \n") if(doHabitat == TRUE){ # If habitat validation activated, the function uses the results to build and save a final map of habitat prediction @@ -494,7 +494,7 @@ POST_FATE.validation = function(name.simulation all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") # save write.csv(all.map.prediction, paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) - cat("\n Habitat results saved") + cat("> Habitat results saved") ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT @@ -510,7 +510,7 @@ POST_FATE.validation = function(name.simulation , output.path = output.path , sim.version = sim.version) - cat("\n Predicted habitat plot saved") + cat("> Predicted habitat plot saved") } if(doComposition == TRUE){ # If PFG composition validation activated, the function uses the results to save a table with proximity of PFG composition for each PFG and habitat*strata define by the user @@ -544,7 +544,7 @@ POST_FATE.validation = function(name.simulation #list of PFG of interest list.PFG = setdiff(list.PFG,exclude.PFG) - cat("\n Data preparation \n") + cat("> Data preparation \n") if (opt.no_CPU > 1) { @@ -585,7 +585,7 @@ POST_FATE.validation = function(name.simulation } # End of loop - cat("\n Richness computation \n") + cat("> Richness computation \n") # names the results names(dying.PFG.list) = sim.version @@ -608,7 +608,7 @@ POST_FATE.validation = function(name.simulation write.csv(dying.distribution, paste0(output.path, "/PFG.extinction.frequency.csv"), row.names = F) write_rds(dying.PFG.list, file = paste0(output.path, "/dying.PFG.list.rds"), compress = "none") - cat("\n PFG richness results saved \n") + cat("> PFG richness results saved \n") } cat("\n\n #------------------------------------------------------------#") diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 25b0aeb..44e7c00 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -69,10 +69,10 @@ do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, s FATE.PFG = FATE.PFG$PFG if(length(setdiff(FATE.PFG,RF.PFG)) > 0) { - cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(FATE.PFG,RF.PFG), " will be removed from the analyses")) + cat(paste0("> Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(FATE.PFG,RF.PFG), " will be removed from the analyses")) FATE.PFG = RF.PFG }else if(length(setdiff(RF.PFG,FATE.PFG)) > 0){ - cat(paste0("\n Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(RF.PFG,FATE.PFG), " will be removed from the analyses")) + cat(paste0("> Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(RF.PFG,FATE.PFG), " will be removed from the analyses")) RF.PFG = FATE.PFG } diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index e61a60b..f270728 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -105,7 +105,6 @@ get_observed_distribution <- function(releves.PFG table.habitat.releve = studied.habitat mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - cat("habitat classes used in the RF algo: ",unique(mat.PFG.agg$habitat),"\n",sep="\t") } else if (names(levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) { # cas où on utilise les levels définis dans la carte table.habitat.releve = levels(hab.obs)[[1]] @@ -151,7 +150,7 @@ get_observed_distribution <- function(releves.PFG mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) mat.PFG.agg$coverage <- NULL - cat("\n Releve data have been transformed into a relative metric \n") + cat("> Releve data have been transformed into a relative metric \n") # 5. Save data diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index bbb213c..7ec8ac4 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -143,7 +143,7 @@ train_RF_habitat = function(releves.PFG mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) mat.PFG.agg$coverage = NULL - cat("\n Releves data have been transformed into a relative metric \n") + cat("> Releves data have been transformed into a relative metric \n") # 2. Cast the df ################ From 7021511f33003d30aedb86f9c4ed526b47d002a2 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 29 Apr 2022 13:53:03 +0200 Subject: [PATCH 099/176] Add predict.all.map argument to let the user decide if the function make the predict map or just computes habitat performance --- R/POST_FATE.validation.R | 67 ++++++++++++++++++----------- R/UTILS.do_habitat_validation.R | 13 ++++-- R/UTILS.get_observed_distribution.R | 2 +- R/UTILS.train_RF_habitat.R | 2 +- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 3c28c8e..df40da2 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -172,18 +172,19 @@ POST_FATE.validation = function(name.simulation , sim.version , year - , perStrata = TRUE + , perStrata = FALSE , opt.no_CPU = 1 , doHabitat = TRUE , releves.PFG , hab.obs - , validation.mask = NULL , studied.habitat - , list.strata.simulations + , predict.all.map + , validation.mask = NULL + , list.strata.simulations = NULL , doComposition = TRUE , PFG.considered_PFG.compo , habitat.considered_PFG.compo - , strata.considered_PFG.compo + , strata.considered_PFG.compo = "A" , doRichness = TRUE , list.PFG , exclude.PFG = NULL){ @@ -214,6 +215,7 @@ POST_FATE.validation = function(name.simulation year = year # choice in the year for validation perStrata = perStrata opt.no_CPU = opt.no_CPU + predict.all.map = predict.all.map # Observed releves data releves.PFG = releves.PFG @@ -296,7 +298,7 @@ POST_FATE.validation = function(name.simulation stop("check 'perStrata' parameter and/or the names of strata in list.strata.releves & list.strata.simulation") } - cat("> Done !") + cat("\n > Done !") ################################################################# # I.2 Train a RF model on observed data (habitat validation only) @@ -321,7 +323,7 @@ POST_FATE.validation = function(name.simulation , perStrata = perStrata , sim.version = sim.version) - cat("> Done ! \n") + cat("\n > Done ! \n") } @@ -351,7 +353,7 @@ POST_FATE.validation = function(name.simulation habitat.whole.area.df = habitat.whole.area.df[which(habitat.whole.area.df$for.validation == 1),] } - cat("> Habitat considered in the prediction exercise : ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t") + cat("\n > Habitat considered in the prediction exercise : ", c(unique(habitat.whole.area.df$habitat)), "\n", sep = "\t") cat("\n ----------- PROCESSING LOOP ON SIMULATIONS") @@ -369,8 +371,8 @@ POST_FATE.validation = function(name.simulation { sim <- sim.version[i] - cat(">", sim, " :") - cat("> Data preparation \n") + cat("\n >", sim, " :") + cat("\n > Data preparation \n") # get simulated abundance per pixel*strata*PFG for pixels in the simulation area if (perStrata == FALSE) { if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv"))) @@ -407,11 +409,11 @@ POST_FATE.validation = function(name.simulation if (doHabitat == TRUE){ # Only for habitat validation - cat("\n ----------- HABITAT PREDICTION") + cat("\n ------ HABITAT PREDICTION") ## USE THE RF MODEL TO VALIDATE FATE OUTPUT - predict.all.map = TRUE + predict.all.map = predict.all.map results.habitat = do_habitat_validation(output.path = output.path , RF.model = RF.model @@ -422,13 +424,13 @@ POST_FATE.validation = function(name.simulation , list.strata = list.strata , perStrata = perStrata) - cat("> Done ! \n") + cat("\n > Done ! \n") } if (doComposition == TRUE){ # Only for PFG composition validation - cat("\n ----------- PFG COMPOSITION VALIDATION") + cat("\n ------ PFG COMPOSITION VALIDATION") output.path.compo = paste0(name.simulation, "/VALIDATION/PFG_COMPOSITION") @@ -457,18 +459,26 @@ POST_FATE.validation = function(name.simulation , simu_PFG = simu_PFG , habitat.whole.area.df = habitat.whole.area.df) - cat("> Done ! \n") + cat("\n > Done ! \n") } - if(doHabitat == TRUE & doComposition == TRUE){ + if(doHabitat == TRUE & doComposition == TRUE & predict.all.map == TRUE){ results = list(habitat.prediction = results.habitat$y.all.map.predicted, habitat.performance = results.habitat$output.validation, RF.model = RF.model, performance.compo = performance.composition) return(results) } - if(doHabitat == TRUE & doComposition == FALSE){ + if(doHabitat == TRUE & doComposition == TRUE & predict.all.map == FALSE){ + results = list(habitat.performance = results.habitat$output.validation, RF.model = RF.model, performance.compo = performance.composition) + return(results) + } + if(doHabitat == TRUE & doComposition == FALSE & predict.all.map == TRUE){ results = list(habitat.prediction = results.habitat$y.all.map.predicted, habitat.performance = results.habitat$output.validation, RF.model = RF.model) return(results) } + if(doHabitat == TRUE & doComposition == FALSE & predict.all.map == FALSE){ + results = list(habitat.performance = results.habitat$output.validation, RF.model = RF.model) + return(results) + } if(doHabitat == FALSE & doComposition == TRUE){ results = list(performance.compo = performance.composition) return(results) @@ -477,7 +487,7 @@ POST_FATE.validation = function(name.simulation } # End of loop on simulations cat("\n ----------- END OF LOOP ON SIMULATIONS \n") - if(doHabitat == TRUE){ # If habitat validation activated, the function uses the results to build and save a final map of habitat prediction + if(doHabitat == TRUE & predict.all.map == TRUE){ # If habitat validation activated, the function uses the results to build and save a final map of habitat prediction # deal with the results regarding model performance output.path = paste0(name.simulation, "/VALIDATION") @@ -494,7 +504,7 @@ POST_FATE.validation = function(name.simulation all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") # save write.csv(all.map.prediction, paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) - cat("> Habitat results saved") + cat("\n > Habitat results saved") ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT @@ -510,7 +520,7 @@ POST_FATE.validation = function(name.simulation , output.path = output.path , sim.version = sim.version) - cat("> Predicted habitat plot saved") + cat("\n > Predicted habitat plot saved") } if(doComposition == TRUE){ # If PFG composition validation activated, the function uses the results to save a table with proximity of PFG composition for each PFG and habitat*strata define by the user @@ -527,7 +537,7 @@ POST_FATE.validation = function(name.simulation #save and return write.csv(results.compo, paste0(output.path.compo, "/performance.composition.csv"), row.names = FALSE) - cat("\n Performance composition file saved \n") + cat("\n > Performance composition file saved \n") } } # End of (doHabitat | doComposition) condition @@ -544,7 +554,7 @@ POST_FATE.validation = function(name.simulation #list of PFG of interest list.PFG = setdiff(list.PFG,exclude.PFG) - cat("> Data preparation \n") + cat("\n > Data preparation \n") if (opt.no_CPU > 1) { @@ -585,7 +595,7 @@ POST_FATE.validation = function(name.simulation } # End of loop - cat("> Richness computation \n") + cat("\n > Richness computation \n") # names the results names(dying.PFG.list) = sim.version @@ -608,7 +618,7 @@ POST_FATE.validation = function(name.simulation write.csv(dying.distribution, paste0(output.path, "/PFG.extinction.frequency.csv"), row.names = F) write_rds(dying.PFG.list, file = paste0(output.path, "/dying.PFG.list.rds"), compress = "none") - cat("> PFG richness results saved \n") + cat("\n > PFG richness results saved \n") } cat("\n\n #------------------------------------------------------------#") @@ -627,17 +637,26 @@ POST_FATE.validation = function(name.simulation } - if(doHabitat == TRUE){ + if(doHabitat == TRUE & predict.all.map == TRUE){ hab.pred = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/hab.pred.csv")) failure = as.numeric((table(hab.pred$prediction.code)[1]/sum(table(hab.pred$prediction.code)))*100) success = as.numeric((table(hab.pred$prediction.code)[2]/sum(table(hab.pred$prediction.code)))*100) + hab.perf = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/performance.habitat.csv")) cat("\n ---------- HABITAT : \n") cat(paste0("\n", round(failure, digits = 2), "% of habitats are not correctly predicted by the simulations \n")) cat(paste0("\n", round(success, digits = 2), "% of habitats are correctly predicted by the simulations \n")) + cat(paste0("\n Habitat performance :", hab.perf)) plot(prediction.map) + } else if (doHabitat == TRUE & predict.all.map == FALSE){ + + hab.perf = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/performance.habitat.csv")) + + cat("\n ---------- HABITAT : \n") + cat(paste0("\n Habitat performance :", hab.perf)) + } else{ cat("\n ---------- HABITAT VALIDATION DISABLED \n") diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 44e7c00..24b409c 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -69,10 +69,10 @@ do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, s FATE.PFG = FATE.PFG$PFG if(length(setdiff(FATE.PFG,RF.PFG)) > 0) { - cat(paste0("> Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(FATE.PFG,RF.PFG), " will be removed from the analyses")) + cat(paste0("\n > Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(FATE.PFG,RF.PFG), " will be removed from the analyses")) FATE.PFG = RF.PFG }else if(length(setdiff(RF.PFG,FATE.PFG)) > 0){ - cat(paste0("> Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(RF.PFG,FATE.PFG), " will be removed from the analyses")) + cat(paste0("\n > Warning : The PFG used to train the RF algorithm are not the same as the PFG used to run FATE ! The PFG ", setdiff(RF.PFG,FATE.PFG), " will be removed from the analyses")) RF.PFG = FATE.PFG } @@ -146,8 +146,13 @@ do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, s output.validation <- c(synthesis.validation$TSS, aggregate.TSS.validation) names(output.validation) <- c(synthesis.validation$habitat, "aggregated") - results.habitat <- list(output.validation = output.validation, y.all.map.predicted = y.all.map.predicted) - names(results.habitat) <- c("output.validation", "y.all.map.predicted") + if(predict.all.map == TRUE){ + results.habitat <- list(output.validation = output.validation, y.all.map.predicted = y.all.map.predicted) + names(results.habitat) <- c("output.validation", "y.all.map.predicted") + }else if(is.null(predict.all.map)){ + results.habitat <- list(output.validation = output.validation) + names(results.habitat) <- c("output.validation") + } return(results.habitat) diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index f270728..021d6cf 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -150,7 +150,7 @@ get_observed_distribution <- function(releves.PFG mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) mat.PFG.agg$coverage <- NULL - cat("> Releve data have been transformed into a relative metric \n") + cat("\n > Releve data have been transformed into a relative metric \n") # 5. Save data diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 7ec8ac4..428b919 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -143,7 +143,7 @@ train_RF_habitat = function(releves.PFG mat.PFG.agg$relative.metric[is.na(mat.PFG.agg$relative.metric)] <- 0 #NA because abs==0 for some PFG, so put 0 instead of NA (maybe not necessary) mat.PFG.agg$coverage = NULL - cat("> Releves data have been transformed into a relative metric \n") + cat("\n > Releves data have been transformed into a relative metric \n") # 2. Cast the df ################ From 5aecce02c2f1c8c1ae4eb885dac302a0a1f6a9bc Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 29 Apr 2022 14:17:29 +0200 Subject: [PATCH 100/176] minor corrections --- R/POST_FATE.validation.R | 53 ++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index df40da2..21400cb 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -487,7 +487,7 @@ POST_FATE.validation = function(name.simulation } # End of loop on simulations cat("\n ----------- END OF LOOP ON SIMULATIONS \n") - if(doHabitat == TRUE & predict.all.map == TRUE){ # If habitat validation activated, the function uses the results to build and save a final map of habitat prediction + if(doHabitat == TRUE){ # If habitat validation activated, the function uses the results to build and save a final map of habitat prediction # deal with the results regarding model performance output.path = paste0(name.simulation, "/VALIDATION") @@ -497,30 +497,35 @@ POST_FATE.validation = function(name.simulation habitat.performance$simulation <- sim.version # save write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) + cat("\n > Habitat performance saved") - # deal with the results regarding habitat prediction over the whole map - all.map.prediction = as.data.frame(lapply(results.simul, "[[", 1)) - all.map.prediction = all.map.prediction[,c(sim.version, "pixel", "habitat")] - all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") - # save - write.csv(all.map.prediction, paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) - cat("\n > Habitat results saved") - - ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT - - # Provide a color df - col.df = data.frame( - habitat = RF.model$classes, - failure = terrain.colors(length(RF.model$classes), alpha = 0.5), - success = terrain.colors(length(RF.model$classes), alpha = 1)) - - prediction.map = plot_predicted_habitat(predicted.habitat = all.map.prediction - , col.df = col.df - , simulation.map = simulation.map - , output.path = output.path - , sim.version = sim.version) - - cat("\n > Predicted habitat plot saved") + if(predict.all.map == TRUE){ + + # deal with the results regarding habitat prediction over the whole map + all.map.prediction = as.data.frame(lapply(results.simul, "[[", 1)) + all.map.prediction = all.map.prediction[,c(sim.version, "pixel", "habitat")] + all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") + # save + write.csv(all.map.prediction, paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) + cat("\n > Habitat prediction saved") + + ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT + + # Provide a color df + col.df = data.frame( + habitat = RF.model$classes, + failure = terrain.colors(length(RF.model$classes), alpha = 0.5), + success = terrain.colors(length(RF.model$classes), alpha = 1)) + + prediction.map = plot_predicted_habitat(predicted.habitat = all.map.prediction + , col.df = col.df + , simulation.map = simulation.map + , output.path = output.path + , sim.version = sim.version) + + cat("\n > Predicted habitat plot saved") + + } } if(doComposition == TRUE){ # If PFG composition validation activated, the function uses the results to save a table with proximity of PFG composition for each PFG and habitat*strata define by the user From 596ecb50181a1d294141941257ef1c1b30e15b9d Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 29 Apr 2022 15:09:12 +0200 Subject: [PATCH 101/176] minor corrections --- R/POST_FATE.validation.R | 5 ++++- R/UTILS.train_RF_habitat.R | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 21400cb..6f6d35b 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -179,6 +179,7 @@ POST_FATE.validation = function(name.simulation , hab.obs , studied.habitat , predict.all.map + , seed , validation.mask = NULL , list.strata.simulations = NULL , doComposition = TRUE @@ -216,6 +217,7 @@ POST_FATE.validation = function(name.simulation perStrata = perStrata opt.no_CPU = opt.no_CPU predict.all.map = predict.all.map + seed = seed # Observed releves data releves.PFG = releves.PFG @@ -321,7 +323,8 @@ POST_FATE.validation = function(name.simulation , RF.param = RF.param , output.path = output.path , perStrata = perStrata - , sim.version = sim.version) + , sim.version = sim.version + , seed = seed) cat("\n > Done ! \n") diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 428b919..bfeb09c 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -65,7 +65,8 @@ train_RF_habitat = function(releves.PFG , RF.param , output.path , perStrata - , sim.version) + , sim.version + , seed) { ############################################################################# @@ -198,7 +199,7 @@ train_RF_habitat = function(releves.PFG ################# #separate the database into a training and a test part - set.seed(123) + set.seed(seed) training.site = sample(mat.PFG.agg$site, size = RF.param$share.training * length(mat.PFG.agg$site), replace = FALSE) releves.training = mat.PFG.agg[which(mat.PFG.agg$site %in% training.site), ] From f7645521a83b8d25d03ef113dec7c2e3362becc5 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 29 Apr 2022 16:03:11 +0200 Subject: [PATCH 102/176] minor corrections --- R/UTILS.do_habitat_validation.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 24b409c..11287ed 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -149,7 +149,7 @@ do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, s if(predict.all.map == TRUE){ results.habitat <- list(output.validation = output.validation, y.all.map.predicted = y.all.map.predicted) names(results.habitat) <- c("output.validation", "y.all.map.predicted") - }else if(is.null(predict.all.map)){ + }else if(predict.all.map == FALSE){ results.habitat <- list(output.validation = output.validation) names(results.habitat) <- c("output.validation") } From 3471a3c2a04f2dcf8f65360ed63d39abc4a01531 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 29 Apr 2022 16:39:57 +0200 Subject: [PATCH 103/176] minor corrections --- R/POST_FATE.validation.R | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 6f6d35b..db4725c 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -492,18 +492,18 @@ POST_FATE.validation = function(name.simulation if(doHabitat == TRUE){ # If habitat validation activated, the function uses the results to build and save a final map of habitat prediction - # deal with the results regarding model performance - output.path = paste0(name.simulation, "/VALIDATION") - RF.model = results.simul[[1]]$RF.model - habitat.performance <- as.data.frame(matrix(unlist(lapply(results.simul,"[[", 2)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) - colnames(habitat.performance) <- c(RF.model$classes, "weighted") - habitat.performance$simulation <- sim.version - # save - write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) - cat("\n > Habitat performance saved") - if(predict.all.map == TRUE){ + # deal with the results regarding model performance + output.path = paste0(name.simulation, "/VALIDATION") + RF.model = results.simul[[1]]$RF.model + habitat.performance <- as.data.frame(matrix(unlist(lapply(results.simul,"[[", 2)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) + colnames(habitat.performance) <- c(RF.model$classes, "weighted") + habitat.performance$simulation <- sim.version + # save + write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) + cat("\n > Habitat performance saved") + # deal with the results regarding habitat prediction over the whole map all.map.prediction = as.data.frame(lapply(results.simul, "[[", 1)) all.map.prediction = all.map.prediction[,c(sim.version, "pixel", "habitat")] @@ -528,6 +528,18 @@ POST_FATE.validation = function(name.simulation cat("\n > Predicted habitat plot saved") + }else if(predict.all.map == FALSE){ + + # deal with the results regarding model performance + output.path = paste0(name.simulation, "/VALIDATION") + RF.model = results.simul[[1]]$RF.model + habitat.performance <- as.data.frame(matrix(unlist(lapply(results.simul,"[[", 1)), ncol = length(RF.model$classes) + 1, byrow = TRUE)) + colnames(habitat.performance) <- c(RF.model$classes, "weighted") + habitat.performance$simulation <- sim.version + # save + write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) + cat("\n > Habitat performance saved") + } } From 6a76a4c18226cc0499b953fe0f2af6415d916b33 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Mon, 2 May 2022 10:35:11 +0200 Subject: [PATCH 104/176] Documentation corrections --- R/POST_FATE.validation.R | 66 +++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index db4725c..f8e77fa 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -7,52 +7,57 @@ ##' @author Matthieu Combaud, Maxime Delprat ##' ##' @description This script is designed to compute validation data for : -##' \cr \code{Habitat} : compares habitat simulations and observations and +##' \cr \code{Habitat} : Compares habitat simulations and observations and ##' create a map to visualize this comparison with all the \code{FATE} and -##' observed data. -##' \cr \code{PFG Composition} : produced a computation of observed distribution +##' observed data (if option selected). +##' \cr \code{PFG Composition} : Produced a computation of observed distribution ##' of relative abundance in the simulation area and a computation of distance between ##' observed and simulated distribution. -##' \cr \code{PFG Richness} : computes the PFG richness over the whole simulation area +##' \cr \code{PFG Richness} : Computes the PFG richness over the whole simulation area ##' for a \code{FATE} simulation and computes the difference between observed and simulated PFG richness. ##' -##' @param name.simulation simulation folder name. -##' @param sim.version a character vector with the name(s) of the simulation(s) to validate. -##' @param year year of simulation for validation. -##' @param perStrata \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. +##' @param name.simulation Simulation folder name. +##' @param sim.version A character vector with the name(s) of the simulation(s) to validate. +##' @param year Year of simulation for validation. +##' @param perStrata \code{Logical}. \cr Default \code{FALSE}. If \code{TRUE}, PFG abundance is defined by strata. ##' If \code{FALSE}, PFG abundance defined for all strata (habitat & PFG composition & PFG richness validation). -##' @param opt.no_CPU default \code{1}. \cr The number of resources that can be used to +##' @param opt.no_CPU Default \code{1}. \cr The number of resources that can be used to ##' parallelize the computation of prediction performance for habitat & richness validation. ##' -##' @param doHabitat \code{Logical}. Default \code{TRUE}. If \code{TRUE}, habitat validation module is activated, +##' @param doHabitat \code{Logical}. Default \code{TRUE}. \cr If \code{TRUE}, habitat validation module is activated, ##' if \code{FALSE}, habitat validation module is disabled. -##' @param releves.PFG a data frame with abundance (column named abund) at each site +##' @param releves.PFG A data frame with abundance (column named abund) at each site ##' and for each PFG and strata, if necessary, & sites coordinates (habitat & PFG composition validation). -##' @param hab.obs a raster map of the extended studied map in the simulation, with same projection +##' @param hab.obs A raster map of the extended studied map in the simulation, with same projection ##' & resolution than simulation mask (habitat & PFG composition validation). -##' @param validation.mask a raster mask that specified which pixels need validation, with same projection -##' & resolution than simulation mask (habitat & PFG composition validation). -##' @param studied.habitat a 2 columns data frame which contains the habitats (2nd column) and the ID (1st column) +##' @param studied.habitat A 2 columns data frame which contains the habitats (2nd column) and the ID (1st column) ##' for each of them which will be taken into account for the validation (habitat & PFG composition validation). -##' @param list.strata.simulations If \code{perStrata} = \code{TRUE}, +##' @param predict.all.map Default \code{FALSE}. \cr If \code{TRUE}, the function will compute habitat prediction +##' over the whole map and will provide a prediction map. +##' @param seed \code{Numerical}. Number of seeds to set in order to generate a Random Forest model. +##' @param validation.mask (\code{Optional}). Default \code{NULL}. \cr A raster mask (with 0 or 1 in each pixel) that specified on +##' which pixels the performance of the prediction will be compute, with same projection & resolution than simulation mask +##' (habitat & PFG composition validation). \cr +##' If \code{NULL}, the function will take the simulation mask (which means that the performance will be compute over the whole map) +##' @param list.strata.simulations (\code{Optional}). Default \code{NULL}. \cr If \code{perStrata} = \code{TRUE}, ##' a character vector which contain \code{FATE} strata definition and correspondence with observed strata definition. ##' If \code{perStrata} = \code{FALSE}, please specify \code{NULL} value. ##' -##' @param doComposition \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG composition validation module is activated, +##' @param doComposition \code{Logical}. Default \code{TRUE}. \cr If \code{TRUE}, PFG composition validation module is activated, ##' if \code{FALSE}, PFG composition validation module is disabled. -##' @param PFG.considered_PFG.compo a character vector of the list of PFG considered +##' @param PFG.considered_PFG.compo A character vector of the list of PFG considered ##' in the validation (PFG composition validation). -##' @param habitat.considered_PFG.compo a character vector of the list of habitat(s) +##' @param habitat.considered_PFG.compo A character vector of the list of habitat(s) ##' considered in the validation (PFG composition validation). -##' @param strata.considered_PFG.compo If \code{perStrata} = \code{FALSE}, a character vector with value "A" +##' @param strata.considered_PFG.compo Default \code{"A"}. \cr If \code{perStrata} = \code{FALSE}, a character vector with value "A" ##' (selection of one or several specific strata disabled). If \code{perStrata} = \code{TRUE}, a character ##' vector with at least one of the observed strata (PFG composition validation). ##' -##' @param doRichness \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG richness validation module is activated, +##' @param doRichness \code{Logical}. Default \code{TRUE}. \cr If \code{TRUE}, PFG richness validation module is activated, ##' if \code{FALSE}, PFG richness validation module is disabled. -##' @param list.PFG a character vector which contain all the PFGs taken account in +##' @param list.PFG A character vector which contain all the PFGs taken account in ##' the simulation and observed in the simulation area (PFG richness validation). -##' @param exclude.PFG default \code{NULL}. A character vector containing the names +##' @param exclude.PFG (\code{Optional}). Default \code{NULL}. \cr A character vector containing the names ##' of the PFG you want to exclude from the analysis (PFG richness validation). ##' ##' @details @@ -67,7 +72,7 @@ ##' of habitat h/number of non-observation of habitat h). \cr The final metrics this script use is the ##' mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed ##' habitat distribution. The habitat validation also provides a visual comparison of observed and -##' simulated habitat on the whole studied area (see \code{\link{do_habitat_validation}} & +##' simulated habitat on the whole studied area, if option slected (see \code{\link{do_habitat_validation}} & ##' \code{\link{plot_predicted_habitat}}).} \cr ##' \item{PFG composition validation}{This code firstly run the \code{get_observed_distribution} ##' function in order to have a \code{obs.distri} file which contain the observed distribution @@ -86,10 +91,10 @@ ##' ##' Output files : ##' \describe{ -##' \item{\file{VALIDATION/HABITAT}}{containing the prepared CBNA data, +##' \item{\file{VALIDATION/HABITAT}}{Containing the prepared CBNA data, ##' RF model, the performance analyzes (confusion matrix and TSS) for the training and ##' testing parts of the RF model, the habitat performance file, the habitat prediction file with -##' observed and simulated habitat for each pixel of the whole map and the final prediction plot.} +##' observed and simulated habitat for each pixel of the whole map and the final prediction plot (if option selected).} ##' } ##' \describe{ ##' \item{\file{VALIDATION/PFG_COMPOSITION}}{1 .csv file which contain the proximity @@ -112,10 +117,8 @@ ##' studied.habitat = data.frame(ID = c(6, 5, 7, 8), habitat = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland")) ##' ## Habitat & validation maps ##' hab.observed = raster("FATE_Champsaur/DATA_OBS/simplified.cesbio.map.grd") -##' validation.mask = raster("FATE_Champsaur/DATA_OBS/certain.habitat.100m.restricted.grd") ##' simulation.map = raster("FATE_Champsaur/DATA/MASK/MASK_Champsaur.tif") ##' hab.obs = projectRaster(from = hab.observed, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") -##' validation.mask = projectRaster(from = validation.mask, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") ##' ## Observed data ##' releves.sites = as.data.frame(st_read("FATE_Champsaur/DATA_OBS/releves.sites.shp")) ##' releves.PFG = as.data.frame(read.csv("FATE_Champsaur/DATA_OBS/releves.PFG.abundance.csv")) @@ -142,8 +145,9 @@ ##' , doHabitat = TRUE ##' , releves.PFG = releves.PFG ##' , hab.obs = hab.obs -##' , validation.mask = validation.mask ##' , studied.habitat = studied.habitat +##' , predict.all.map = TRUE +##' , seed = 123 ##' , list.strata.simulations = list.strata.simulations ##' , doComposition = TRUE ##' , PFG.considered_PFG.compo = PFG.considered_PFG.compo @@ -178,7 +182,7 @@ POST_FATE.validation = function(name.simulation , releves.PFG , hab.obs , studied.habitat - , predict.all.map + , predict.all.map = FALSE , seed , validation.mask = NULL , list.strata.simulations = NULL @@ -326,7 +330,7 @@ POST_FATE.validation = function(name.simulation , sim.version = sim.version , seed = seed) - cat("\n > Done ! \n") + cat("> Done ! \n") } From 3b88ab984217ab94e776c7022569030cece31d52 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Mon, 2 May 2022 13:57:21 +0200 Subject: [PATCH 105/176] modification about colors in the final prediction map --- R/POST_FATE.validation.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index f8e77fa..66bfda0 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -72,7 +72,7 @@ ##' of habitat h/number of non-observation of habitat h). \cr The final metrics this script use is the ##' mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed ##' habitat distribution. The habitat validation also provides a visual comparison of observed and -##' simulated habitat on the whole studied area, if option slected (see \code{\link{do_habitat_validation}} & +##' simulated habitat on the whole studied area, if option selected (see \code{\link{do_habitat_validation}} & ##' \code{\link{plot_predicted_habitat}}).} \cr ##' \item{PFG composition validation}{This code firstly run the \code{get_observed_distribution} ##' function in order to have a \code{obs.distri} file which contain the observed distribution @@ -521,8 +521,8 @@ POST_FATE.validation = function(name.simulation # Provide a color df col.df = data.frame( habitat = RF.model$classes, - failure = terrain.colors(length(RF.model$classes), alpha = 0.5), - success = terrain.colors(length(RF.model$classes), alpha = 1)) + failure = rainbow(length(RF.model$classes), alpha = 0.5), + success = rainbow(length(RF.model$classes), alpha = 1)) prediction.map = plot_predicted_habitat(predicted.habitat = all.map.prediction , col.df = col.df From 9db0c75781ac99fdfb0fd41fb28515f04365b5f0 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Mon, 2 May 2022 14:43:33 +0200 Subject: [PATCH 106/176] small mistake correction --- R/POST_FATE.validation.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 66bfda0..a101143 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -273,7 +273,7 @@ POST_FATE.validation = function(name.simulation }else if(extent(validation.mask) != extent(simulation.map)){ validation.mask = crop(validation.mask, simulation.map) }else { - validation.mask = vlidation.mask + validation.mask = validation.mask } if(!all(origin(simulation.map) == origin(validation.mask))){ cat("\n setting origin validation mask to match simulation.map \n") From a9bcdd329bcfd8c226a8024381e5c6249134abb7 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Wed, 18 May 2022 09:38:18 +0200 Subject: [PATCH 107/176] Using fread & fwrite function from data.table package instead of read.csv & write.csv to improve reading and writing speed --- R/POST_FATE.validation.R | 39 +++++++----- docs/reference/POST_FATE.validation.html | 80 +++++++++++++----------- docs/reference/train_RF_habitat.html | 3 +- man/POST_FATE.validation.Rd | 72 +++++++++++---------- man/train_RF_habitat.Rd | 3 +- 5 files changed, 110 insertions(+), 87 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index a101143..d5e2a3f 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -161,7 +161,7 @@ ##' ##' @importFrom stringr str_split ##' @importFrom raster raster res crop origin compareCRS extent ncell getValues levels -##' @importFrom utils read.csv write.csv +##' @importFrom utils read.csv ##' @importFrom foreach foreach %dopar% ##' @importFrom forcats fct_expand ##' @importFrom readr write_rds @@ -169,6 +169,7 @@ ##' @importFrom dplyr select rename ##' @importFrom tidyselect all_of ##' @importFrom stats aggregate +##' @importFrom data.table fread fwrite ##' ### END OF HEADER ################################################################### @@ -384,8 +385,9 @@ POST_FATE.validation = function(name.simulation if (perStrata == FALSE) { if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv"))) { - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] # keep only the PFG, ID.pixel and abundance at any year columns + simu_PFG = fread(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv")) + simu_PFG = as.data.frame(simu_PFG) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", year)] # keep only the PFG, ID.pixel and abundance at any year columns # careful : the number of abundance data files to save is to defined in POST_FATE.temporal.evolution function colnames(simu_PFG) = c("PFG", "pixel", "abs") simu_PFG$strata <- "A" @@ -397,8 +399,9 @@ POST_FATE.validation = function(name.simulation } else if (perStrata == TRUE) { if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv"))) { - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv")) - simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", paste0("X", year))] + simu_PFG = fread(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv")) + simu_PFG = as.data.frame(simu_PFG) + simu_PFG = simu_PFG[, c("PFG", "ID.pixel", "strata", year)] colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") new.strata <- rep(NA, nrow(simu_PFG)) for (i in 1:length(list.strata.simulations)) { @@ -505,7 +508,7 @@ POST_FATE.validation = function(name.simulation colnames(habitat.performance) <- c(RF.model$classes, "weighted") habitat.performance$simulation <- sim.version # save - write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) + fwrite(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) cat("\n > Habitat performance saved") # deal with the results regarding habitat prediction over the whole map @@ -513,7 +516,7 @@ POST_FATE.validation = function(name.simulation all.map.prediction = all.map.prediction[,c(sim.version, "pixel", "habitat")] all.map.prediction = rename(all.map.prediction, "true.habitat" = "habitat") # save - write.csv(all.map.prediction, paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) + fwrite(all.map.prediction, paste0(output.path,"/HABITAT/habitat.prediction.csv"), row.names = FALSE) cat("\n > Habitat prediction saved") ## AGGREGATE HABITAT PREDICTION AND PLOT PREDICTED HABITAT @@ -541,7 +544,7 @@ POST_FATE.validation = function(name.simulation colnames(habitat.performance) <- c(RF.model$classes, "weighted") habitat.performance$simulation <- sim.version # save - write.csv(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) + fwrite(habitat.performance, paste0(output.path, "/HABITAT/performance.habitat.csv"), row.names = FALSE) cat("\n > Habitat performance saved") } @@ -560,7 +563,7 @@ POST_FATE.validation = function(name.simulation results.compo$simulation <- rownames(results) #save and return - write.csv(results.compo, paste0(output.path.compo, "/performance.composition.csv"), row.names = FALSE) + fwrite(results.compo, paste0(output.path.compo, "/performance.composition.csv"), row.names = FALSE) cat("\n > Performance composition file saved \n") } @@ -598,8 +601,9 @@ POST_FATE.validation = function(name.simulation if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv"))){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", paste0("X",year))] + simu_PFG = fread(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_", sim, ".csv")) + simu_PFG = as.data.frame(simu_PFG) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", year)] colnames(simu_PFG) = c("PFG", "pixel", "abs") } @@ -608,8 +612,9 @@ POST_FATE.validation = function(name.simulation if(file.exists(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv"))){ - simu_PFG = read.csv(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv")) - simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", paste0("X", year))] + simu_PFG = fread(paste0(name.simulation, "/RESULTS/POST_FATE_TABLE_PIXEL_evolution_abundance_perStrata_", sim, ".csv")) + simu_PFG = as.data.frame(simu_PFG) + simu_PFG = simu_PFG[,c("PFG","ID.pixel", "strata", year)] colnames(simu_PFG) = c("PFG", "pixel", "strata", "abs") } @@ -638,8 +643,8 @@ POST_FATE.validation = function(name.simulation dir.create(output.path, recursive = TRUE, showWarnings = FALSE) - write.csv(PFG.richness.df, paste0(output.path, "/performance.richness.csv"), row.names = F) - write.csv(dying.distribution, paste0(output.path, "/PFG.extinction.frequency.csv"), row.names = F) + fwrite(PFG.richness.df, paste0(output.path, "/performance.richness.csv"), row.names = F) + fwrite(dying.distribution, paste0(output.path, "/PFG.extinction.frequency.csv"), row.names = F) write_rds(dying.PFG.list, file = paste0(output.path, "/dying.PFG.list.rds"), compress = "none") cat("\n > PFG richness results saved \n") @@ -663,10 +668,10 @@ POST_FATE.validation = function(name.simulation if(doHabitat == TRUE & predict.all.map == TRUE){ - hab.pred = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/hab.pred.csv")) + hab.pred = as.data.frame(fread(paste0(name.simulation, "/VALIDATION/HABITAT/hab.pred.csv"))) failure = as.numeric((table(hab.pred$prediction.code)[1]/sum(table(hab.pred$prediction.code)))*100) success = as.numeric((table(hab.pred$prediction.code)[2]/sum(table(hab.pred$prediction.code)))*100) - hab.perf = read.csv(paste0(name.simulation, "/VALIDATION/HABITAT/performance.habitat.csv")) + hab.perf = as.data.frame(fread(paste0(name.simulation, "/VALIDATION/HABITAT/performance.habitat.csv"))) cat("\n ---------- HABITAT : \n") cat(paste0("\n", round(failure, digits = 2), "% of habitats are not correctly predicted by the simulations \n")) diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index 0e574be..81b161d 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -1,12 +1,12 @@ Computes validation data for habitat, PFG richness and composition for a FATE simulation. — POST_FATE.validation • RFateComputes validation data for habitat, PFG richness and composition for a FATE simulation. — POST_FATE.validation • RFate @@ -143,16 +152,22 @@

Computes validation data for habitat, PFG richness and composition for a
-

This script is designed to compute validation data for : -
Habitat : Compares habitat simulations and observations and +

This script is designed to compute validation data for :

Habitat
+

Compares habitat simulations and observations and create a map to visualize this comparison with all the FATE and -observed data (if option selected). -
PFG Composition : Produced a computation of observed distribution +observed data (if option selected).

+ +
PFG Composition
+

Produced a computation of observed distribution of relative abundance in the simulation area and a computation of distance between -observed and simulated distribution. -
PFG Richness : Computes the PFG richness over the whole simulation area -for a FATE simulation and computes the difference between observed and simulated PFG richness.

-
+observed and simulated distribution.

+ +
PFG Richness
+

Computes the PFG richness over the whole simulation area +for a FATE simulation and computes the difference between observed and simulated PFG richness.

+ + +
POST_FATE.validation(
@@ -189,18 +204,18 @@ 

Arguments

year

an integer corresponding to the year of simulation for validation.

perStrata
-

(Logical) Default FALSE.
+

(logical) Default FALSE.
If TRUE, habitat & PFG composition are computed with PFG abundance defined by strata. If FALSE, habitat & PFG composition are computed with PFG abundance defined for all strata.

opt.no_CPU

default 1.
An integer corresponding to the number of resources that can be used to parallelize the computation of prediction performance for habitat & richness validation.

doHabitat
-

(Logical) default TRUE.
If TRUE, habitat validation module is activated, +

(logical) default TRUE.
If TRUE, habitat validation module is activated, if FALSE, habitat validation module is disabled.

releves.PFG

a data.frame with at least 5 columns :
site, x, y, abund, PFG -
(and optionally, strata, code.habitat) +
(and optionally, strata, code.habitat)
(see
Details)
(habitat & PFG composition validation).

hab.obs
@@ -211,7 +226,7 @@

Arguments


(see Details)
(habitat & PFG composition validation).

predict.all.map
-

(Logical) default FALSE.
If TRUE, the function will compute habitat prediction +

(logical) default FALSE.
If TRUE, the function will compute habitat prediction over the whole map and will provide a prediction map.

RF.seed

an integer corresponding to the number of seeds to set in order to generate a Random Forest model.

@@ -227,7 +242,7 @@

Arguments

a character vector which contain FATE strata definition and correspondence with observed strata definition. If perStrata = FALSE, please specify NULL value.

doComposition
-

(Logical) default TRUE.
If TRUE, PFG composition validation module is activated, +

(logical) default TRUE.
If TRUE, PFG composition validation module is activated, if FALSE, PFG composition validation module is disabled.

PFG.considered_PFG.compo

a character vector of the list of PFG considered @@ -240,7 +255,7 @@

Arguments

(selection of one or several specific strata disabled). If perStrata = TRUE, a character vector with at least one of the observed strata (PFG composition validation).

doRichness
-

(Logical) default TRUE.
If TRUE, PFG richness validation module is activated, +

(logical) default TRUE.
If TRUE, PFG richness validation module is activated, if FALSE, PFG richness validation module is disabled.

list.PFG

a character vector which contain all the PFGs taken account in @@ -251,38 +266,81 @@

Arguments

Value

- -
+
Habitat
+

into the name.simulation/VALIDATION/HABITAT/ directory :
+ 1 .csv file containing the prepared observed data.
+ 1 .rds file containing the RF model.
+ 5 .csv file containing the performance analyzes (confusion matrix and TSS) for the training & + testing parts of the RF model and the final habitat performance.
+ If option selected, 1 .csv file containing the habitat prediction within each pixel, 1 .csv file with + sucess or failure of the prediction and 1 .png prediction map.

+ + +
PFG Composition
+

into the name.simulation/VALIDATION/PFG COMPOSITION/ directory :
+ 1 .csv file containing the proximity between observed and simulated data computed for each PFG/strata/habitat.
+ 1 .csv file containing the observed releves transformed into relative metrics.
+ 1 .csv file containing the final output with the distribution per PFG, strata and habitat.

+ + +
PFG richness
+

into the name.simulation/VALIDATION/PFG RICHNESS/ directory :
+ 1 .csv file of PFG richness in a FATE simulation.
+ 1 .csv fie of the PFG extinction frequency in a FATE simulation.
+ 1 .rds file which is the abundance per PFG file.

+ + +

Details

Habitat validation
-

The observed habitat is derived from a map of the area or, if defined, -from studied.habitat, the simulated habitat is derived from FATE simulated relative -abundance, based on a random forest algorithm trained on observed releves data (see train_RF_habitat).
-To compare observations and simulations, the function computes confusion matrix between -observations and predictions and then compute the TSS for each habitat h -(number of prediction of habitat h/number of observation of habitat h + number of non-prediction -of habitat h/number of non-observation of habitat h).
The final metrics this script use is the -mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed -habitat distribution. The habitat validation also provides a visual comparison of observed and -simulated habitat on the whole studied area, if option selected (see do_habitat_validation & -plot_predicted_habitat).

- +


Observed habitat
+

is provided by the hab.obs map. The final set of habitats taken into account + in the validation is provided by studied.habitat table which contains + all the habitats, with their corresponding ID.

+ +
Simulated habitat
+

is determined from FATE simulated relative abundances, thanks to a + Random Forest algorithm.
+ The model is trained on releves.PFG data. Information about PFG abundances at each + sites with xy coordinates are necessary.
+ Eventually, habitat ID information can be provided, as well as strata name. + (see train_RF_habitat).

+ + +

To compare observations and simulations, the function computes confusion matrix between + observations and predictions and then computes the TSS for each habitat h + (number of prediction of habitat h/number of observation of habitat h + number of non-prediction + of habitat h/number of non-observation of habitat h).
The final metric used is the + mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed + habitat distribution. The habitat validation also provides a visual comparison of observed and + simulated habitat on the whole studied area, if option selected + (see do_habitat_validation & plot_predicted_habitat).

+ +
PFG composition validation
-

This code firstly run the get_observed_distribution -function in order to have a obs.distri file which contain the observed distribution -per PFG, strata and habitat. This file is also an argument for the do_PFG_composition_validation -function run next. This second sub function provides the computation of distance between observed -and simulated distribution.

+


Observed distribution
+

is computed for a chosen set of PFG, habitat & strata + (for all strata if strata definition is not activated) by computing 4 quartiles of the distribution, based + on observed releves provided (see get_observed_distribution).

+ +
Simulated distribution
+

is computed in the same way than the observed distribution, with + FATE abundances.

+ + +

Then, a distribution similarity between each habitat/strata combination is provided by computing a + pseudo-distance between observed and simulated quartiles for each PFG y. + $$S_{\text{ habitat, strata}} = \sum S_{\text{ y}}$$ + with + $$S_{\text{ y}} = 1 - \frac{\text{1}}{4} * \sum abs(Q_{\text{ i}{\text{, }sim}} - Q_{\text{ i}{\text{, }obs}})$$

PFG richness validation
-

Firstly, the function updates the list.PFG with exclude.PFG vector. -Then, the script takes the abundance per PFG (and per strata if option selected) file from the -results of the FATE simulation and computes the difference between the list.PFG -and all the PFG which are presents in the abundance file, in order to obtain the PFG richness -for a simulation. The function also determine if an observed PFG is missing in the results of the -simulation at a specific year.

+

the observed PFG richness is computed based on observed data, + the simulated PFG richness is the number of PFG for which abundance over the simulation area + is strictly superior to zero for the simulation year under scrutiny.
+ Then, observed and simulated richness are compared for a set of PFG in order to quantify the PFG mortality.

diff --git a/docs/reference/train_RF_habitat.html b/docs/reference/train_RF_habitat.html index c92a1ac..2802e60 100644 --- a/docs/reference/train_RF_habitat.html +++ b/docs/reference/train_RF_habitat.html @@ -157,7 +157,7 @@

Create a random forest algorithm trained on observed vegetation data

Arguments

releves.PFG

a data.frame with at least 5 columns :
site, x, y, abund, PFG -
(and optionally, strata, code.habitat) +
(and optionally, strata, code.habitat)
(see Details)

external.training.mask

(optional) default NULL. diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index ddcdb31..26477bf 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -35,19 +35,19 @@ POST_FATE.validation( \item{year}{an \code{integer} corresponding to the year of simulation for validation.} -\item{perStrata}{(\code{Logical}) Default \code{FALSE}. \cr +\item{perStrata}{(\code{logical}) Default \code{FALSE}. \cr If \code{TRUE}, habitat & PFG composition are computed with PFG abundance defined by strata. If \code{FALSE}, habitat & PFG composition are computed with PFG abundance defined for all strata.} \item{opt.no_CPU}{default \code{1}. \cr An \code{integer} corresponding to the number of resources that can be used to parallelize the computation of prediction performance for habitat & richness validation.} -\item{doHabitat}{(\code{Logical}) default \code{TRUE}. \cr If \code{TRUE}, habitat validation module is activated, +\item{doHabitat}{(\code{logical}) default \code{TRUE}. \cr If \code{TRUE}, habitat validation module is activated, if \code{FALSE}, habitat validation module is disabled.} \item{releves.PFG}{a \code{data.frame} with at least 5 columns : \cr \code{site}, \code{x}, \code{y}, \code{abund}, \code{PFG} -\cr (\emph{and optionally, \code{strata}}, \code{code.habitat}) +\cr (\emph{and optionally, \code{strata}, \code{code.habitat}}) \cr (see \href{POST_FATE.validation#details}{\code{Details}}) \cr (habitat & PFG composition validation).} @@ -59,7 +59,7 @@ if \code{FALSE}, habitat validation module is disabled.} \cr (see \href{POST_FATE.validation#details}{\code{Details}}) \cr (habitat & PFG composition validation).} -\item{predict.all.map}{(\code{Logical}) default \code{FALSE}. \cr If \code{TRUE}, the function will compute habitat prediction +\item{predict.all.map}{(\code{logical}) default \code{FALSE}. \cr If \code{TRUE}, the function will compute habitat prediction over the whole map and will provide a prediction map.} \item{RF.seed}{an \code{integer} corresponding to the number of seeds to set in order to generate a \code{Random Forest} model.} @@ -75,7 +75,7 @@ If \code{NULL}, the function will take the simulation mask (which means that the a character vector which contain \code{FATE} strata definition and correspondence with observed strata definition. If \code{perStrata} = \code{FALSE}, please specify \code{NULL} value.} -\item{doComposition}{(\code{Logical}) default \code{TRUE}. \cr If \code{TRUE}, PFG composition validation module is activated, +\item{doComposition}{(\code{logical}) default \code{TRUE}. \cr If \code{TRUE}, PFG composition validation module is activated, if \code{FALSE}, PFG composition validation module is disabled.} \item{PFG.considered_PFG.compo}{a \code{character} vector of the list of PFG considered @@ -88,7 +88,7 @@ considered in the validation (PFG composition validation).} (selection of one or several specific strata disabled). If \code{perStrata} = \code{TRUE}, a \code{character} vector with at least one of the observed strata (PFG composition validation).} -\item{doRichness}{(\code{Logical}) default \code{TRUE}. \cr If \code{TRUE}, PFG richness validation module is activated, +\item{doRichness}{(\code{logical}) default \code{TRUE}. \cr If \code{TRUE}, PFG richness validation module is activated, if \code{FALSE}, PFG richness validation module is disabled.} \item{list.PFG}{a \code{character} vector which contain all the PFGs taken account in @@ -98,43 +98,82 @@ the simulation and observed in the simulation area (PFG richness validation).} of the PFG you want to exclude from the analysis (PFG richness validation).} } \value{ - +\describe{ + \item{Habitat}{into the \code{name.simulation/VALIDATION/HABITAT/ directory :} \cr + 1 .csv file containing the prepared observed data. \cr + 1 .rds file containing the RF model. \cr + 5 .csv file containing the performance analyzes (confusion matrix and TSS) for the training & + testing parts of the RF model and the final habitat performance. \cr + If option selected, 1 .csv file containing the habitat prediction within each pixel, 1 .csv file with + sucess or failure of the prediction and 1 .png prediction map.} +} +\describe{ + \item{PFG Composition}{into the \code{name.simulation/VALIDATION/PFG COMPOSITION/ directory :} \cr + 1 .csv file containing the proximity between observed and simulated data computed for each PFG/strata/habitat. \cr + 1 .csv file containing the observed releves transformed into relative metrics. \cr + 1 .csv file containing the final output with the distribution per PFG, strata and habitat.} +} +\describe{ + \item{PFG richness}{into the \code{name.simulation/VALIDATION/PFG RICHNESS/ directory :} \cr + 1 .csv file of PFG richness in a \code{FATE} simulation. \cr + 1 .csv fie of the PFG extinction frequency in a \code{FATE} simulation. \cr + 1 .rds file which is the abundance per PFG file.} +} } \description{ -This script is designed to compute validation data for : -\cr \code{Habitat} : Compares habitat simulations and observations and +This script is designed to compute validation data for : \cr +\describe{ + \item{Habitat}{Compares habitat simulations and observations and create a map to visualize this comparison with all the \code{FATE} and -observed data (if option selected). -\cr \code{PFG Composition} : Produced a computation of observed distribution +observed data (if option selected).} + \item{PFG Composition}{Produced a computation of observed distribution of relative abundance in the simulation area and a computation of distance between -observed and simulated distribution. -\cr \code{PFG Richness} : Computes the PFG richness over the whole simulation area -for a \code{FATE} simulation and computes the difference between observed and simulated PFG richness. +observed and simulated distribution.} + \item{PFG Richness}{Computes the PFG richness over the whole simulation area +for a \code{FATE} simulation and computes the difference between observed and simulated PFG richness.} +} } \details{ \describe{ - \item{Habitat validation}{The observed habitat is derived from a map of the area or, if defined, -from \code{studied.habitat}, the simulated habitat is derived from \code{FATE} simulated relative -abundance, based on a random forest algorithm trained on observed releves data (see \code{\link{train_RF_habitat}}). \cr -To compare observations and simulations, the function computes confusion matrix between -observations and predictions and then compute the TSS for each habitat h -(number of prediction of habitat h/number of observation of habitat h + number of non-prediction -of habitat h/number of non-observation of habitat h). \cr The final metrics this script use is the -mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed -habitat distribution. The habitat validation also provides a visual comparison of observed and -simulated habitat on the whole studied area, if option selected (see \code{\link{do_habitat_validation}} & -\code{\link{plot_predicted_habitat}}).} \cr - \item{PFG composition validation}{This code firstly run the \code{get_observed_distribution} -function in order to have a \code{obs.distri} file which contain the observed distribution -per PFG, strata and habitat. This file is also an argument for the \code{do_PFG_composition_validation} -function run next. This second sub function provides the computation of distance between observed -and simulated distribution.} - \item{PFG richness validation}{Firstly, the function updates the \code{list.PFG} with \code{exclude.PFG} vector. -Then, the script takes the abundance per PFG (and per strata if option selected) file from the -results of the \code{FATE} simulation and computes the difference between the \code{list.PFG} -and all the PFG which are presents in the abundance file, in order to obtain the PFG richness -for a simulation. The function also determine if an observed PFG is missing in the results of the -simulation at a specific year.} + \item{Habitat validation}{ \cr + \describe{ + \item{Observed habitat}{is provided by the \strong{hab.obs} map. The final set of habitats taken into account + in the validation is provided by \strong{studied.habitat} table which contains + all the \strong{habitats}, with their corresponding \strong{ID}.} + \item{Simulated habitat}{is determined from \code{FATE} simulated relative abundances, thanks to a + \code{Random Forest} algorithm. \cr + The model is trained on \strong{releves.PFG} data. Information about \strong{PFG abundances} at each + \strong{sites} with \strong{xy} coordinates are necessary. \cr + Eventually, \strong{habitat ID} information can be provided, as well as \strong{strata} name. + (see \code{\link{train_RF_habitat}}).} + } + To compare observations and simulations, the function computes confusion matrix between + observations and predictions and then computes the TSS for each habitat h + (number of prediction of habitat h/number of observation of habitat h + number of non-prediction + of habitat h/number of non-observation of habitat h). \cr The final metric used is the + mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed + habitat distribution. The habitat validation also provides a visual comparison of observed and + simulated habitat on the whole studied area, if option selected + (see \code{\link{do_habitat_validation}} & \code{\link{plot_predicted_habitat}}).} + + \item{PFG composition validation}{ \cr + \describe{ + \item{Observed distribution}{is computed for a chosen set of PFG, habitat & strata + (for all strata if strata definition is not activated) by computing 4 quartiles of the distribution, based + on observed releves provided (see \code{\link{get_observed_distribution}}).} + \item{Simulated distribution}{is computed in the same way than the observed distribution, with + \code{FATE} abundances.} + } + Then, a distribution similarity between each habitat/strata combination is provided by computing a + pseudo-distance between observed and simulated quartiles for each PFG y. + \deqn{S_{\text{ habitat, strata}} = \sum S_{\text{ y}}} + with + \deqn{S_{\text{ y}} = 1 - \frac{\text{1}}{4} * \sum abs(Q_{\text{ i}{\text{, }sim}} - Q_{\text{ i}{\text{, }obs}})} + } + \item{PFG richness validation}{the observed PFG richness is computed based on observed data, + the simulated PFG richness is the number of PFG for which abundance over the simulation area + is strictly superior to zero for the simulation year under scrutiny. \cr + Then, observed and simulated richness are compared for a set of PFG in order to quantify the PFG mortality.} } } \author{ diff --git a/man/train_RF_habitat.Rd b/man/train_RF_habitat.Rd index 07b0bff..78627c8 100644 --- a/man/train_RF_habitat.Rd +++ b/man/train_RF_habitat.Rd @@ -18,7 +18,7 @@ train_RF_habitat( \arguments{ \item{releves.PFG}{a \code{data.frame} with at least 5 columns : \cr \code{site}, \code{x}, \code{y}, \code{abund}, \code{PFG} -\cr (\emph{and optionally, \code{strata}}, \code{code.habitat}) +\cr (\emph{and optionally, \code{strata}, \code{code.habitat}}) \cr (see \href{train_RF_habitat#details}{\code{Details}})} \item{external.training.mask}{(optional) default \code{NULL}. From 166255673ed237c6b12d06e3a2c5c5312914254e Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Thu, 9 Jun 2022 16:05:06 +0200 Subject: [PATCH 130/176] minor changes in documentation --- R/POST_FATE.validation.R | 3 ++- docs/reference/POST_FATE.validation.html | 2 +- man/POST_FATE.validation.Rd | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index a2a153e..ff759de 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -118,7 +118,7 @@ ##' \item{Habitat}{into the \code{name.simulation/VALIDATION/HABITAT/ directory :} \cr ##' 1 .csv file containing the prepared observed data. \cr ##' 1 .rds file containing the RF model. \cr -##' 5 .csv file containing the performance analyzes (confusion matrix and TSS) for the training & +##' 5 .csv files containing the performance analyzes (confusion matrix and TSS) for the training & ##' testing parts of the RF model and the final habitat performance. \cr ##' If option selected, 1 .csv file containing the habitat prediction within each pixel, 1 .csv file with ##' sucess or failure of the prediction and 1 .png prediction map.} @@ -178,6 +178,7 @@ ##' , doRichness = TRUE ##' , list.PFG = list.PFG) ##' +##' ##' @export ##' ##' @importFrom stringr str_split diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index c76c08b..a2ea939 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -270,7 +270,7 @@

Value

into the name.simulation/VALIDATION/HABITAT/ directory :
1 .csv file containing the prepared observed data.
1 .rds file containing the RF model.
- 5 .csv file containing the performance analyzes (confusion matrix and TSS) for the training & + 5 .csv files containing the performance analyzes (confusion matrix and TSS) for the training & testing parts of the RF model and the final habitat performance.
If option selected, 1 .csv file containing the habitat prediction within each pixel, 1 .csv file with sucess or failure of the prediction and 1 .png prediction map.

diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index 26477bf..373ad48 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -102,7 +102,7 @@ of the PFG you want to exclude from the analysis (PFG richness validation).} \item{Habitat}{into the \code{name.simulation/VALIDATION/HABITAT/ directory :} \cr 1 .csv file containing the prepared observed data. \cr 1 .rds file containing the RF model. \cr - 5 .csv file containing the performance analyzes (confusion matrix and TSS) for the training & + 5 .csv files containing the performance analyzes (confusion matrix and TSS) for the training & testing parts of the RF model and the final habitat performance. \cr If option selected, 1 .csv file containing the habitat prediction within each pixel, 1 .csv file with sucess or failure of the prediction and 1 .png prediction map.} From 8026709026b64f785bdd1e189d406552397e1e81 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 10 Jun 2022 12:09:11 +0200 Subject: [PATCH 131/176] correction in documentation of the validation functions --- NAMESPACE | 5 -- R/POST_FATE.validation.R | 26 +++++---- R/UTILS.do_PFG_composition_validation.R | 2 - R/UTILS.do_habitat_validation.R | 23 ++++---- R/UTILS.get_observed_distribution.R | 2 - R/UTILS.plot_predicted_habitat.R | 2 - R/UTILS.train_RF_habitat.R | 34 ++++++------ docs/reference/POST_FATE.validation.html | 68 ++++++++++++++++++++--- docs/reference/do_habitat_validation.html | 23 ++++---- docs/reference/train_RF_habitat.html | 29 +++++----- man/POST_FATE.validation.Rd | 64 ++++++++++++++++++--- man/do_habitat_validation.Rd | 21 +++---- man/train_RF_habitat.Rd | 31 ++++++----- 13 files changed, 213 insertions(+), 117 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index b1babe3..13f1a18 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -54,13 +54,8 @@ export(betapart.core) export(cluster.stats) export(designLHDNorm) export(divLeinster) -export(do_PFG_composition_validation) -export(do_habitat_validation) export(dunn) export(ecospat.kd) -export(get_observed_distribution) -export(plot_predicted_habitat) -export(train_RF_habitat) importFrom(FD,gowdis) importFrom(PresenceAbsence,auc) importFrom(PresenceAbsence,cmx) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index ff759de..81d2ede 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -81,8 +81,7 @@ ##' \code{Random Forest} algorithm. \cr ##' The model is trained on \strong{releves.PFG} data. Information about \strong{PFG abundances} at each ##' \strong{sites} with \strong{xy} coordinates are necessary. \cr -##' Eventually, \strong{habitat ID} information can be provided, as well as \strong{strata} name. -##' (see \code{\link{train_RF_habitat}}).} +##' Eventually, \strong{habitat ID} information can be provided, as well as \strong{strata} name.} ##' } ##' To compare observations and simulations, the function computes confusion matrix between ##' observations and predictions and then computes the TSS for each habitat h @@ -90,14 +89,13 @@ ##' of habitat h/number of non-observation of habitat h). \cr The final metric used is the ##' mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed ##' habitat distribution. The habitat validation also provides a visual comparison of observed and -##' simulated habitat on the whole studied area, if option selected -##' (see \code{\link{do_habitat_validation}} & \code{\link{plot_predicted_habitat}}).} +##' simulated habitat on the whole studied area, if option selected.} ##' ##' \item{PFG composition validation}{ \cr ##' \describe{ ##' \item{Observed distribution}{is computed for a chosen set of PFG, habitat & strata ##' (for all strata if strata definition is not activated) by computing 4 quartiles of the distribution, based -##' on observed releves provided (see \code{\link{get_observed_distribution}}).} +##' on observed releves provided.} ##' \item{Simulated distribution}{is computed in the same way than the observed distribution, with ##' \code{FATE} abundances.} ##' } @@ -106,6 +104,7 @@ ##' \deqn{S_{\text{ habitat, strata}} = \sum S_{\text{ y}}} ##' with ##' \deqn{S_{\text{ y}} = 1 - \frac{\text{1}}{4} * \sum abs(Q_{\text{ i}{\text{, }sim}} - Q_{\text{ i}{\text{, }obs}})} +##' with i varying from 1 to 4. ##' } ##' \item{PFG richness validation}{the observed PFG richness is computed based on observed data, ##' the simulated PFG richness is the number of PFG for which abundance over the simulation area @@ -116,9 +115,9 @@ ##' @return ##' \describe{ ##' \item{Habitat}{into the \code{name.simulation/VALIDATION/HABITAT/ directory :} \cr -##' 1 .csv file containing the prepared observed data. \cr +##' 1 .csv file containing the prepared observed data (relative abundances & habitat information). \cr ##' 1 .rds file containing the RF model. \cr -##' 5 .csv files containing the performance analyzes (confusion matrix and TSS) for the training & +##' 5 .csv files containing the performance analysis (confusion matrix and TSS) for the training & ##' testing parts of the RF model and the final habitat performance. \cr ##' If option selected, 1 .csv file containing the habitat prediction within each pixel, 1 .csv file with ##' sucess or failure of the prediction and 1 .png prediction map.} @@ -138,27 +137,34 @@ ##' ##' @examples ##' +##' library(raster) +##' ##' ## Load example data ##' Champsaur_params = .loadData("Champsaur_params", "RData") ##' ##' ## Create a skeleton folder -##' PRE_FATE.skeletonDirectory(name.simulation = "FATE_Champsaur) +##' PRE_FATE.skeletonDirectory(name.simulation = "FATE_Champsaur") ##' ##' ## Load results from a simulation ##' .loadData("Champsaur_results_V1", "7z") ##' -##' ## Extract files from the 7z folder and put 'POST_FATE_TABLE_PIXEL_evolution_abundance_SIMUL_V1.1.csv' file in the RESULTS folder +##' ## Please extract files from the 7z folder in 'FATE_CHAMPSAUR/RESULTS' ##' ##' ## Define a vector to choose habitats taken into account ##' studied.habitat = data.frame(ID = c(6, 5, 7, 8), habitat = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland")) +##' ##' ## Habitat & validation maps ##' hab.observed = Champsaur_params$stk.mask$habitat ##' simulation.map = Champsaur_params$stk.mask$Champsaur ##' hab.obs = projectRaster(from = hab.observed, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") +##' writeRaster(simulation.map, filename = "FATE_Champsaur/DATA/MASK/MASK_Champsaur.tif") +##' ##' ## Observed data ##' releves.PFG = Champsaur_params$tab.releves +##' ##' ## List of PFG taken into account in a FATE simulation ##' list.PFG = as.factor(c("C1", "C2", "C3", "C4", "H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5")) +##' ##' ## Habitat, strata and PFG considered in PFG compo validation ##' habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") ##' PFG.considered_PFG.compo = as.factor(c("H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5")) @@ -171,7 +177,7 @@ ##' , hab.obs = hab.obs ##' , studied.habitat = studied.habitat ##' , predict.all.map = TRUE -##' , seed = 123 +##' , RF.seed = 123 ##' , doComposition = TRUE ##' , PFG.considered_PFG.compo = PFG.considered_PFG.compo ##' , habitat.considered_PFG.compo = habitat.considered diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index c782d13..1ec1981 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -44,8 +44,6 @@ ##' \item{\file{VALIDATION/PFG_COMPOSITION} : \cr ##' A .csv file which contain the proximity between observed and simulated data computed ##' for each PFG/strata/habitat. -##' -##' @export ##' ##' @importFrom dplyr rename filter group_by mutate %>% select ##' @importFrom raster raster projectRaster res crs crop extent origin compareRaster diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 58c99bd..8e726f0 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -7,27 +7,28 @@ ##' ##' @author Matthieu Combaud & Maxime Delprat ##' -##' @description To compare observations and simulations, this function compute +##' @description To compare observations and simulations, this function computes ##' confusion matrix between observation and prediction and then compute the TSS ##' for each habitats. ##' -##' @param output.path Access path to the for the folder where output files +##' @param output.path a \code{string} containing the access path to the for the folder where output files ##' will be created. -##' @param RF.model Random forest model trained on observed abundance data (\code{\link{train_RF_habitat}} +##' @param RF.model random forest model trained on observed abundance data (output of \code{train_RF_habitat} ##' function) -##' @param predict.all.map \code{Logical}. If TRUE, the script will predict -##' habitat for the whole map. -##' @param sim Name of the single simulation to validate. -##' @param simu_PFG A \code{data.frame} provides by \code{POST_FATE.temporalEvolution} with simulated abundance for each PFG and strata +##' @param predict.all.map \code{logical}. \cr +##' If TRUE, the script will predict habitat for the whole map. +##' @param sim a \code{string} containing the name of the single simulation to validate. +##' @param simu_PFG a \code{data.frame} provides by \code{POST_FATE.temporalEvolution} with simulated abundance for each PFG and strata ##' (if option selected) and pixel ID (see \code{\link{POST_FATE.temporalEvolution}}). -##' @param habitat.whole.area.df A \code{data.frame} provides by \code{POST_FATE.validation} with 3 columns : +##' @param habitat.whole.area.df a \code{data.frame} built in \code{POST_FATE.validation} with 3 columns : ##' \cr \code{pixel} which contains the ID of each pixel in the study area. ##' \cr \code{code.habitat} which contains the ID of the habitat in each pixel. ##' \cr \code{for.validation} for each pixel, \code{0} if does not need validation, \code{1} if needs validation. -##' @param list.strata If abundance file is defined by strata : a character vector which contains \code{FATE} +##' @param list.strata if abundance file is defined by strata : a character vector containing \code{FATE} ##' strata definition and correspondence with observed strata definition. \cr ##' If abundance file is defined for all strata : a character vector with value "all". -##' @param perStrata \code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. +##' @param perStrata \code{logical}. \cr +##' If \code{TRUE}, PFG abundance is defined by strata. ##' If \code{FALSE}, PFG abundance defined for all strata. ##' ##' @details @@ -43,8 +44,6 @@ ##' If option selected, the function also returns an habitat prediction file with ##' observed and simulated habitat for each pixel of the whole map. ##' -##' @export -##' ##' @importFrom dplyr group_by %>% mutate rename select ##' @importFrom raster predict ##' @importFrom reshape2 dcast diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index 7c5160b..93c2adb 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -45,8 +45,6 @@ ##' \item{\file{VALIDATION/PFG_COMPOSITION} : \cr ##' 1 .csv file which contain the observed relevés transformed into relative metrics. \cr ##' 1 .csv file which contain the final output with the distribution per PFG, strata and habitat. -##' -##' @export ##' ##' @importFrom dplyr select filter group_by mutate %>% rename ##' @importFrom raster compareCRS res crs levels diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index 5fe3197..e8c1a73 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -35,8 +35,6 @@ ##' ##' a synthetic.prediction.png file which contain the final prediction map. ##' -##' @export -##' ##' @importFrom dplyr all_of rename select ##' @importFrom utils write.csv ##' @importFrom raster raster crs extent res ratify writeRaster levels diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index da3690f..8ecf8ca 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -6,8 +6,8 @@ ##' ##' @author Matthieu Combaud, Maxime Delprat ##' -##' @description This script is designed to produce a random forest model -##' trained on observed PFG abundance and a map of observed habitat. +##' @description This script is designed to produce a \code{Random Forest} model +##' trained on observed PFG abundances & habitats. ##' ##' @param releves.PFG a \code{data.frame} with at least 5 columns : \cr ##' \code{site}, \code{x}, \code{y}, \code{abund}, \code{PFG} @@ -21,30 +21,28 @@ ##' \code{ID} ,\code{habitat} ##' \cr (see \href{train_RF_habitat#details}{\code{Details}}) ##' @param RF.param a \code{list} of 2 parameters to fit a random forest model : \cr -##' \code{share.training} defines the size of the training part of the data base. \cr -##' \code{ntree} is the number of trees build by the algorithm, it allows to reduce the prediction error. +##' \code{share.training} defines the proportion of the data base used for training the model. \cr +##' \code{ntree} is the number of trees built by the algorithm, it allows to reduce the prediction error. ##' @param output.path access path to the folder where output files will be created. -##' @param perStrata (\code{Logical}) default \code{FALSE}. +##' @param perStrata (\code{logical}) default \code{FALSE}. ##' \cr If \code{TRUE}, the PFG abundance must be defined ##' by strata in each site. If \code{FALSE}, PFG abundance must be defined for all strata. ##' ##' @details ##' -##' This function transforms PFG abundance in relative abundance, -##' gets habitat information from an habitat data frame previously defined and a habitat map, -##' keep releves on interesting habitat(s) and then builds a random forest model. Finally, -##' the function analyzes the model performance with computation of confusion matrix and TSS between -##' the training and testing sample. +##' The \code{Random Forest} algorithm is trained on \strong{releves.PFG} data. \cr +##' Information about \strong{PFG abundances} at each \strong{sites} with \strong{xy} coordinates are necessary. \cr +##' Eventually, \strong{habitat ID} information can be provided. If not, observed habitat is provided by the +##' \strong{hab.obs.RF} map. The final set of habitats taken into account in the validation is provided by +##' \strong{studied.habitat} table containing all the \strong{habitats}, with their corresponding \strong{ID}. +##' Finally, a performance analysis is provided by computing TSS between training and testing samples of the data base. ##' ##' @return -##' -##' 2 prepared observed releves files are created before the building of the random -##' forest model in a folder previously defined. \cr -##' 5 more files are created at the end of the script to save the RF model and -##' the performance analyzes (confusion matrix and TSS) for the training and -##' testing parts. -##' -##' @export +##' into the \code{name.simulation/VALIDATION/HABITAT/ directory :} \cr +##' 1 .csv file containing the prepared observed data (relative abundances & habitat information). \cr +##' 1 .rds file containing the RF model. \cr +##' 4 .csv files containing the performance analysis (2 TSS per habitat files, and 2 aggregate TSS files) for the training & +##' testing samples. ##' ##' @importFrom dplyr filter %>% group_by select ##' @importFrom stats aggregate diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index a2ea939..195bc52 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -268,9 +268,9 @@

Arguments

Value

Habitat

into the name.simulation/VALIDATION/HABITAT/ directory :
- 1 .csv file containing the prepared observed data.
+ 1 .csv file containing the prepared observed data (relative abundances & habitat information).
1 .rds file containing the RF model.
- 5 .csv files containing the performance analyzes (confusion matrix and TSS) for the training & + 5 .csv files containing the performance analysis (confusion matrix and TSS) for the training & testing parts of the RF model and the final habitat performance.
If option selected, 1 .csv file containing the habitat prediction within each pixel, 1 .csv file with sucess or failure of the prediction and 1 .png prediction map.

@@ -305,8 +305,7 @@

Details

Random Forest algorithm.
The model is trained on releves.PFG data. Information about PFG abundances at each sites with xy coordinates are necessary.
- Eventually, habitat ID information can be provided, as well as strata name. - (see train_RF_habitat).

+ Eventually, habitat ID information can be provided, as well as strata name.

To compare observations and simulations, the function computes confusion matrix between @@ -315,15 +314,14 @@

Details

of habitat h/number of non-observation of habitat h).
The final metric used is the mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed habitat distribution. The habitat validation also provides a visual comparison of observed and - simulated habitat on the whole studied area, if option selected - (see do_habitat_validation & plot_predicted_habitat).

+ simulated habitat on the whole studied area, if option selected.

PFG composition validation


Observed distribution

is computed for a chosen set of PFG, habitat & strata (for all strata if strata definition is not activated) by computing 4 quartiles of the distribution, based - on observed releves provided (see get_observed_distribution).

+ on observed releves provided.

Simulated distribution

is computed in the same way than the observed distribution, with @@ -334,7 +332,8 @@

Details

pseudo-distance between observed and simulated quartiles for each PFG y. $$S_{\text{ habitat, strata}} = \sum S_{\text{ y}}$$ with - $$S_{\text{ y}} = 1 - \frac{\text{1}}{4} * \sum abs(Q_{\text{ i}{\text{, }sim}} - Q_{\text{ i}{\text{, }obs}})$$

+ $$S_{\text{ y}} = 1 - \frac{\text{1}}{4} * \sum abs(Q_{\text{ i}{\text{, }sim}} - Q_{\text{ i}{\text{, }obs}})$$ + with i varying from 1 to 4.

PFG richness validation

the observed PFG richness is computed based on observed data, @@ -349,6 +348,59 @@

Author

Matthieu Combaud, Maxime Delprat

+
+

Examples

+

+library(raster)
+
+## Load example data
+Champsaur_params = .loadData("Champsaur_params", "RData")
+
+## Create a skeleton folder
+PRE_FATE.skeletonDirectory(name.simulation = "FATE_Champsaur")
+
+## Load results from a simulation
+.loadData("Champsaur_results_V1", "7z")
+
+## Please extract files from the 7z folder in 'FATE_CHAMPSAUR/RESULTS'
+
+## Define a vector to choose habitats taken into account
+studied.habitat = data.frame(ID = c(6, 5, 7, 8), habitat = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland"))
+
+## Habitat & validation maps
+hab.observed = Champsaur_params$stk.mask$habitat
+simulation.map = Champsaur_params$stk.mask$Champsaur
+hab.obs = projectRaster(from = hab.observed, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb")
+writeRaster(simulation.map, filename = "FATE_Champsaur/DATA/MASK/MASK_Champsaur.tif")
+
+## Observed data
+releves.PFG = Champsaur_params$tab.releves
+
+## List of PFG taken into account in a FATE simulation
+list.PFG = as.factor(c("C1", "C2", "C3", "C4", "H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5"))
+
+## Habitat, strata and PFG considered in PFG compo validation
+habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland")
+PFG.considered_PFG.compo = as.factor(c("H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5"))
+
+POST_FATE.validation(name.simulation = "FATE_Champsaur"
+                     , sim.version = "SIMUL_V1.1"
+                     , year = 2000
+                     , doHabitat = TRUE
+                     , releves.PFG = releves.PFG
+                     , hab.obs = hab.obs
+                     , studied.habitat = studied.habitat
+                     , predict.all.map = TRUE
+                     , RF.seed = 123
+                     , doComposition = TRUE
+                     , PFG.considered_PFG.compo = PFG.considered_PFG.compo
+                     , habitat.considered_PFG.compo = habitat.considered
+                     , doRichness = TRUE
+                     , list.PFG = list.PFG)
+
+
+
+
-

This script is designed to produce a random forest model -trained on observed PFG abundance and a map of observed habitat.

+

This script is designed to produce a Random Forest model +trained on observed PFG abundances & habitats.

@@ -166,28 +166,27 @@

Arguments

a data.frame with 2 columns :
ID ,habitat
(see Details)

RF.param
-

a list of 2 parameters to fit a random forest model :
share.training defines the size of the training part of the data base.
ntree is the number of trees build by the algorithm, it allows to reduce the prediction error.

+

a list of 2 parameters to fit a random forest model :
share.training defines the proportion of the data base used for training the model.
ntree is the number of trees built by the algorithm, it allows to reduce the prediction error.

output.path

access path to the folder where output files will be created.

perStrata
-

(Logical) default FALSE. +

(logical) default FALSE.
If TRUE, the PFG abundance must be defined by strata in each site. If FALSE, PFG abundance must be defined for all strata.

Value

-

2 prepared observed releves files are created before the building of the random -forest model in a folder previously defined.
5 more files are created at the end of the script to save the RF model and -the performance analyzes (confusion matrix and TSS) for the training and -testing parts.

+

into the name.simulation/VALIDATION/HABITAT/ directory :
1 .csv file containing the prepared observed data (relative abundances & habitat information).
1 .rds file containing the RF model.
4 .csv files containing the performance analysis (2 TSS per habitat files, and 2 aggregate TSS files) for the training & +testing samples.

Details

-

This function transforms PFG abundance in relative abundance, -gets habitat information from an habitat data frame previously defined and a habitat map, -keep releves on interesting habitat(s) and then builds a random forest model. Finally, -the function analyzes the model performance with computation of confusion matrix and TSS between -the training and testing sample.

+

The Random Forest algorithm is trained on releves.PFG data.
+Information about PFG abundances at each sites with xy coordinates are necessary.
+Eventually, habitat ID information can be provided. If not, observed habitat is provided by the +hab.obs.RF map. The final set of habitats taken into account in the validation is provided by +studied.habitat table containing all the habitats, with their corresponding ID. +Finally, a performance analysis is provided by computing TSS between training and testing samples of the data base.

Author

diff --git a/man/POST_FATE.validation.Rd b/man/POST_FATE.validation.Rd index 373ad48..20e3fcb 100644 --- a/man/POST_FATE.validation.Rd +++ b/man/POST_FATE.validation.Rd @@ -100,9 +100,9 @@ of the PFG you want to exclude from the analysis (PFG richness validation).} \value{ \describe{ \item{Habitat}{into the \code{name.simulation/VALIDATION/HABITAT/ directory :} \cr - 1 .csv file containing the prepared observed data. \cr + 1 .csv file containing the prepared observed data (relative abundances & habitat information). \cr 1 .rds file containing the RF model. \cr - 5 .csv files containing the performance analyzes (confusion matrix and TSS) for the training & + 5 .csv files containing the performance analysis (confusion matrix and TSS) for the training & testing parts of the RF model and the final habitat performance. \cr If option selected, 1 .csv file containing the habitat prediction within each pixel, 1 .csv file with sucess or failure of the prediction and 1 .png prediction map.} @@ -144,8 +144,7 @@ for a \code{FATE} simulation and computes the difference between observed and si \code{Random Forest} algorithm. \cr The model is trained on \strong{releves.PFG} data. Information about \strong{PFG abundances} at each \strong{sites} with \strong{xy} coordinates are necessary. \cr - Eventually, \strong{habitat ID} information can be provided, as well as \strong{strata} name. - (see \code{\link{train_RF_habitat}}).} + Eventually, \strong{habitat ID} information can be provided, as well as \strong{strata} name.} } To compare observations and simulations, the function computes confusion matrix between observations and predictions and then computes the TSS for each habitat h @@ -153,14 +152,13 @@ for a \code{FATE} simulation and computes the difference between observed and si of habitat h/number of non-observation of habitat h). \cr The final metric used is the mean of TSS per habitat over all habitats, weighted by the share of each habitat in the observed habitat distribution. The habitat validation also provides a visual comparison of observed and - simulated habitat on the whole studied area, if option selected - (see \code{\link{do_habitat_validation}} & \code{\link{plot_predicted_habitat}}).} + simulated habitat on the whole studied area, if option selected.} \item{PFG composition validation}{ \cr \describe{ \item{Observed distribution}{is computed for a chosen set of PFG, habitat & strata (for all strata if strata definition is not activated) by computing 4 quartiles of the distribution, based - on observed releves provided (see \code{\link{get_observed_distribution}}).} + on observed releves provided.} \item{Simulated distribution}{is computed in the same way than the observed distribution, with \code{FATE} abundances.} } @@ -169,12 +167,64 @@ for a \code{FATE} simulation and computes the difference between observed and si \deqn{S_{\text{ habitat, strata}} = \sum S_{\text{ y}}} with \deqn{S_{\text{ y}} = 1 - \frac{\text{1}}{4} * \sum abs(Q_{\text{ i}{\text{, }sim}} - Q_{\text{ i}{\text{, }obs}})} + with i varying from 1 to 4. } \item{PFG richness validation}{the observed PFG richness is computed based on observed data, the simulated PFG richness is the number of PFG for which abundance over the simulation area is strictly superior to zero for the simulation year under scrutiny. \cr Then, observed and simulated richness are compared for a set of PFG in order to quantify the PFG mortality.} } +} +\examples{ + +library(raster) + +## Load example data +Champsaur_params = .loadData("Champsaur_params", "RData") + +## Create a skeleton folder +PRE_FATE.skeletonDirectory(name.simulation = "FATE_Champsaur") + +## Load results from a simulation +.loadData("Champsaur_results_V1", "7z") + +## Please extract files from the 7z folder in 'FATE_CHAMPSAUR/RESULTS' + +## Define a vector to choose habitats taken into account +studied.habitat = data.frame(ID = c(6, 5, 7, 8), habitat = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland")) + +## Habitat & validation maps +hab.observed = Champsaur_params$stk.mask$habitat +simulation.map = Champsaur_params$stk.mask$Champsaur +hab.obs = projectRaster(from = hab.observed, res = res(simulation.map)[1], crs = crs(projection(simulation.map)), method = "ngb") +writeRaster(simulation.map, filename = "FATE_Champsaur/DATA/MASK/MASK_Champsaur.tif") + +## Observed data +releves.PFG = Champsaur_params$tab.releves + +## List of PFG taken into account in a FATE simulation +list.PFG = as.factor(c("C1", "C2", "C3", "C4", "H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5")) + +## Habitat, strata and PFG considered in PFG compo validation +habitat.considered = c("coniferous.forest", "deciduous.forest", "natural.grassland", "woody.heatland") +PFG.considered_PFG.compo = as.factor(c("H1", "H2", "H3", "H4", "H5", "H6", "P1", "P2", "P3", "P4", "P5")) + +POST_FATE.validation(name.simulation = "FATE_Champsaur" + , sim.version = "SIMUL_V1.1" + , year = 2000 + , doHabitat = TRUE + , releves.PFG = releves.PFG + , hab.obs = hab.obs + , studied.habitat = studied.habitat + , predict.all.map = TRUE + , RF.seed = 123 + , doComposition = TRUE + , PFG.considered_PFG.compo = PFG.considered_PFG.compo + , habitat.considered_PFG.compo = habitat.considered + , doRichness = TRUE + , list.PFG = list.PFG) + + } \author{ Matthieu Combaud, Maxime Delprat diff --git a/man/do_habitat_validation.Rd b/man/do_habitat_validation.Rd index 18b0927..e9f2972 100644 --- a/man/do_habitat_validation.Rd +++ b/man/do_habitat_validation.Rd @@ -17,30 +17,31 @@ do_habitat_validation( ) } \arguments{ -\item{output.path}{Access path to the for the folder where output files +\item{output.path}{a \code{string} containing the access path to the for the folder where output files will be created.} -\item{RF.model}{Random forest model trained on observed abundance data (\code{\link{train_RF_habitat}} +\item{RF.model}{random forest model trained on observed abundance data (output of \code{train_RF_habitat} function)} -\item{predict.all.map}{\code{Logical}. If TRUE, the script will predict -habitat for the whole map.} +\item{predict.all.map}{\code{logical}. \cr +If TRUE, the script will predict habitat for the whole map.} -\item{sim}{Name of the single simulation to validate.} +\item{sim}{a \code{string} containing the name of the single simulation to validate.} -\item{simu_PFG}{A \code{data.frame} provides by \code{POST_FATE.temporalEvolution} with simulated abundance for each PFG and strata +\item{simu_PFG}{a \code{data.frame} provides by \code{POST_FATE.temporalEvolution} with simulated abundance for each PFG and strata (if option selected) and pixel ID (see \code{\link{POST_FATE.temporalEvolution}}).} -\item{habitat.whole.area.df}{A \code{data.frame} provides by \code{POST_FATE.validation} with 3 columns : +\item{habitat.whole.area.df}{a \code{data.frame} built in \code{POST_FATE.validation} with 3 columns : \cr \code{pixel} which contains the ID of each pixel in the study area. \cr \code{code.habitat} which contains the ID of the habitat in each pixel. \cr \code{for.validation} for each pixel, \code{0} if does not need validation, \code{1} if needs validation.} -\item{list.strata}{If abundance file is defined by strata : a character vector which contains \code{FATE} +\item{list.strata}{if abundance file is defined by strata : a character vector containing \code{FATE} strata definition and correspondence with observed strata definition. \cr If abundance file is defined for all strata : a character vector with value "all".} -\item{perStrata}{\code{Logical}. Default \code{TRUE}. If \code{TRUE}, PFG abundance is defined by strata. +\item{perStrata}{\code{logical}. \cr +If \code{TRUE}, PFG abundance is defined by strata. If \code{FALSE}, PFG abundance defined for all strata.} } \value{ @@ -49,7 +50,7 @@ If option selected, the function also returns an habitat prediction file with observed and simulated habitat for each pixel of the whole map. } \description{ -To compare observations and simulations, this function compute +To compare observations and simulations, this function computes confusion matrix between observation and prediction and then compute the TSS for each habitats. } diff --git a/man/train_RF_habitat.Rd b/man/train_RF_habitat.Rd index 78627c8..39a615b 100644 --- a/man/train_RF_habitat.Rd +++ b/man/train_RF_habitat.Rd @@ -29,32 +29,33 @@ train_RF_habitat( \cr (see \href{train_RF_habitat#details}{\code{Details}})} \item{RF.param}{a \code{list} of 2 parameters to fit a random forest model : \cr -\code{share.training} defines the size of the training part of the data base. \cr -\code{ntree} is the number of trees build by the algorithm, it allows to reduce the prediction error.} +\code{share.training} defines the proportion of the data base used for training the model. \cr +\code{ntree} is the number of trees built by the algorithm, it allows to reduce the prediction error.} \item{output.path}{access path to the folder where output files will be created.} -\item{perStrata}{(\code{Logical}) default \code{FALSE}. +\item{perStrata}{(\code{logical}) default \code{FALSE}. \cr If \code{TRUE}, the PFG abundance must be defined by strata in each site. If \code{FALSE}, PFG abundance must be defined for all strata.} } \value{ -2 prepared observed releves files are created before the building of the random -forest model in a folder previously defined. \cr -5 more files are created at the end of the script to save the RF model and -the performance analyzes (confusion matrix and TSS) for the training and -testing parts. +into the \code{name.simulation/VALIDATION/HABITAT/ directory :} \cr +1 .csv file containing the prepared observed data (relative abundances & habitat information). \cr +1 .rds file containing the RF model. \cr +4 .csv files containing the performance analysis (2 TSS per habitat files, and 2 aggregate TSS files) for the training & +testing samples. } \description{ -This script is designed to produce a random forest model -trained on observed PFG abundance and a map of observed habitat. +This script is designed to produce a \code{Random Forest} model +trained on observed PFG abundances & habitats. } \details{ -This function transforms PFG abundance in relative abundance, -gets habitat information from an habitat data frame previously defined and a habitat map, -keep releves on interesting habitat(s) and then builds a random forest model. Finally, -the function analyzes the model performance with computation of confusion matrix and TSS between -the training and testing sample. +The \code{Random Forest} algorithm is trained on \strong{releves.PFG} data. \cr +Information about \strong{PFG abundances} at each \strong{sites} with \strong{xy} coordinates are necessary. \cr +Eventually, \strong{habitat ID} information can be provided. If not, observed habitat is provided by the +\strong{hab.obs.RF} map. The final set of habitats taken into account in the validation is provided by +\strong{studied.habitat} table containing all the \strong{habitats}, with their corresponding \strong{ID}. +Finally, a performance analysis is provided by computing TSS between training and testing samples of the data base. } \author{ Matthieu Combaud, Maxime Delprat From ad434a0a39f8bcb4371644cc5fd37ab709c4dd2e Mon Sep 17 00:00:00 2001 From: Maxime Delprat <98315113+maximedelprat@users.noreply.github.com> Date: Fri, 10 Jun 2022 14:36:56 +0200 Subject: [PATCH 132/176] Update _pkgdown.yml Deletion of validation sub functions --- _pkgdown.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index ed474a0..832f986 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -145,8 +145,3 @@ reference: - ".scaleMaps" - ".getCutoff" - ".unzip_ALL" - - "train_RF_habitat" - - "do_habitat_validation" - - "do_PFG_composition_validation" - - "get_observed_distribution" - - "plot_predicted_habitat" From e666e70bfc31ca85f8df5dd6d2de1318cbaafcf0 Mon Sep 17 00:00:00 2001 From: Maxime Delprat Date: Fri, 10 Jun 2022 14:39:01 +0200 Subject: [PATCH 133/176] corrections in documentations and deletion of exportation of validation sub functions --- NAMESPACE | 21 ---- R/POST_FATE.validation.R | 19 +++- R/UTILS.do_PFG_composition_validation.R | 57 +--------- R/UTILS.do_habitat_validation.R | 57 +--------- R/UTILS.get_observed_distribution.R | 128 ++++++----------------- R/UTILS.plot_predicted_habitat.R | 48 +-------- R/UTILS.train_RF_habitat.R | 61 +---------- docs/reference/POST_FATE.validation.html | 2 +- docs/reference/index.html | 22 ---- man/POST_FATE.validation.Rd | 2 +- man/do_PFG_composition_validation.Rd | 60 ----------- man/do_habitat_validation.Rd | 65 ------------ man/get_observed_distribution.Rd | 63 ----------- man/plot_predicted_habitat.Rd | 50 --------- man/train_RF_habitat.Rd | 62 ----------- 15 files changed, 58 insertions(+), 659 deletions(-) delete mode 100644 man/do_PFG_composition_validation.Rd delete mode 100644 man/do_habitat_validation.Rd delete mode 100644 man/get_observed_distribution.Rd delete mode 100644 man/plot_predicted_habitat.Rd delete mode 100644 man/train_RF_habitat.Rd diff --git a/NAMESPACE b/NAMESPACE index 13f1a18..fa781f1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -75,7 +75,6 @@ importFrom(adehabitatHR,kernelUD) importFrom(adehabitatMA,ascgen) importFrom(ape,as.phylo) importFrom(ape,plot.phylo) -importFrom(caret,confusionMatrix) importFrom(cluster,silhouette) importFrom(colorspace,heat_hcl) importFrom(colorspace,sequential_hcl) @@ -84,13 +83,7 @@ importFrom(cowplot,ggdraw) importFrom(data.table,fread) importFrom(data.table,fwrite) importFrom(data.table,rbindlist) -importFrom(data.table,setDT) importFrom(doParallel,registerDoParallel) -importFrom(dplyr,"%>%") -importFrom(dplyr,all_of) -importFrom(dplyr,filter) -importFrom(dplyr,group_by) -importFrom(dplyr,mutate) importFrom(dplyr,rename) importFrom(dplyr,select) importFrom(forcats,fct_expand) @@ -128,9 +121,6 @@ importFrom(ggplot2,geom_smooth) importFrom(ggplot2,geom_vline) importFrom(ggplot2,ggplot) importFrom(ggplot2,ggsave) -importFrom(ggplot2,ggtitle) -importFrom(ggplot2,guide_legend) -importFrom(ggplot2,guides) importFrom(ggplot2,labs) importFrom(ggplot2,scale_alpha) importFrom(ggplot2,scale_color_continuous) @@ -167,18 +157,13 @@ importFrom(huge,huge.npn) importFrom(methods,as) importFrom(parallel,mclapply) importFrom(phyloclim,niche.overlap) -importFrom(prettyR,Mode) -importFrom(randomForest,randomForest) -importFrom(randomForest,tuneRF) importFrom(raster,as.data.frame) importFrom(raster,as.matrix) importFrom(raster,cellFromXY) importFrom(raster,cellStats) importFrom(raster,compareCRS) -importFrom(raster,compareRaster) importFrom(raster,coordinates) importFrom(raster,crop) -importFrom(raster,crs) importFrom(raster,extension) importFrom(raster,extent) importFrom(raster,extract) @@ -188,26 +173,20 @@ importFrom(raster,mask) importFrom(raster,ncell) importFrom(raster,nlayers) importFrom(raster,origin) -importFrom(raster,predict) importFrom(raster,projectRaster) importFrom(raster,projection) importFrom(raster,raster) importFrom(raster,rasterToPoints) -importFrom(raster,ratify) importFrom(raster,res) importFrom(raster,stack) importFrom(raster,writeRaster) importFrom(raster,xyFromCell) importFrom(readr,write_rds) -importFrom(reshape2,dcast) importFrom(reshape2,melt) -importFrom(sf,st_crop) -importFrom(sf,st_transform) importFrom(shiny,runApp) importFrom(sp,SpatialPoints) importFrom(stats,aggregate) importFrom(stats,as.dist) -importFrom(stats,complete.cases) importFrom(stats,cophenetic) importFrom(stats,cor) importFrom(stats,cutree) diff --git a/R/POST_FATE.validation.R b/R/POST_FATE.validation.R index 81d2ede..2988bce 100644 --- a/R/POST_FATE.validation.R +++ b/R/POST_FATE.validation.R @@ -35,7 +35,7 @@ ##' \cr (see \href{POST_FATE.validation#details}{\code{Details}}) ##' \cr (habitat & PFG composition validation). ##' @param hab.obs a \code{raster} map of the studied map in the simulation, with same projection -##' & resolution than simulation mask, used to predict habitat & PFG composition. +##' & resolution than simulation mask, used to predict habitat & PFG distribution ##' @param studied.habitat a \code{data.frame} with 2 columns : \cr ##' \code{ID} ,\code{habitat} ##' \cr (see \href{POST_FATE.validation#details}{\code{Details}}) @@ -504,8 +504,23 @@ POST_FATE.validation = function(name.simulation cat("\n Get observed distribution...\n") + if(perStrata == TRUE & ncol(releves.PFG) == 7) + { + hab.obs.compo = NULL + }else if(perStrata == TRUE & ncol(releves.PFG) == 6) + { + hab.obs.compo = habitat.FATE.map + }else if(perStrata == FALSE & ncol(releves.PFG) == 6) + { + hab.obs.compo = NULL + }else if(perStrata == FALSE & ncol(releves.PFG) == 5) + { + hab.obs.compo = habitat.FATE.map + }else { + stop("releves.PFG must be a data frame with at least 5 columns : 'site', 'x', 'y', 'abund', 'PFG' and optionally, 'strata', 'code.habitat'") + } obs.distri = get_observed_distribution(releves.PFG = releves.PFG - , hab.obs = hab.obs + , hab.obs.compo = hab.obs.compo , studied.habitat = studied.habitat , PFG.considered_PFG.compo = PFG.considered_PFG.compo , strata.considered_PFG.compo = strata.considered_PFG.compo diff --git a/R/UTILS.do_PFG_composition_validation.R b/R/UTILS.do_PFG_composition_validation.R index 1ec1981..e1a3c38 100644 --- a/R/UTILS.do_PFG_composition_validation.R +++ b/R/UTILS.do_PFG_composition_validation.R @@ -1,58 +1,5 @@ -### HEADER ##################################################################### -##' -##' @title Compute distance between observed and simulated distribution. -##' -##' @name do_PFG_composition_validation -##' -##' @author Matthieu Combaud, Maxime Delprat -##' -##' @description This script is designed to compare the difference between the -##' PFG distribution in observed and simulated data. For a set of PFG, strata and -##' habitats chosen, the function computes distance between observed and simulated -##' distribution for a precise \code{FATE} simulation. -##' -##' @param sim Name of the single simulation to validate. -##' @param PFG.considered_PFG.compo A character vector of the list of PFG considered -##' in the validation. -##' @param strata.considered_PFG.compo A character vector of the list of precise -##' strata considered in the validation. -##' @param habitat.considered_PFG.compo A character vector of the list of habitat(s) -##' considered in the validation. -##' @param observed.distribution A \code{data.frame} provides by \code{get_observed_distribution} with 5 columns : -##' \cr \code{PFG}, \code{habitat}, \code{strata}, \code{rank}, \code{observed.quantile} -##' \cr (see \code{\link{get_observed_distribution}}) -##' @param simu_PFG A \code{data.frame} provides by \code{POST_FATE.temporalEvolution} with simulated abundance for each PFG and strata -##' (if option selected) and pixel ID (see \code{\link{POST_FATE.temporalEvolution}}). -##' @param habitat.whole.area.df A \code{data.frame} provides by \code{POST_FATE.validation} with 3 columns : -##' \cr \code{pixel} which contains the ID of each pixel in the study area. -##' \cr \code{code.habitat} which contains the ID of the habitat in each pixel. -##' \cr \code{for.validation} for each pixel, \code{0} if does not need validation, \code{1} if needs validation. -##' -##' @details -##' -##' Firstly, this code merges \code{habitat.whole.area.df} data frame with the simulated PFG abundance -##' \code{simu_PFG} data frame (with or without strata definition). -##' After filtration of the required PFG, strata and habitat(s), the function transforms -##' the data into relative metrics and, then, compute distribution per PFG, strata -##' and habitat (if necessary). Finally, the code computes proximity between observed -##' and simulated data, per PFG, strata and habitat. -##' -##' @return -##' -##' 1 file is created in \cr -##' \describe{ -##' \item{\file{VALIDATION/PFG_COMPOSITION} : \cr -##' A .csv file which contain the proximity between observed and simulated data computed -##' for each PFG/strata/habitat. -##' -##' @importFrom dplyr rename filter group_by mutate %>% select -##' @importFrom raster raster projectRaster res crs crop extent origin compareRaster -##' getValues ncell compareCRS levels -##' @importFrom stats aggregate -##' @importFrom data.table setDT -##' @importFrom tidyselect all_of -##' -### END OF HEADER ############################################################## + +################################################################# do_PFG_composition_validation <- function(sim, PFG.considered_PFG.compo , strata.considered_PFG.compo, habitat.considered_PFG.compo diff --git a/R/UTILS.do_habitat_validation.R b/R/UTILS.do_habitat_validation.R index 8e726f0..7af6a6b 100644 --- a/R/UTILS.do_habitat_validation.R +++ b/R/UTILS.do_habitat_validation.R @@ -1,58 +1,5 @@ -### HEADER ##################################################################### -##' -##' @title Compare observed and simulated habitat of a \code{FATE} simulation -##' to a chosen year. -##' -##' @name do_habitat_validation -##' -##' @author Matthieu Combaud & Maxime Delprat -##' -##' @description To compare observations and simulations, this function computes -##' confusion matrix between observation and prediction and then compute the TSS -##' for each habitats. -##' -##' @param output.path a \code{string} containing the access path to the for the folder where output files -##' will be created. -##' @param RF.model random forest model trained on observed abundance data (output of \code{train_RF_habitat} -##' function) -##' @param predict.all.map \code{logical}. \cr -##' If TRUE, the script will predict habitat for the whole map. -##' @param sim a \code{string} containing the name of the single simulation to validate. -##' @param simu_PFG a \code{data.frame} provides by \code{POST_FATE.temporalEvolution} with simulated abundance for each PFG and strata -##' (if option selected) and pixel ID (see \code{\link{POST_FATE.temporalEvolution}}). -##' @param habitat.whole.area.df a \code{data.frame} built in \code{POST_FATE.validation} with 3 columns : -##' \cr \code{pixel} which contains the ID of each pixel in the study area. -##' \cr \code{code.habitat} which contains the ID of the habitat in each pixel. -##' \cr \code{for.validation} for each pixel, \code{0} if does not need validation, \code{1} if needs validation. -##' @param list.strata if abundance file is defined by strata : a character vector containing \code{FATE} -##' strata definition and correspondence with observed strata definition. \cr -##' If abundance file is defined for all strata : a character vector with value "all". -##' @param perStrata \code{logical}. \cr -##' If \code{TRUE}, PFG abundance is defined by strata. -##' If \code{FALSE}, PFG abundance defined for all strata. -##' -##' @details -##' -##' For a given simulation \code{sim}, this script takes the evolution abundance for each PFG -##' and all strata (or for each PFG & each strata if option selected) data frame and predicts -##' the habitat for the whole map (if option selected) thanks to the RF model. -##' Finally, the function computes habitat performance based on TSS for each habitat. -##' -##' @return -##' -##' Habitat performance file. \cr -##' If option selected, the function also returns an habitat prediction file with -##' observed and simulated habitat for each pixel of the whole map. -##' -##' @importFrom dplyr group_by %>% mutate rename select -##' @importFrom raster predict -##' @importFrom reshape2 dcast -##' @importFrom caret confusionMatrix -##' @importFrom utils write.csv -##' @importFrom tidyselect all_of -##' @importFrom stringr str_split -##' -### END OF HEADER ############################################################## + +################################################################# do_habitat_validation <- function(output.path, RF.model, predict.all.map, sim, simu_PFG, habitat.whole.area.df, list.strata, perStrata) { diff --git a/R/UTILS.get_observed_distribution.R b/R/UTILS.get_observed_distribution.R index 93c2adb..6540906 100644 --- a/R/UTILS.get_observed_distribution.R +++ b/R/UTILS.get_observed_distribution.R @@ -1,72 +1,16 @@ -### HEADER ##################################################################### -##' -##' @title Compute distribution of relative abundance over observed releves. -##' -##' @name get_observed_distribution -##' -##' @author Matthieu Combaud, Maxime Delprat -##' -##' @description This script is designed to compute distribution, per PFG/strata/habitat, -##' of relative abundance, from observed data. -##' -##' @param name.simulation Simulation folder name. -##' @param releves.PFG A \code{data.frame} with at least 5 columns : \cr -##' \code{site}, \code{x}, \code{y}, which contain respectively ID, x coordinate & y coordinate of each site of the study area. \cr -##' \code{abund} & \code{PFG} which contain respectively abundance (can be absolute abundance, Braun-Blanquet abundance or presence-absence) -##' & name of PFG. -##' \cr (\emph{and optionally, \code{strata}}) which contains the number of strata at each the abundance is noted. -##' (habitat & PFG composition validation). -##' @param hab.obs A raster map of the extended studied map in the simulation, with same projection -##' & resolution than simulation mask. -##' @param studied.habitat A \code{data.frame} with 2 columns : -##' \cr \code{ID} which contains the habitat ID, & \code{habitat} which contains the habitat names which will be taken into account -##' for the validation (habitat & PFG composition validation). -##' @param PFG.considered_PFG.compo A character vector which contains the list of PFG considered -##' in the validation. -##' @param strata.considered_PFG.compo A character vector which contains the list of precise -##' strata considered in the validation. -##' @param habitat.considered_PFG.compo A character vector which contains the list of habitat(s) -##' considered in the validation. -##' @param perStrata \code{Logical}. -##' \cr All strata together (FALSE) or per strata (TRUE). -##' -##' @details -##' -##' The function takes the \code{releves.PFG} file and aggregate coverage per PFG. -##' Then, the code gets habitat information from the \code{hab.obs} map & the \code{studied.habitat} -##' data frame, keep only interesting habitat(s), strata and PFG, and transforms -##' the data into relative metrics. Finally, the script computes distribution per PFG -##' and if required per strata/habitat (else all strata/habitat will be considered together). -##' -##' @return -##' -##' 2 files are created in -##' \describe{ -##' \item{\file{VALIDATION/PFG_COMPOSITION} : \cr -##' 1 .csv file which contain the observed relevés transformed into relative metrics. \cr -##' 1 .csv file which contain the final output with the distribution per PFG, strata and habitat. -##' -##' @importFrom dplyr select filter group_by mutate %>% rename -##' @importFrom raster compareCRS res crs levels -##' @importFrom stats aggregate -##' @importFrom sf st_transform st_crop -##' @importFrom utils write.csv -##' @importFrom data.table setDT -##' -### END OF HEADER ############################################################## + +################################################################# get_observed_distribution <- function(releves.PFG - , hab.obs - , studied.habitat = NULL + , hab.obs.compo = NULL + , studied.habitat , PFG.considered_PFG.compo , strata.considered_PFG.compo , habitat.considered_PFG.compo - , perStrata + , perStrata = FALSE , output.path){ - # composition.mask = NULL - #1. Aggregate coverage per PFG ######################################### @@ -81,13 +25,21 @@ get_observed_distribution <- function(releves.PFG } else if (is.numeric(releves.PFG$abund)) # absolute abundance { releves.PFG$coverage = releves.PFG$abund + }else + { + stop("Abund data in releves.PFG must be Braun-Blanquet abundance, presences absence or absolute abundance values.") } - if(perStrata == T){ - mat.PFG.agg <- aggregate(coverage ~ site + PFG + strata, data = releves.PFG, FUN = "sum") - }else if(perStrata == F){ - mat.PFG.agg <- aggregate(coverage ~ site + PFG, data = releves.PFG, FUN = "sum") - mat.PFG.agg$strata <- "A" #"A" is for "all". + if (perStrata == TRUE & !is.null(hab.obs.compo)) { + mat.PFG.agg = aggregate(coverage ~ site + PFG + strata, data = releves.PFG, FUN = "sum") + } else if (perStrata == FALSE & !is.null(hab.obs.compo)) { + mat.PFG.agg = aggregate(coverage ~ site + PFG, data = releves.PFG, FUN = "sum") + mat.PFG.agg$strata = "A" + } else if (perStrata == TRUE & is.null(hab.obs.compo)) { + mat.PFG.agg = aggregate(coverage ~ site + PFG + strata + code.habitat, data = releves.PFG, FUN = "sum") + } else if (perStrata == FALSE & is.null(hab.obs.compo)) { + mat.PFG.agg = aggregate(coverage ~ site + PFG + code.habitat, data = releves.PFG, FUN = "sum") + mat.PFG.agg$strata = "A" } #2. Get habitat information @@ -95,40 +47,24 @@ get_observed_distribution <- function(releves.PFG #get habitat code and name coord = releves.PFG %>% group_by(site) %>% filter(!duplicated(site)) - mat.PFG.agg = merge(mat.PFG.agg, coord[,c("site","x","y")], by = "site") - mat.PFG.agg$code.habitat = extract(x = hab.obs, y = mat.PFG.agg[,c("x", "y")]) - mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] - if (nrow(mat.PFG.agg) == 0) { - stop("Code habitat vector is empty. Please verify values of your hab.obs map") + if(is.null(hab.obs.compo)) + { + mat.PFG.agg = merge(mat.PFG.agg, coord[,c("site","x","y","code.habitat")], by = "site") } - - #correspondence habitat code/habitat name - if (!is.null(studied.habitat) & nrow(studied.habitat) > 0 & ncol(studied.habitat) == 2) - { # cas où pas de levels dans la carte d'habitat et utilisation d'un vecteur d'habitat - table.habitat.releve = studied.habitat - mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA - mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - } else if (names(levels(hab.obs)[[1]]) == c("ID", "habitat", "colour") & nrow(levels(hab.obs)[[1]]) > 0 & is.null(studied.habitat)) - { # cas où on utilise les levels définis dans la carte - table.habitat.releve = levels(hab.obs)[[1]] - mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") - mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$habitat %in% studied.habitat$habitat), ] - } else + if(!is.null(hab.obs.compo)) { - stop("Habitat definition in hab.obs map is not correct") + mat.PFG.agg = merge(mat.PFG.agg, coord[,c("site","x","y")], by = "site") + mat.PFG.agg$code.habitat = extract(x = hab.obs.compo, y = mat.PFG.agg[,c("x", "y")]) + mat.PFG.agg = mat.PFG.agg[which(!is.na(mat.PFG.agg$code.habitat)), ] + if (nrow(mat.PFG.agg) == 0) { + stop("Code habitat vector is empty. Please verify values of your hab.obs map") + } } - # #(optional) keep only releves data in a specific area - # if(!is.null(composition.mask)){ - # - # if(compareCRS(mat.PFG.agg,composition.mask)==F){ #as this stage it is not a problem to transform crs(mat.PFG.agg) since we have no more merge to do (we have already extracted habitat info from the map) - # mat.PFG.agg<-st_transform(x=mat.PFG.agg,crs=crs(composition.mask)) - # } - # - # mat.PFG.agg<-st_crop(x=mat.PFG.agg,y=composition.mask) - # print("'releve' map has been cropped to match 'external.training.mask'.") - # } - + #correspondence habitat code/habitat name + table.habitat.releve = studied.habitat + mat.PFG.agg = mat.PFG.agg[which(mat.PFG.agg$code.habitat %in% studied.habitat$ID), ] # filter non interesting habitat + NA + mat.PFG.agg = merge(mat.PFG.agg, table.habitat.releve[, c("ID", "habitat")], by.x = "code.habitat", by.y = "ID") # 3. Keep only releve on interesting habitat, strata and PFG ##################################################################" diff --git a/R/UTILS.plot_predicted_habitat.R b/R/UTILS.plot_predicted_habitat.R index e8c1a73..972eb23 100644 --- a/R/UTILS.plot_predicted_habitat.R +++ b/R/UTILS.plot_predicted_habitat.R @@ -1,51 +1,5 @@ -### HEADER ##################################################################### -##' -##' @title Create a raster map of habitat prediction for a set of \code{FATE} -##' simulations at a specific simulation year. -##' -##' @name plot_predicted_habitat -##' -##' @author Matthieu Combaud, Maxime Delprat -##' -##' @description This script is designed to create a raster map of habitat prediction -##' based on an habitat prediction file. For each pixel, the habitat failure or success value -##' is associated to a color and then, the map is built. -##' -##' @param predicted.habitat a \CODE{data.frame} provides by \code{do_habitat.validation} with at least 3 columns : -##' \cr \code{pixel}, \code{true.habitat} & 1 column per simulation in \code{sim.version} which contains the predicted habitat for this simulation. -##' \cr (see \code{\link{do_habitat_valdiation}}). -##' @param col.df A \code{data.frame} with 3 columns : -##' \cr \code{habitat} which contains all the habitats taken into account in the simulation. -##' \cr \code{failure} which contains colors to designate the failure of the prediction of each habitats -##' \cr \code{success} which contains colors to designate the success of the prediction of each habitats -##' @param simulation.map A raster map of the whole studied area. -##' @param output.path Access path to the for the folder where output files -##' will be created. -##' @param sim.version A character vector with the name(s) of the simulation(s) to validate. -##' -##' @details -##' -##' The function determines true/false prediction ('failure' if false, 'success' if true) -##' and prepare a data frame containing colors and habitats codes. Then, the script merge -##' the prediction data frame with the color and code habitat data frame. Finally, -##' the function draw a raster map and a plot of prediction habitat over it, thanks -##' to the data prepared before. -##' -##' @return -##' -##' a synthetic.prediction.png file which contain the final prediction map. -##' -##' @importFrom dplyr all_of rename select -##' @importFrom utils write.csv -##' @importFrom raster raster crs extent res ratify writeRaster levels -##' @importFrom stats complete.cases -##' @importFrom ggplot2 ggplot geom_raster coord_equal scale_fill_manual -##' ggtitle guides theme ggsave guide_legend -##' @importFrom reshape2 melt -##' @importFrom prettyR Mode -##' -### END OF HEADER ############################################################## +############################################################################# plot_predicted_habitat = function(predicted.habitat , col.df diff --git a/R/UTILS.train_RF_habitat.R b/R/UTILS.train_RF_habitat.R index 8ecf8ca..a8dacba 100644 --- a/R/UTILS.train_RF_habitat.R +++ b/R/UTILS.train_RF_habitat.R @@ -1,62 +1,5 @@ -### HEADER ##################################################################### -##' -##' @title Create a random forest algorithm trained on observed vegetation data -##' -##' @name train_RF_habitat -##' -##' @author Matthieu Combaud, Maxime Delprat -##' -##' @description This script is designed to produce a \code{Random Forest} model -##' trained on observed PFG abundances & habitats. -##' -##' @param releves.PFG a \code{data.frame} with at least 5 columns : \cr -##' \code{site}, \code{x}, \code{y}, \code{abund}, \code{PFG} -##' \cr (\emph{and optionally, \code{strata}, \code{code.habitat}}) -##' \cr (see \href{train_RF_habitat#details}{\code{Details}}) -##' @param hab.obs.RF (optional) default \code{NULL]. -##' \cr If habitat ID is not provided in \code{releves.PFG}, a \code{raster} map of the observed habitat in the studied area. -##' @param external.training.mask (optional) default \code{NULL}. -##' \cr a \code{raster} map for keeping releves data only in a specific area. -##' @param studied.habitat a \code{data.frame} with 2 columns : \cr -##' \code{ID} ,\code{habitat} -##' \cr (see \href{train_RF_habitat#details}{\code{Details}}) -##' @param RF.param a \code{list} of 2 parameters to fit a random forest model : \cr -##' \code{share.training} defines the proportion of the data base used for training the model. \cr -##' \code{ntree} is the number of trees built by the algorithm, it allows to reduce the prediction error. -##' @param output.path access path to the folder where output files will be created. -##' @param perStrata (\code{logical}) default \code{FALSE}. -##' \cr If \code{TRUE}, the PFG abundance must be defined -##' by strata in each site. If \code{FALSE}, PFG abundance must be defined for all strata. -##' -##' @details -##' -##' The \code{Random Forest} algorithm is trained on \strong{releves.PFG} data. \cr -##' Information about \strong{PFG abundances} at each \strong{sites} with \strong{xy} coordinates are necessary. \cr -##' Eventually, \strong{habitat ID} information can be provided. If not, observed habitat is provided by the -##' \strong{hab.obs.RF} map. The final set of habitats taken into account in the validation is provided by -##' \strong{studied.habitat} table containing all the \strong{habitats}, with their corresponding \strong{ID}. -##' Finally, a performance analysis is provided by computing TSS between training and testing samples of the data base. -##' -##' @return -##' into the \code{name.simulation/VALIDATION/HABITAT/ directory :} \cr -##' 1 .csv file containing the prepared observed data (relative abundances & habitat information). \cr -##' 1 .rds file containing the RF model. \cr -##' 4 .csv files containing the performance analysis (2 TSS per habitat files, and 2 aggregate TSS files) for the training & -##' testing samples. -##' -##' @importFrom dplyr filter %>% group_by select -##' @importFrom stats aggregate -##' @importFrom reshape2 dcast -##' @importFrom data.table setDT -##' @importFrom raster extract compareCRS levels crs -##' @importFrom sf st_transform st_crop -##' @importFrom randomForest randomForest tuneRF -##' @importFrom caret confusionMatrix -##' @importFrom readr write_rds -##' @importFrom utils read.csv write.csv -##' @importFrom stringr str_split -##' -### END OF HEADER ############################################################## + +################################################################# train_RF_habitat = function(releves.PFG diff --git a/docs/reference/POST_FATE.validation.html b/docs/reference/POST_FATE.validation.html index 195bc52..61a45bf 100644 --- a/docs/reference/POST_FATE.validation.html +++ b/docs/reference/POST_FATE.validation.html @@ -220,7 +220,7 @@

Arguments


(habitat & PFG composition validation).

hab.obs

a raster map of the studied map in the simulation, with same projection -& resolution than simulation mask, used to predict habitat & PFG composition.

+& resolution than simulation mask, used to predict habitat & PFG distribution

studied.habitat

a data.frame with 2 columns :
ID ,habitat
(see Details) diff --git a/docs/reference/index.html b/docs/reference/index.html index 2a1a3ad..bb363d8 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -371,28 +371,6 @@

Tool box

train_RF_habitat()

- -

Create a random forest algorithm trained on observed vegetation data

- -

do_habitat_validation()

- -

Compare observed and simulated habitat of a FATE simulation -to a chosen year.

- -

do_PFG_composition_validation()

- -

Compute distance between observed and simulated distribution.

- -

get_observed_distribution()

- -

Compute distribution of relative abundance over observed releves.

- -

plot_predicted_habitat()

- -

Create a raster map of habitat prediction for a set of FATE -simulations at a specific simulation year.

- - - + + diff --git a/docs/reference/POST_FATE.binaryMaps.html b/docs/reference/POST_FATE.binaryMaps.html index b2a49e7..623ad05 100644 --- a/docs/reference/POST_FATE.binaryMaps.html +++ b/docs/reference/POST_FATE.binaryMaps.html @@ -20,7 +20,7 @@ RFate - 1.0.3 + 1.0.4 @@ -261,7 +261,7 @@

Author

-

Site built with pkgdown 2.0.2.

+

Site built with pkgdown 2.0.1.

diff --git a/docs/reference/POST_FATE.graphic_evolutionCoverage.html b/docs/reference/POST_FATE.graphic_evolutionCoverage.html index 9737718..87cef9b 100644 --- a/docs/reference/POST_FATE.graphic_evolutionCoverage.html +++ b/docs/reference/POST_FATE.graphic_evolutionCoverage.html @@ -1,73 +1,18 @@ - - - - - - - -Create a graphical representation of the evolution of PFG coverage -and abundance through time for a FATE simulation — POST_FATE.graphic_evolutionCoverage • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create a graphical representation of the evolution of PFG coverage +and abundance through time for a FATE simulation — POST_FATE.graphic_evolutionCoverage • RFate - - - - - - - - - - - + + - - -
-
- -
- -
+
@@ -215,136 +149,147 @@

Create a graphical representation of the evolution of PFG coverage over the whole area.

-
POST_FATE.graphic_evolutionCoverage(
-  name.simulation,
-  file.simulParam = NULL,
-  opt.fixedScale = TRUE,
-  opt.doPlot = TRUE
-)
- -

Arguments

- - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+
POST_FATE.graphic_evolutionCoverage(
+  name.simulation,
+  file.simulParam = NULL,
+  opt.fixedScale = TRUE,
+  opt.doPlot = TRUE
+)
+
+ +
+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

opt.fixedScale

(optional) default TRUE.
If +the PARAM_SIMUL folder of the FATE simulation

+
opt.fixedScale
+

(optional) default TRUE.
If FALSE, the ordinate scale will be adapted for each PFG for the -graphical representation of the evolution of abundances through time

opt.doPlot

(optional) default TRUE.
If TRUE, +graphical representation of the evolution of abundances through time

+
opt.doPlot
+

(optional) default TRUE.
If TRUE, plot(s) will be processed, otherwise only the calculation and reorganization -of outputs will occur, be saved and returned

+of outputs will occur, be saved and returned

+
+
+

Value

+

A list containing two data.frame objects with the +following columns, and two ggplot2 objects :

tab.spaceOccupancy
+

PFG
+

concerned plant functional group (for abundance)

-

Value

+
HAB
+

concerned habitat

-

A list containing two data.frame objects with the -following columns, and two ggplot2 objects :

-
-
tab.spaceOccupancy

-
PFG

concerned plant functional group (for abundance)

-
HAB

concerned habitat

-
year

concerned simulation year

-
spaceOccupancy

number of occupied pixels divided by the - total number of pixels within the studied area

-
tab.totalAbundance

-
PFG

concerned plant functional group (for abundance)

-
HAB

concerned habitat

-
year

concerned simulation year

-
totalAbundance

total abundance over all the pixels - within the studied area

-
plot.spaceOccupancy

ggplot2 object, representing the +

year
+

concerned simulation year

+ +
spaceOccupancy
+

number of occupied pixels divided by the + total number of pixels within the studied area

+ + +

+ +
tab.totalAbundance
+

PFG
+

concerned plant functional group (for abundance)

+ +
HAB
+

concerned habitat

+ +
year
+

concerned simulation year

+ +
totalAbundance
+

total abundance over all the pixels + within the studied area

+ + +

+ +
plot.spaceOccupancy
+

ggplot2 object, representing the evolution of each PFG space occupancy

-
plot.totalAbundance

ggplot2 object, representing the - evolution of each PFG total abundance

-
+
plot.totalAbundance
+

ggplot2 object, representing the + evolution of each PFG total abundance

-

Two POST_FATE_TABLE_ZONE_evolution_[...].csv files are created :

-
spaceOccupancy

always, containing tab.spaceOccupancy

-
totalAbundance

always, containing tab.totalAbundance

+

Two POST_FATE_TABLE_ZONE_evolution_[...].csv files are created :

spaceOccupancy
+

always, containing tab.spaceOccupancy

-
+
totalAbundance
+

always, containing tab.totalAbundance

-

One POST_FATE_GRAPHIC_A_evolution_coverage_[...].pdf file is created -containing two types of graphics :

-
spaceOccupancy

to visualize for each PFG the evolution of its +

One POST_FATE_GRAPHIC_A_evolution_coverage_[...].pdf file is created +containing two types of graphics :

spaceOccupancy
+

to visualize for each PFG the evolution of its occupation of the studied area through simulation time

-
totalAbundance

to visualize for each PFG the evolution of its - abundance within the whole studied area through simulation time

-
+
totalAbundance
+

to visualize for each PFG the evolution of its + abundance within the whole studied area through simulation time

-

Details

+
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, two preanalytical graphics :

-
    -
  • the evolution of space occupancy of each plant functional - group through simulation time,
    with space occupancy +

    • the evolution of space occupancy of each plant functional + group through simulation time,
      with space occupancy representing the percentage of pixels within the mask of studied area where the PFG is present

    • the evolution of total abundance of each plant functional - group through simulation time,
      with total abundance being the + group through simulation time,
      with total abundance being the sum over the whole studied area of the PFG abundances (FATE arbitrary unit)

    • -
    - -

    If the information has been provided (see -POST_FATE.temporalEvolution), the graphics will be also done -per habitat.

    -

    It requires that the POST_FATE.temporalEvolution +

If the information has been provided (see +POST_FATE.temporalEvolution), the graphics will be also done +per habitat.

+

It requires that the POST_FATE.temporalEvolution function has been run and that the file POST_FATE_TABLE_PIXEL_evolution_abundance.csv exists.

-

See also

- - -

Author

- +
+ +
+

Author

Maya Guéguen

+
+
- - - + + diff --git a/docs/reference/POST_FATE.graphic_evolutionPixels.html b/docs/reference/POST_FATE.graphic_evolutionPixels.html index 9139f82..bb308ad 100644 --- a/docs/reference/POST_FATE.graphic_evolutionPixels.html +++ b/docs/reference/POST_FATE.graphic_evolutionPixels.html @@ -1,72 +1,17 @@ - - - - - - - -Create a graphical representation of the evolution of PFG abundance -through time for 5 (or more) pixels of a FATE simulation — POST_FATE.graphic_evolutionPixels • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create a graphical representation of the evolution of PFG abundance +through time for 5 (or more) pixels of a FATE simulation — POST_FATE.graphic_evolutionPixels • RFate - - - - - - - - - - - + + - - -
-
- -
- -
+
@@ -213,131 +147,129 @@

Create a graphical representation of the evolution of PFG abundance area.

-
POST_FATE.graphic_evolutionPixels(
-  name.simulation,
-  file.simulParam = NULL,
-  opt.cells_ID = NULL,
-  opt.doPlot = TRUE
-)
+
+
POST_FATE.graphic_evolutionPixels(
+  name.simulation,
+  file.simulParam = NULL,
+  opt.cells_ID = NULL,
+  opt.doPlot = TRUE
+)
+
-

Arguments

- - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

opt.cells_ID

(optional) default NULL.
The cells ID -of the studied area for which PFG abundances will be extracted

opt.doPlot

(optional) default TRUE.
If TRUE, +the PARAM_SIMUL folder of the FATE simulation

+
opt.cells_ID
+

(optional) default NULL.
The cells ID +of the studied area for which PFG abundances will be extracted

+
opt.doPlot
+

(optional) default TRUE.
If TRUE, plot(s) will be processed, otherwise only the calculation and reorganization -of outputs will occur, be saved and returned

- -

Value

- +of outputs will occur, be saved and returned

+
+
+

Value

A list containing one data.frame object with the -following columns, and one ggplot2 object :

-
-
tab

-
TYPE

concerned information (either 'light', +following columns, and one ggplot2 object :

tab
+

TYPE
+

concerned information (either 'light', 'abundance' or 'soil')

-
GROUP

concerned entity (either + +

GROUP
+

concerned entity (either 'STRATUM_[...]', PFG name or 'soil')

-
ID.pixel

number of the concerned pixel

-
HAB

habitat of the concerned pixel

-
YEAR

concerned simulation year

-
value

concerned value extracted from .csv files - produced by POST_FATE.temporalEvolution

-
plot

ggplot2 object, representing the evolution of each PFG - abundance, and light and soil resources if those modules were - activated

-
+
ID.pixel
+

number of the concerned pixel

+
HAB
+

habitat of the concerned pixel

-

One POST_FATE_TABLE_PIXEL_evolution_pixels_[...].csv file is created :

-
pixels ids

always, containing the data.frame detailed - above

+
YEAR
+

concerned simulation year

-
+
value
+

concerned value extracted from .csv files + produced by POST_FATE.temporalEvolution

+ +

+ +
plot
+

ggplot2 object, representing the evolution of each PFG + abundance, and light and soil resources if those modules were + activated

-

One POST_FATE_[...].pdf file is created :

-
GRAPHIC_A
pixels

to visualize for each PFG the evolution - of its abundance within each selected pixel through simulation time

-
+

One POST_FATE_TABLE_PIXEL_evolution_pixels_[...].csv file is created :

pixels ids
+

always, containing the data.frame detailed + above

-

Details

+

One POST_FATE_[...].pdf file is created :

GRAPHIC_A
pixels
+

to visualize for each PFG the evolution + of its abundance within each selected pixel through simulation time

+ + +
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, one preanalytical graphic :

-

It requires that the POST_FATE.temporalEvolution function has been run and that the file POST_FATE_TABLE_PIXEL_evolution_abundance.csv exists (as well as the POST_FATE_TABLE_PIXEL_evolution_light.csv and POST_FATE_TABLE_PIXEL_evolution_soil.csv files if those modules were activated).

-

See also

- - -

Author

- +
+ +
+

Author

Maya Guéguen

+
+
- - - + + diff --git a/docs/reference/POST_FATE.graphic_evolutionStability.html b/docs/reference/POST_FATE.graphic_evolutionStability.html index 9884ae7..a93a67c 100644 --- a/docs/reference/POST_FATE.graphic_evolutionStability.html +++ b/docs/reference/POST_FATE.graphic_evolutionStability.html @@ -1,71 +1,16 @@ - - - - - - - -Create a graphical representation of the evolution of habitat -composition through time for a FATE simulation — POST_FATE.graphic_evolutionStability • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create a graphical representation of the evolution of habitat +composition through time for a FATE simulation — POST_FATE.graphic_evolutionStability • RFate - - - - - - - - - - - + + - - -
-
- -
- -
+
@@ -211,103 +145,124 @@

Create a graphical representation of the evolution of habitat abundance and evenness of each habitat.

-
POST_FATE.graphic_evolutionStability(
-  name.simulation,
-  file.simulParam = NULL,
-  movingWindow_size = 3,
-  movingWindow_step = 1,
-  opt.doPlot = TRUE
-)
- -

Arguments

- - - - - - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+
POST_FATE.graphic_evolutionStability(
+  name.simulation,
+  file.simulParam = NULL,
+  movingWindow_size = 3,
+  movingWindow_step = 1,
+  opt.doPlot = TRUE
+)
+
+ +
+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

movingWindow_size

default 3.
An integer +the PARAM_SIMUL folder of the FATE simulation

+
movingWindow_size
+

default 3.
An integer corresponding to the size (in years) of the moving window that will -be used to calculate metrics of habitat stability

movingWindow_step

default 1.
An integer +be used to calculate metrics of habitat stability

+
movingWindow_step
+

default 1.
An integer corresponding to the step (in years) between the years of the moving -window

opt.doPlot

(optional) default TRUE.
If TRUE, +window

+
opt.doPlot
+

(optional) default TRUE.
If TRUE, plot(s) will be processed, otherwise only the calculation and reorganization -of outputs will occur, be saved and returned

+of outputs will occur, be saved and returned

+
+
+

Value

+

A list containing two data.frame objects with the +following columns, and one ggplot2 object :

tab.hab
+

HAB
+

concerned habitat

-

Value

+
year
+

concerned simulation year

-

A list containing two data.frame objects with the -following columns, and one ggplot2 object :

-
-
tab.hab

-
HAB

concerned habitat

-
year

concerned simulation year

-
totalAbundance

total abundance over all the pixels +

totalAbundance
+

total abundance over all the pixels within the concerned habitat

-
no.PFG

number of PFG over all the pixels within the + +

no.PFG
+

number of PFG over all the pixels within the concerned habitat

-
evenness

evenness over all the pixels within the - concerned habitat

-
tab.stab

-
HAB

concerned habitat

-
no.years

number of simulation years used (moving + +

evenness
+

evenness over all the pixels within the + concerned habitat

+ + +

+ +
tab.stab
+

HAB
+

concerned habitat

+ +
no.years
+

number of simulation years used (moving window size)

-
yearStep

step between each simulation year of the moving + +

yearStep
+

step between each simulation year of the moving window

-
yearStart

first simulation year of the moving window

-
yearEnd

last simulation year of the moving window

-
metric

concerned metric (either totalAbundance or + +

yearStart
+

first simulation year of the moving window

+ +
yearEnd
+

last simulation year of the moving window

+ +
metric
+

concerned metric (either totalAbundance or evenness)

-
mean

mean value of the concerned metric over the years + +

mean
+

mean value of the concerned metric over the years of the concerned moving window

-
sd

value of standard deviation of the concerned metric + +

sd
+

value of standard deviation of the concerned metric over the years of the concerned moving window

-
cv

value of coefficient of variation of the concerned - metric over the years of the concerned moving window

-
plot.stab

ggplot2 object, representing the evolution of - total abundance and evenness of each habitat

-
+
cv
+

value of coefficient of variation of the concerned + metric over the years of the concerned moving window

-

Two POST_FATE_TABLE_HAB_evolution_[...].csv files are created :

-
stability1

always, containing tab.hab

-
stability2

if successive years available, containing - tab.stab

+ +

-
+
plot.stab
+

ggplot2 object, representing the evolution of + total abundance and evenness of each habitat

-

One POST_FATE_[...].pdf files is created :

-
GRAPHIC_A
stability

to visualize for each habitat the - evolution of its total abundance and its evenness through simulation time

+

Two POST_FATE_TABLE_HAB_evolution_[...].csv files are created :

stability1
+

always, containing tab.hab

-
+
stability2
+

if successive years available, containing + tab.stab

-

Details

+

One POST_FATE_[...].pdf files is created :

GRAPHIC_A
stability
+

to visualize for each habitat the + evolution of its total abundance and its evenness through simulation time

+ + +
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, one preanalytical graphic :

-
    -
  • the evolution of total abundance (FATE +

    • the evolution of total abundance (FATE arbitrary unit) and evenness (between 0 and 1) of each habitat through simulation time, with evenness representing the uniformity of the species composition of the habitat @@ -321,46 +276,43 @@

      Details \text{, }\text{Habitat}_j}}{abund_{\text{ PFG}_{all}\text{, } \text{Habitat}_j}} $$

    • -
    - -

    If the information has been provided (see -POST_FATE.temporalEvolution), the graphics will be also done -per habitat.

    -

    It requires that the POST_FATE.temporalEvolution +

If the information has been provided (see +POST_FATE.temporalEvolution), the graphics will be also done +per habitat.

+

It requires that the POST_FATE.temporalEvolution function has been run and that the file POST_FATE_TABLE_PIXEL_evolution_abundance.csv exists.

-

See also

- - -

Author

- +
+ +
+

Author

Ceres Barros, Maya Guéguen

+
+
- - - + + diff --git a/docs/reference/POST_FATE.graphic_mapPFG.html b/docs/reference/POST_FATE.graphic_mapPFG.html index e55c5ae..d1e53ee 100644 --- a/docs/reference/POST_FATE.graphic_mapPFG.html +++ b/docs/reference/POST_FATE.graphic_mapPFG.html @@ -1,73 +1,18 @@ - - - - - - - -Create a map related to plant functional group results (richness, +<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Create a map related to plant functional group results (richness, relative cover, light or soil CWM) for one (or several) specific year of a -FATE simulation — POST_FATE.graphic_mapPFG • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - -
-
- -
- -
+
@@ -214,217 +148,227 @@

Create a map related to plant functional group results (richness, or soil CWM) for one (or several) specific FATE simulation year.

-
POST_FATE.graphic_mapPFG(
-  name.simulation,
-  file.simulParam = NULL,
-  years,
-  opt.stratum_min = 1,
-  opt.stratum_max = 10,
-  opt.doBinary = TRUE,
-  opt.no_CPU = 1,
-  opt.doPlot = TRUE
-)
- -

Arguments

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+
POST_FATE.graphic_mapPFG(
+  name.simulation,
+  file.simulParam = NULL,
+  years,
+  opt.stratum_min = 0,
+  opt.stratum_max = 10,
+  opt.doBinary = TRUE,
+  opt.no_CPU = 1,
+  opt.doPlot = TRUE
+)
+
+ +
+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

years

an integer, or a vector of integer, +the PARAM_SIMUL folder of the FATE simulation

+
years
+

an integer, or a vector of integer, corresponding to the simulation year(s) that will be used to extract PFG -abundance and binary maps

opt.stratum_min

(optional) default 1.
An +abundance and binary maps

+
opt.stratum_min
+

(optional) default 1.
An integer corresponding to the lowest stratum from which PFG -abundances will be summed up

opt.stratum_max

(optional) default 10.
An +abundances will be summed up

+
opt.stratum_max
+

(optional) default 10.
An integer corresponding to the highest stratum from which PFG -abundances will be summed up

opt.doBinary

(optional) default TRUE.
If +abundances will be summed up

+
opt.doBinary
+

(optional) default TRUE.
If TRUE, abundance maps (absolute or relative) are systematically multiplied by binary maps (see -Details)

opt.no_CPU

(optional) default 1.
The number of +Details)

+
opt.no_CPU
+

(optional) default 1.
The number of resources that can be used to parallelize the unzip/zip of raster -files

opt.doPlot

(optional) default TRUE.
If TRUE, +files

+
opt.doPlot
+

(optional) default TRUE.
If TRUE, plot(s) will be processed, otherwise only the calculation and reorganization -of outputs will occur, be saved and returned

+of outputs will occur, be saved and returned

+
+
+

Value

+

A list containing one or several (one for each simulation +year) list of raster and ggplot2 objects :

tab
+

cover
+

raster of relative coverage

-

Value

+
DIV.0
+

raster of species richness

-

A list containing one or several (one for each simulation -year) list of raster and ggplot2 objects :

-
-
tab

-
cover

raster of relative coverage

-
DIV.0

raster of species richness

-
DIV.1

raster of Shannon entropy

-
DIV.2

raster of Simpson concentration

-
CWM.light

raster of light community weighted mean

-
CWM.soil

raster of soil community weighted mean

-
plot

-
cover

ggplot2 object, representing cover +

DIV.1
+

raster of Shannon entropy

+ +
DIV.2
+

raster of Simpson concentration

+ +
CWM.light
+

raster of light community weighted mean

+ +
CWM.soil
+

raster of soil community weighted mean

+ + +

+ +
plot
+

cover
+

ggplot2 object, representing cover raster

-
richness

ggplot2 object, representing + +

richness
+

ggplot2 object, representing DIV.0 raster

-
CWM.light

ggplot2 object, representing + +

CWM.light
+

ggplot2 object, representing CWM.light raster

-
CWM.soil

ggplot2 object, representing - CWM.soil raster

-
+
CWM.soil
+

ggplot2 object, representing + CWM.soil raster

+ +

-

POST_FATE_GRAPHIC_C_map_PFG_[...].pdf file is created containing up -to four graphics :

-
map_PFGcover

to visualize the PFG cover within the studied + +

POST_FATE_GRAPHIC_C_map_PFG_[...].pdf file is created containing up +to four graphics :

map_PFGcover
+

to visualize the PFG cover within the studied area

-
map_PFGrichness

to visualize the PFG richness within the + +

map_PFGrichness
+

to visualize the PFG richness within the studied area

-
PFGlight

to visualize the light CWM within the studied area

-
PFGsoil

to visualize the soil CWM within the studied area

-
+
PFGlight
+

to visualize the light CWM within the studied area

+ +
PFGsoil
+

to visualize the soil CWM within the studied area

-

Three PFGrichness_YEAR_[...]_STRATA_all_q[...].tif files are created -into the simulation results folder :

-
q0

PFG richness

-
q1

PFG Shannon entropy

-
q2

PFG Simpson concentration

-
+

Three PFGrichness_YEAR_[...]_STRATA_all_q[...].tif files are created +into the simulation results folder :

q0
+

PFG richness

-

Raster files are also created for cover, and light and soil CWM if those -modules were selected (see PRE_FATE.params_globalParameters).

-

Details

+
q1
+

PFG Shannon entropy

+
q2
+

PFG Simpson concentration

+ + +

Raster files are also created for cover, and light and soil CWM if those +modules were selected (see PRE_FATE.params_globalParameters).

+
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, up to six raster -maps and preanalytical graphics.

+maps
and preanalytical graphics.

For each PFG and each selected simulation year, raster maps are retrieved from the results folders ABUND_perPFG_perStrata and BIN_perPFG_perStrata and unzipped. Informations extracted lead to the production of up to six graphics before the maps are compressed again :

-

It requires that the POST_FATE.relativeAbund, +(POST_FATE.graphic_validationStatistics) and +POST_FATE.binaryMaps functions have been run and that the folders BIN_perPFG_allStrata and BIN_perPFG_perStrata exist. -

+

If opt.doBinary = TRUE, abundance maps (absolute or relative) are systematically multiplied by binary maps extracted from BIN_perPFG_allStrata and BIN_perPFG_perStrata folders and -produced by POST_FATE.binaryMaps function. +produced by POST_FATE.binaryMaps function. This way, produced raster maps reflect the validated/refined predictions. opt.doBinary can be set to FALSE to reflect pure simulation results.

-

See also

- - -

Author

- +
+ +
+

Author

Maya Guéguen

+
-

Examples

-
-if (FALSE) {                      
-POST_FATE.graphic_mapPFG(name.simulation = "FATE_simulation"
-                         , file.simulParam = "Simul_parameters_V1.txt"
-                         , years = 850
-                         , opt.stratum_min = 3
-                         , opt.no_CPU = 1)
+    
+

Examples

+

+if (FALSE) {                      
+POST_FATE.graphic_mapPFG(name.simulation = "FATE_simulation"
+                         , file.simulParam = "Simul_parameters_V1.txt"
+                         , years = 850
+                         , opt.stratum_min = 3
+                         , opt.no_CPU = 1)
                                     
-POST_FATE.graphic_mapPFG(name.simulation = "FATE_simulation"
-                         , file.simulParam = "Simul_parameters_V1.txt"
-                         , year = c(850, 950)
-                         , opt.doBinary = FALSE)
-}
+POST_FATE.graphic_mapPFG(name.simulation = "FATE_simulation"
+                         , file.simulParam = "Simul_parameters_V1.txt"
+                         , year = c(850, 950)
+                         , opt.doBinary = FALSE)
+}
                                     
                                     
 
-
+
+
+ - - - + + diff --git a/docs/reference/POST_FATE.graphic_mapPFGvsHS.html b/docs/reference/POST_FATE.graphic_mapPFGvsHS.html index 9b4dbd2..e2fc8b4 100644 --- a/docs/reference/POST_FATE.graphic_mapPFGvsHS.html +++ b/docs/reference/POST_FATE.graphic_mapPFGvsHS.html @@ -1,73 +1,18 @@ - - - - - - - -Create maps of both habitat suitability and simulated occurrences of +<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Create maps of both habitat suitability and simulated occurrences of each Plant Functional Group for one (or several) specific year of a -FATE simulation — POST_FATE.graphic_mapPFGvsHS • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - -
-
- -
- -
+
@@ -214,127 +148,114 @@

Create maps of both habitat suitability and simulated occurrences of FATE simulation year.

-
POST_FATE.graphic_mapPFGvsHS(
-  name.simulation,
-  file.simulParam = NULL,
-  years,
-  opt.stratum = "all"
-)
+
+
POST_FATE.graphic_mapPFGvsHS(
+  name.simulation,
+  file.simulParam = NULL,
+  years,
+  opt.stratum = "all"
+)
+
-

Arguments

- - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

years

an integer, or a vector of integer, +the PARAM_SIMUL folder of the FATE simulation

+
years
+

an integer, or a vector of integer, corresponding to the simulation year(s) that will be used to extract PFG -binary maps

opt.stratum

(optional) default all.
The stratum -number from which to extract PFG binary maps

- -

Value

- +binary maps

+
opt.stratum
+

(optional) default all.
The stratum +number from which to extract PFG binary maps

+
+
+

Value

A list containing one or several (one for each simulation year) list of ggplot2 objects, representing for each plant functional group its map of modelled presence / absence vs its -habitat suitability map.

- -

One POST_FATE_[...].pdf file is created :

-
GRAPHIC_B
map_PFGvsHS

to visualize the PFG presence +habitat suitability map.

One POST_FATE_[...].pdf file is created :

GRAPHIC_B
map_PFGvsHS
+

to visualize the PFG presence within the studied area (probability and simulated occurrence)

-
- -

Details

+
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, one preanalytical graphic. -

+

For each PFG and each selected simulation year, raster maps are retrieved from the results folder BIN_perPFG_allStrata (unless the opt.stratum is used, then it will be from the folder BIN_perPFG_perStrata) and unzipped. Informations extracted lead to the production of one graphic before the maps are compressed again :

-
    -
  • the comparison between each PFG habitat suitability map and - its simulated map of presence

  • -
- - -

It requires that the POST_FATE.relativeAbund and the -POST_FATE.binaryMaps function have been run +

  • the comparison between each PFG habitat suitability map and + its simulated map of presence

  • +

It requires that the POST_FATE.relativeAbund and the +POST_FATE.binaryMaps function have been run and that the folders BIN_perPFG_allStrata and BIN_perPFG_perStrata exist.

-

See also

- - -

Author

- +
+ +
+

Author

Maya Guéguen

+
-

Examples

-
-if (FALSE) {                      
-POST_FATE.graphic_mapPFGvsHS(name.simulation = "FATE_simulation"
-                             , file.simulParam = "Simul_parameters_V1.txt"
-                             , years = 850)
+    
+

Examples

+

+if (FALSE) {                      
+POST_FATE.graphic_mapPFGvsHS(name.simulation = "FATE_simulation"
+                             , file.simulParam = "Simul_parameters_V1.txt"
+                             , years = 850)
                                     
-POST_FATE.graphic_mapPFGvsHS(name.simulation = "FATE_simulation"
-                             , file.simulParam = "Simul_parameters_V1.txt"
-                             , years = c(850, 950))
+POST_FATE.graphic_mapPFGvsHS(name.simulation = "FATE_simulation"
+                             , file.simulParam = "Simul_parameters_V1.txt"
+                             , years = c(850, 950))
                                     
-POST_FATE.graphic_mapPFGvsHS(name.simulation = "FATE_simulation"
-                             , file.simulParam = "Simul_parameters_V1.txt"
-                             , years = 850
-                             , opt.stratum = 2)
-}
+POST_FATE.graphic_mapPFGvsHS(name.simulation = "FATE_simulation"
+                             , file.simulParam = "Simul_parameters_V1.txt"
+                             , years = 850
+                             , opt.stratum = 2)
+}
                                     
                                     
 
-
+
+
+ - - - + + diff --git a/docs/reference/POST_FATE.graphic_validationStatistics.html b/docs/reference/POST_FATE.graphic_validationStatistics.html index 1ab8738..0578546 100644 --- a/docs/reference/POST_FATE.graphic_validationStatistics.html +++ b/docs/reference/POST_FATE.graphic_validationStatistics.html @@ -1,73 +1,18 @@ - - - - - - - -Create a graphical representation of several statistics for each PFG +<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Create a graphical representation of several statistics for each PFG to asses the quality of the model for one (or several) specific year of a -FATE simulation — POST_FATE.graphic_validationStatistics • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + -
-
- -
- -
+
@@ -214,166 +148,161 @@

Create a graphical representation of several statistics for each PFG assessment for one (or several) specific FATE simulation year.

-
POST_FATE.graphic_validationStatistics(
-  name.simulation,
-  file.simulParam = NULL,
-  years,
-  mat.PFG.obs,
-  opt.ras_habitat = NULL,
-  opt.doPlot = TRUE
-)
- -

Arguments

- - - - - - - - - - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+
POST_FATE.graphic_validationStatistics(
+  name.simulation,
+  file.simulParam = NULL,
+  years,
+  mat.PFG.obs,
+  opt.ras_habitat = NULL,
+  opt.doPlot = TRUE
+)
+
+ +
+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

years

an integer, or a vector of integer, +the PARAM_SIMUL folder of the FATE simulation

+
years
+

an integer, or a vector of integer, corresponding to the simulation year(s) that will be used to extract PFG -relative abundance maps

mat.PFG.obs

a data.frame with 4 columns : PFG, -X, Y, obs

opt.ras_habitat

(optional) default NULL.
+relative abundance maps

+
mat.PFG.obs
+

a data.frame with 4 columns : PFG, +X, Y, obs

+
opt.ras_habitat
+

(optional) default NULL.
A string corresponding to the file name of a raster mask, with an -integer value within each pixel, corresponding to a specific habitat

opt.doPlot

(optional) default TRUE.
If TRUE, +integer value within each pixel, corresponding to a specific habitat

+
opt.doPlot
+

(optional) default TRUE.
If TRUE, plot(s) will be processed, otherwise only the calculation and reorganization -of outputs will occur, be saved and returned

+of outputs will occur, be saved and returned

+
+
+

Value

+

A list containing one data.frame object with the +following columns, and one ggplot2 object :

tab
+

PFG
+

concerned plant functional group

-

Value

+
AUC.sd
+

standard deviation of the AUC values

-

A list containing one data.frame object with the -following columns, and one ggplot2 object :

-
-
tab

-
PFG

concerned plant functional group

-
AUC.sd

standard deviation of the AUC values

-
sensitivity.sd

standard deviation of the sensitivity +

sensitivity.sd
+

standard deviation of the sensitivity values

-
specificity.sd

standard deviation of the specificity + +

specificity.sd
+

standard deviation of the specificity values

-
variable

name of the calculated statistic among + +

variable
+

name of the calculated statistic among sensitivity, specificity, TSS and AUC

-
value

value of the corresponding statistic

-
plot

ggplot2 object, representing the values for each PFG + +

value
+

value of the corresponding statistic

+ + +

+ +
plot
+

ggplot2 object, representing the values for each PFG of these four validation statistics (sensitivity, specificity, TSS, AUC) -

+

-
-

One POST_FATE_TABLE_YEAR_[...].csv file is created :

-
validationStatistics

containing the data.frame +

One POST_FATE_TABLE_YEAR_[...].csv file is created :

validationStatistics
+

containing the data.frame detailed above

-
-

One POST_FATE_[...].pdf file is created :

-
GRAPHIC_B
validationStatistics

to assess the modeling +

One POST_FATE_[...].pdf file is created :

GRAPHIC_B
validationStatistics
+

to assess the modeling quality of each PFG based on given observations within the studied area

-
- -

Details

+
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, PFG validation -statistic values and one preanalytical graphic.

+statistic values
and one preanalytical graphic.

Observation records (presences and absences) are required for each PFG within the mat.PFG.obs object :

-
-
PFG

the concerned plant functional group

-
X, Y

the coordinates of each observation, matching with the +

PFG
+

the concerned plant functional group

+ +
X, Y
+

the coordinates of each observation, matching with the projection of the mask of name.simulation

-
obs

either 0 or 1 to indicate presence or - absence

-
+
obs
+

either 0 or 1 to indicate presence or + absence

-

For each PFG and each selected simulation year, raster maps are retrieved +

For each PFG and each selected simulation year, raster maps are retrieved from the results folder ABUND_REL_perPFG_allStrata and lead to the production of one table :

-
    -
  • the value of several statistics to evaluate the predictive - quality of the model for each plant functional group
    - (sensitivity, - specificity, - auc, +

    • the value of several statistics to evaluate the predictive + quality of the model for each plant functional group
      + (sensitivity, + specificity, + auc, TSS = sensitivity + specificity - 1)

    • -
    - -

    If a raster mask for habitat has been provided, the values and -graphics will be also calculated per habitat.

    -

    It requires that the POST_FATE.relativeAbund +

If a raster mask for habitat has been provided, the values and +graphics will be also calculated per habitat.

+

It requires that the POST_FATE.relativeAbund function has been run and that the folder ABUND_REL_perPFG_allStrata -exists.

+exists.

This .csv file can then be used by other functions :

- - -

See also

- - -

Author

- +
+ +
+

Author

Isabelle Boulangeat, Damien Georges, Maya Guéguen

+
+
- - - + + diff --git a/docs/reference/POST_FATE.graphics.html b/docs/reference/POST_FATE.graphics.html index 519887b..4e9cb2b 100644 --- a/docs/reference/POST_FATE.graphics.html +++ b/docs/reference/POST_FATE.graphics.html @@ -1,74 +1,19 @@ - - - - - - - -Create all possible graphical representations for a FATE -simulation — POST_FATE.graphics • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create all possible graphical representations for a FATE +simulation — POST_FATE.graphics • RFate - - - - - - - - - + + - - - - -
-
- -
- -
+
@@ -217,329 +151,302 @@

Create all possible graphical representations for a FATE specific year (richness, abundance, light, soil).

-
POST_FATE.graphics(
-  name.simulation,
-  file.simulParam = NULL,
-  years,
-  no_years,
-  opt.ras_habitat = NULL,
-  doFunc.evolCov = TRUE,
-  doFunc.evolPix = TRUE,
-  doFunc.evolStab = TRUE,
-  evolPix.cells_ID = NULL,
-  evolStab.mw_size = 3,
-  evolStab.mw_step = 1,
-  evol.fixedScale = TRUE,
-  doFunc.valid = TRUE,
-  valid.mat.PFG.obs,
-  doFunc.mapPFGvsHS = TRUE,
-  doFunc.mapPFG = TRUE,
-  mapPFGvsHS.stratum = "all",
-  binMap.method,
-  binMap.method1.threshold = 0.05,
-  binMap.method2.cutoff = NULL,
-  mapPFG.stratum_min = 1,
-  mapPFG.stratum_max = 10,
-  mapPFG.doBinary = TRUE,
-  opt.doPlot = TRUE,
-  opt.no_CPU = 1
-)
- -

Arguments

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+
POST_FATE.graphics(
+  name.simulation,
+  file.simulParam = NULL,
+  years,
+  no_years,
+  opt.ras_habitat = NULL,
+  doFunc.evolCov = TRUE,
+  doFunc.evolPix = TRUE,
+  doFunc.evolStab = TRUE,
+  evolPix.cells_ID = NULL,
+  evolStab.mw_size = 3,
+  evolStab.mw_step = 1,
+  evol.fixedScale = TRUE,
+  doFunc.valid = TRUE,
+  valid.mat.PFG.obs,
+  doFunc.mapPFGvsHS = TRUE,
+  doFunc.mapPFG = TRUE,
+  mapPFGvsHS.stratum = "all",
+  binMap.method,
+  binMap.method1.threshold = 0.05,
+  binMap.method2.cutoff = NULL,
+  mapPFG.stratum_min = 1,
+  mapPFG.stratum_max = 10,
+  mapPFG.doBinary = TRUE,
+  opt.doPlot = TRUE,
+  opt.no_CPU = 1
+)
+
+ +
+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

years

an integer, or a vector of integer, +the PARAM_SIMUL folder of the FATE simulation

+
years
+

an integer, or a vector of integer, corresponding to the simulation year(s) that will be used to extract PFG -abundance maps (see POST_FATE.relativeAbund, -POST_FATE.graphic_validationStatistics, -POST_FATE.graphic_mapPFGvsHS)

no_years

an integer corresponding to the number of simulation +abundance maps (see POST_FATE.relativeAbund, +POST_FATE.graphic_validationStatistics, +POST_FATE.graphic_mapPFGvsHS)

+
no_years
+

an integer corresponding to the number of simulation years that will be used to extract PFG abundance / light / soil maps (see -POST_FATE.temporalEvolution)

opt.ras_habitat

(optional) default NULL.
+POST_FATE.temporalEvolution)

+
opt.ras_habitat
+

(optional) default NULL.
A string corresponding to the file name of a raster mask, with an integer value within each pixel, corresponding to a specific habitat -(see POST_FATE.temporalEvolution, -POST_FATE.graphic_validationStatistics)

doFunc.evolCov

default TRUE.
If TRUE, -POST_FATE.graphic_evolutionCoverage function will be run.

doFunc.evolPix

default TRUE.
If TRUE, -POST_FATE.graphic_evolutionPixels function will be run.

doFunc.evolStab

default TRUE.
If TRUE, -POST_FATE.graphic_evolutionStability function will be run.

evolPix.cells_ID

(optional) default NULL.
The +(see POST_FATE.temporalEvolution, +POST_FATE.graphic_validationStatistics)

+
doFunc.evolCov
+

default TRUE.
If TRUE, +POST_FATE.graphic_evolutionCoverage function will be run.

+
doFunc.evolPix
+

default TRUE.
If TRUE, +POST_FATE.graphic_evolutionPixels function will be run.

+
doFunc.evolStab
+

default TRUE.
If TRUE, +POST_FATE.graphic_evolutionStability function will be run.

+
evolPix.cells_ID
+

(optional) default NULL.
The cells ID of the studied area for which PFG abundances will be extracted -(see POST_FATE.graphic_evolutionPixels)

evolStab.mw_size

(optional) default NULL.
An +(see POST_FATE.graphic_evolutionPixels)

+
evolStab.mw_size
+

(optional) default NULL.
An integer corresponding to the size (in years) of the moving window that will be used to calculate metrics of habitat stability (see -POST_FATE.graphic_evolutionStability)

evolStab.mw_step

(optional) default NULL.
An +POST_FATE.graphic_evolutionStability)

+
evolStab.mw_step
+

(optional) default NULL.
An integer corresponding to the step (in years) of the moving window that will be used to calculate metrics of habitat stability (see -POST_FATE.graphic_evolutionStability)

evol.fixedScale

(optional) default TRUE.
If +POST_FATE.graphic_evolutionStability)

+
evol.fixedScale
+

(optional) default TRUE.
If FALSE, the ordinate scale will be adapted for each PFG for the graphical representation of the evolution of abundances through time (see -POST_FATE.graphic_evolutionCoverage)

doFunc.valid

default TRUE.
If TRUE, -POST_FATE.graphic_validationStatistics function will be run.

valid.mat.PFG.obs

a data.frame with 4 columns : PFG, +POST_FATE.graphic_evolutionCoverage)

+
doFunc.valid
+

default TRUE.
If TRUE, +POST_FATE.graphic_validationStatistics function will be run.

+
valid.mat.PFG.obs
+

a data.frame with 4 columns : PFG, X, Y, obs (see -POST_FATE.graphic_validationStatistics)

doFunc.mapPFGvsHS

default TRUE.
If TRUE, -POST_FATE.graphic_mapPFGvsHS function will be run.

doFunc.mapPFG

default TRUE.
If TRUE, -POST_FATE.graphic_mapPFG function will be run.

mapPFGvsHS.stratum

(optional) default all.
The +POST_FATE.graphic_validationStatistics)

+
doFunc.mapPFGvsHS
+

default TRUE.
If TRUE, +POST_FATE.graphic_mapPFGvsHS function will be run.

+
doFunc.mapPFG
+

default TRUE.
If TRUE, +POST_FATE.graphic_mapPFG function will be run.

+
mapPFGvsHS.stratum
+

(optional) default all.
The stratum number from which to extract PFG binary maps (see -POST_FATE.graphic_mapPFGvsHS)

binMap.method

an integer to choose the transformation method : -
1 (relative abundance) or 2 (optimizing TSS) (see -POST_FATE.binaryMaps)

binMap.method1.threshold

default 0.05.
If method = 1, +POST_FATE.graphic_mapPFGvsHS)

+
binMap.method
+

an integer to choose the transformation method : +
1 (relative abundance) or 2 (optimizing TSS) (see +POST_FATE.binaryMaps)

+
binMap.method1.threshold
+

default 0.05.
If method = 1, minimum relative abundance required for each PFG to be considered as present -in the concerned pixel (see POST_FATE.binaryMaps)

binMap.method2.cutoff

default NULL.
If method = 2, a -data.frame with 3 columns : year, PFG, cutoff
-(see POST_FATE.binaryMaps)

mapPFG.stratum_min

(optional) default 1.
An +in the concerned pixel (see POST_FATE.binaryMaps)

+
binMap.method2.cutoff
+

default NULL.
If method = 2, a +data.frame with 3 columns : year, PFG, cutoff
+(see POST_FATE.binaryMaps)

+
mapPFG.stratum_min
+

(optional) default 1.
An integer corresponding to the lowest stratum from which PFG -abundances will be summed up (see POST_FATE.graphic_mapPFG)

mapPFG.stratum_max

(optional) default 10.
An +abundances will be summed up (see POST_FATE.graphic_mapPFG)

+
mapPFG.stratum_max
+

(optional) default 10.
An integer corresponding to the highest stratum from which PFG -abundances will be summed up (see POST_FATE.graphic_mapPFG)

mapPFG.doBinary

(optional) default TRUE.
If +abundances will be summed up (see POST_FATE.graphic_mapPFG)

+
mapPFG.doBinary
+

(optional) default TRUE.
If TRUE, abundance maps (absolute or relative) are systematically -multiplied by binary maps (see POST_FATE.graphic_mapPFG)

opt.doPlot

(optional) default TRUE.
If TRUE, +multiplied by binary maps (see POST_FATE.graphic_mapPFG)

+
opt.doPlot
+

(optional) default TRUE.
If TRUE, plot(s) will be processed, otherwise only the calculation and reorganization -of outputs will occur, be saved and returned

opt.no_CPU

(optional) default 1.
The number of +of outputs will occur, be saved and returned

+
opt.no_CPU
+

(optional) default 1.
The number of resources that can be used to parallelize the unzip/zip of raster -files, as well as the extraction of values from raster files

+files, as well as the extraction of values from raster files

+
+
+

Value

+

The following POST_FATE_GRAPHIC_[...].pdf files are created :

A_evolution_coverage
+

spaceOccupancy
+

to visualize for each PFG the evolution of + its occupation of the studied area through simulation time

-

Value

+
abundance
+

to visualize for each PFG the evolution of its + abundance within the whole studied area through simulation time

-

The following POST_FATE_GRAPHIC_[...].pdf files are created :

-
A_evolution_coverage

-
spaceOccupancy

to visualize for each PFG the evolution of - its occupation of the studied area through simulation time

-
abundance

to visualize for each PFG the evolution of its - abundance within the whole studied area through simulation time

-
A_evolution_pixels

to visualize for each PFG the evolution + +

+ +
A_evolution_pixels
+

to visualize for each PFG the evolution of its abundance within each selected pixel through simulation time, as well as the evolution of light and soil resources

-
A_evolution_stability

to visualize for each habitat the + +

A_evolution_stability
+

to visualize for each habitat the evolution of its total abundance and its evenness through simulation time

-
B_validationStatistics

to assess the modeling quality of + +

B_validationStatistics
+

to assess the modeling quality of each PFG based on given observations within the studied area

-
B_map_PFGvsHS

to visualize the PFG presence within the + +

B_map_PFGvsHS
+

to visualize the PFG presence within the studied area (probability and simulated occurrence)

-
C_map_PFG

-
PFGcover

to visualize the PFG cover within the studied + +

C_map_PFG
+

PFGcover
+

to visualize the PFG cover within the studied area

-
PFGrichness

to visualize the PFG richness within the + +

PFGrichness
+

to visualize the PFG richness within the studied area

-
PFGlight

to visualize the light CWM within the studied + +

PFGlight
+

to visualize the light CWM within the studied area

-
PFGsoil

to visualize the soil CWM within the studied area

-
+
PFGsoil
+

to visualize the soil CWM within the studied area

+ + +

+ -

Three folders are created :

-
ABUND_REL_perPFG
_allStrata

containing relative +

Three folders are created :

ABUND_REL_perPFG
_allStrata
+

containing relative abundance raster maps for each PFG across all strata (see - POST_FATE.relativeAbund)

-
BIN_perPFG
_allStrata

containing presence / absence + POST_FATE.relativeAbund)

+ +
BIN_perPFG
_allStrata
+

containing presence / absence raster maps for each PFG across all strata (see - POST_FATE.binaryMaps)

-
BIN_perPFG
_perStrata

containing presence / absence - raster maps for each PFG for each stratum (see - POST_FATE.binaryMaps)

+ POST_FATE.binaryMaps)

-
+
BIN_perPFG
_perStrata
+

containing presence / absence + raster maps for each PFG for each stratum (see + POST_FATE.binaryMaps)

-

Details

+
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, up to eleven -preanalytical graphics.

+preanalytical graphics
.

For each PFG and each selected simulation year, raster maps are retrieved from the results folders (ABUND_perPFG_perStrata, ABUND_perPFG_allStrata, ABUND_REL_perPFG_allStrata, BIN_perPFG_perStrata, BIN_perPFG_allStrata, LIGHT or SOIL) and unzipped. Informations extracted lead to the production of the following graphics before the maps are compressed again :

-
+ +
+

Author

Maya Guéguen

+
+
- - - + + diff --git a/docs/reference/POST_FATE.relativeAbund.html b/docs/reference/POST_FATE.relativeAbund.html index af4dfdd..66cda9c 100644 --- a/docs/reference/POST_FATE.relativeAbund.html +++ b/docs/reference/POST_FATE.relativeAbund.html @@ -1,71 +1,16 @@ - - - - - - - -Create relative abundance maps for each Plant Functional Group for -one (or several) specific year of a FATE simulation — POST_FATE.relativeAbund • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create relative abundance maps for each Plant Functional Group for +one (or several) specific year of a FATE simulation — POST_FATE.relativeAbund • RFate - - - - - - - - - - + + - - - -
-
- -
- -
+
@@ -211,121 +145,108 @@

Create relative abundance maps for each Plant Functional Group for simulation year.

-
POST_FATE.relativeAbund(
-  name.simulation,
-  file.simulParam = NULL,
-  years,
-  opt.no_CPU = 1
-)
+
+
POST_FATE.relativeAbund(
+  name.simulation,
+  file.simulParam = NULL,
+  years,
+  opt.no_CPU = 1
+)
+
-

Arguments

- - - - - - - - - - - - - - - - - - -
name.simulation

a string corresponding to the main directory -or simulation name of the FATE simulation

file.simulParam

default NULL.
A string +

+

Arguments

+
name.simulation
+

a string corresponding to the main directory +or simulation name of the FATE simulation

+
file.simulParam
+

default NULL.
A string corresponding to the name of a parameter file that will be contained into -the PARAM_SIMUL folder of the FATE simulation

years

an integer, or a vector of integer, +the PARAM_SIMUL folder of the FATE simulation

+
years
+

an integer, or a vector of integer, corresponding to the simulation year(s) that will be used to extract PFG -abundance maps

opt.no_CPU

(optional) default 1.
The number of +abundance maps

+
opt.no_CPU
+

(optional) default 1.
The number of resources that can be used to parallelize the unzip/zip of raster -files

- -

Value

- -

One result folder is created :

-
ABUND_REL_perPFG
_allStrata

containing relative +files

+
+
+

Value

+

One result folder is created :

ABUND_REL_perPFG
_allStrata
+

containing relative abundance raster maps for each PFG across all strata

-
- -

Details

+
+
+

Details

This function allows to obtain, for a specific FATE simulation and a specific parameter file within this simulation, raster maps of PFG -relative abundance.

+relative abundance.

For each PFG and each selected simulation year, raster maps are retrieved from the results folder ABUND_perPFG_allStrata and unzipped. Informations extracted lead to the production of the same number of raster before the maps are compressed again :

-
    -
  • for each selected simulation year(s), relative abundances +

    • for each selected simulation year(s), relative abundances for all strata combined are calculated : $$\frac{abund_{\text{ PFG}_i\text{, }\text{Stratum}_{all}}} - {abund_{\text{ PFG}_{all}\text{, }\text{Stratum}_{all}}}$$

    • -
    - -

    These raster files can then be used by other functions :

    - - -

    See also

    - - -

    Author

    - + {abund_{\text{ PFG}_{all}\text{, }\text{Stratum}_{all}}}$$

  • +

These raster files can then be used by other functions :

+
+ +
+

Author

Maya Guéguen

+
-

Examples

-
-if (FALSE) {                      
-POST_FATE.relativeAbund(name.simulation = "FATE_simulation"
-                        , file.simulParam = "Simul_parameters_V1.txt"
-                        , years = 850
-                        , opt.no_CPU = 1)
+    
+

Examples

+

+if (FALSE) {                      
+POST_FATE.relativeAbund(name.simulation = "FATE_simulation"
+                        , file.simulParam = "Simul_parameters_V1.txt"
+                        , years = 850
+                        , opt.no_CPU = 1)
                                     
-POST_FATE.relativeAbund(name.simulation = "FATE_simulation"
-                        , file.simulParam = "Simul_parameters_V1.txt"
-                        , years = c(850, 950)
-                        , opt.no_CPU = 1)
-}
+POST_FATE.relativeAbund(name.simulation = "FATE_simulation"
+                        , file.simulParam = "Simul_parameters_V1.txt"
+                        , years = c(850, 950)
+                        , opt.no_CPU = 1)
+}
                                                         
                                     
 
-
+
+
+ - - - + + diff --git a/docs/reference/POST_FATE.temporalEvolution.html b/docs/reference/POST_FATE.temporalEvolution.html index df368ef..b5a66d7 100644 --- a/docs/reference/POST_FATE.temporalEvolution.html +++ b/docs/reference/POST_FATE.temporalEvolution.html @@ -22,7 +22,7 @@ RFate - 1.0.3 + 1.1.0 @@ -34,7 +34,7 @@
-
  • from user data :
    - with the values contained within the responseStage, - killedIndiv and resproutIndiv columns, if provided
    + + +

  • +
  • from user data :
    with the values contained within the responseStage, + killedIndiv and resproutIndiv columns, if provided
    The PFG column can contain either the life form (H, C or P) or the PFG name. Both methods can be combined - (but are applied in the order given by the PFG column).

    -

  • -

    -
    PROP_KILLED

    = the proportion of propagules killed by each - disturbance
    - (currently set to 0 for all PFG and disturbances)

    -
    ACTIVATED_SEED

    = the proportion of seeds activated by each - disturbance
    - (currently set to 0 for all PFG and disturbances)

    + (but are applied in the order given by the PFG column).

    +

    - +
    PROP_KILLED
    +

    = the proportion of propagules killed by each + disturbance
    + (currently set to 0 for all PFG and disturbances)

    -

    See also

    +
    ACTIVATED_SEED
    +

    = the proportion of seeds activated by each + disturbance
    + (currently set to 0 for all PFG and disturbances)

    - -

    Author

    + + +
    +

    Author

    Isabelle Boulangeat, Damien Georges, Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation')
    -PRE_FATE.skeletonDirectory()
    -
    -
    -mat.char = data.frame(PFG = paste0('PFG', 1:6)
    -                      , type = c('C', 'C', 'H', 'H', 'P', 'P')
    -                      , maturity = c(5, 5, 3, 3, 8, 9)
    -                      , longevity = c(12, 200, 25, 4, 110, 70)
    -                      , age_above_150cm = c(1000, 100, 1000, 1000, 10, 12))
    -
    -mat.tol = data.frame(nameDist = 'grazing'
    -                     , PFG = paste0('PFG', 1:6)
    -                     , strategy_tol = c('indifferent', 'grazing_herbs_1'
    -                                        , 'grazing_herbs_1', 'grazing_herbs_2'
    -                                        , 'indifferent', 'grazing_trees_2'))
    -
    -## Create PFG response to disturbance parameter files (with PFG characteristics) -------------
    -PRE_FATE.params_PFGdisturbance(name.simulation = 'FATE_simulation'
    -                               , mat.PFG.dist = mat.char
    -                               , mat.PFG.tol = mat.tol)
    +    
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation')
    +PRE_FATE.skeletonDirectory()
    +
    +
    +mat.char = data.frame(PFG = paste0('PFG', 1:6)
    +                      , type = c('C', 'C', 'H', 'H', 'P', 'P')
    +                      , maturity = c(5, 5, 3, 3, 8, 9)
    +                      , longevity = c(12, 200, 25, 4, 110, 70)
    +                      , age_above_150cm = c(1000, 100, 1000, 1000, 10, 12))
    +
    +mat.tol = data.frame(nameDist = 'grazing'
    +                     , PFG = paste0('PFG', 1:6)
    +                     , strategy_tol = c('indifferent', 'grazing_herbs_1'
    +                                        , 'grazing_herbs_1', 'grazing_herbs_2'
    +                                        , 'indifferent', 'grazing_trees_2'))
    +
    +## Create PFG response to disturbance parameter files (with PFG characteristics) -------------
    +PRE_FATE.params_PFGdisturbance(name.simulation = 'FATE_simulation'
    +                               , mat.PFG.dist = mat.char
    +                               , mat.PFG.tol = mat.tol)
                                                             
     
    -## Create PFG response to disturbance parameter files (with all values) ----------------------
    -mat.tol = expand.grid(responseStage = 1:3
    -                      , PFG = paste0('PFG', 1:6)
    -                      , nameDist = 'Mowing')
    -mat.tol$breakAge = c(1, 4, 10
    -                     , 1, 4, 10
    -                     , 1, 2, 50
    -                     , 1, 2, 20
    -                     , 2, 6, 95
    -                     , 3, 8, 55)
    -mat.tol$resproutAge = c(0, 0, 4
    -                        , 0, 0, 4
    -                        , 0, 0, 2
    -                        , 0, 0, 2
    -                        , 0, 2, 5
    -                        , 0, 4, 7)
    -mat.tol$killedIndiv = c(10, 10, 5
    -                        , 10, 10, 5
    -                        , 10, 10, 5
    -                        , 10, 10, 5
    -                        , 10, 7, 4
    -                        , 10, 6, 3)
    -mat.tol$resproutIndiv = c(0, 0, 5
    -                          , 0, 0, 5
    -                          , 0, 0, 3
    -                          , 0, 0, 3
    -                          , 0, 1, 4
    -                          , 0, 2, 5)
    -str(mat.tol)
    -
    -PRE_FATE.params_PFGdisturbance(name.simulation = 'FATE_simulation'
    -                               , mat.PFG.tol = mat.tol)
    +## Create PFG response to disturbance parameter files (with all values) ----------------------
    +mat.tol = expand.grid(responseStage = 1:3
    +                      , PFG = paste0('PFG', 1:6)
    +                      , nameDist = 'Mowing')
    +mat.tol$breakAge = c(1, 4, 10
    +                     , 1, 4, 10
    +                     , 1, 2, 50
    +                     , 1, 2, 20
    +                     , 2, 6, 95
    +                     , 3, 8, 55)
    +mat.tol$resproutAge = c(0, 0, 4
    +                        , 0, 0, 4
    +                        , 0, 0, 2
    +                        , 0, 0, 2
    +                        , 0, 2, 5
    +                        , 0, 4, 7)
    +mat.tol$killedIndiv = c(10, 10, 5
    +                        , 10, 10, 5
    +                        , 10, 10, 5
    +                        , 10, 10, 5
    +                        , 10, 7, 4
    +                        , 10, 6, 3)
    +mat.tol$resproutIndiv = c(0, 0, 5
    +                          , 0, 0, 5
    +                          , 0, 0, 3
    +                          , 0, 0, 3
    +                          , 0, 1, 4
    +                          , 0, 2, 5)
    +str(mat.tol)
    +
    +PRE_FATE.params_PFGdisturbance(name.simulation = 'FATE_simulation'
    +                               , mat.PFG.tol = mat.tol)
                                                             
                                                             
    -## -------------------------------------------------------------------------------------------
    +## -------------------------------------------------------------------------------------------
     
    -## Load example data
    -Champsaur_params = .loadData('Champsaur_params', 'RData')
    +## Load example data
    +Champsaur_params = .loadData('Champsaur_params', 'RData')
     
    -## Create a skeleton folder
    -PRE_FATE.skeletonDirectory(name.simulation = 'FATE_Champsaur')
    +## Create a skeleton folder
    +PRE_FATE.skeletonDirectory(name.simulation = 'FATE_Champsaur')
     
     
    -## PFG traits for succession
    -tab.succ = Champsaur_params$tab.SUCC
    -str(tab.succ)
    +## PFG traits for succession
    +tab.succ = Champsaur_params$tab.SUCC
    +str(tab.succ)
     
    -## Create PFG succession parameter files (fixing strata limits) --------------
    -PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_Champsaur'
    -                           , mat.PFG.succ = tab.succ
    -                           , strata.limits = c(0, 20, 50, 150, 400, 1000, 2000)
    -                           , strata.limits_reduce = FALSE)
    +## Create PFG succession parameter files (fixing strata limits) --------------
    +PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_Champsaur'
    +                           , mat.PFG.succ = tab.succ
    +                           , strata.limits = c(0, 20, 50, 150, 400, 1000, 2000)
    +                           , strata.limits_reduce = FALSE)
     
    -require(data.table)
    -tmp = fread('FATE_Champsaur/DATA/PFGS/SUCC_COMPLETE_TABLE.csv')
    -tab.succ = Champsaur_params$tab.SUCC
    -tab.succ$age_above_150cm = tmp$CHANG_STR_AGES_to_str_4_150
    -tab.succ = tab.succ[, c('PFG', 'type', 'maturity', 'longevity', 'age_above_150cm')]
    -str(tab.succ)
    +require(data.table)
    +tmp = fread('FATE_Champsaur/DATA/PFGS/SUCC_COMPLETE_TABLE.csv')
    +tab.succ = Champsaur_params$tab.SUCC
    +tab.succ$age_above_150cm = tmp$CHANG_STR_AGES_to_str_4_150
    +tab.succ = tab.succ[, c('PFG', 'type', 'maturity', 'longevity', 'age_above_150cm')]
    +str(tab.succ)
     
    -## PFG traits for disturbance
    -tab.dist = Champsaur_params$tab.DIST
    -str(tab.dist)
    +## PFG traits for disturbance
    +tab.dist = Champsaur_params$tab.DIST
    +str(tab.dist)
     
    -## Create PFG response to disturbance parameter files (give warnings) ------------------------
    -PRE_FATE.params_PFGdisturbance(name.simulation = 'FATE_Champsaur'
    -                               , mat.PFG.dist = tab.succ
    -                               , mat.PFG.tol = tab.dist)
    +## Create PFG response to disturbance parameter files (give warnings) ------------------------
    +PRE_FATE.params_PFGdisturbance(name.simulation = 'FATE_Champsaur'
    +                               , mat.PFG.dist = tab.succ
    +                               , mat.PFG.tol = tab.dist)
     
     
    -
    +
    + + - - - + + diff --git a/docs/reference/PRE_FATE.params_PFGdrought.html b/docs/reference/PRE_FATE.params_PFGdrought.html index a0824e0..3d813fb 100644 --- a/docs/reference/PRE_FATE.params_PFGdrought.html +++ b/docs/reference/PRE_FATE.params_PFGdrought.html @@ -1,71 +1,16 @@ - - - - - - - -Create DROUGHT parameter files for a FATE -simulation — PRE_FATE.params_PFGdrought • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create DROUGHT parameter files for a FATE +simulation — PRE_FATE.params_PFGdrought • RFate - - - - - - - - - + + - - - - -
    -
    - -
    - -
    +
    @@ -211,325 +145,358 @@

    Create DROUGHT parameter files for a FATE of them) used in the drought disturbance module of FATE.

    -
    PRE_FATE.params_PFGdrought(
    -  name.simulation,
    -  mat.PFG.dist = NULL,
    -  mat.PFG.tol,
    -  mat.PFG.drought,
    -  opt.folder.name = NULL
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    mat.PFG.dist

    (optional)
    -a data.frame with 5 columns :
    -PFG, type, maturity, longevity, +

    +
    PRE_FATE.params_PFGdrought(
    +  name.simulation,
    +  mat.PFG.dist = NULL,
    +  mat.PFG.tol,
    +  mat.PFG.drought,
    +  opt.folder.name = NULL
    +)
    +
    + +
    +

    Arguments

    +
    name.simulation
    +

    a string corresponding to the main directory +or simulation name of the FATE simulation

    +
    mat.PFG.dist
    +

    (optional)
    +a data.frame with 5 columns :
    PFG, type, maturity, longevity, age_above_150cm (see -Details)

    mat.PFG.tol

    a data.frame with 3 to 7 columns :

      -
    • nameDist,

    • +Details)

      +
      mat.PFG.tol
      +

      a data.frame with 3 to 7 columns :

      • nameDist,

      • PFG,

      • (responseStage, breakAge, resproutAge),

      • responseStage, killedIndiv, resproutIndiv (or strategy_tol)

      • -

      (see Details)

    mat.PFG.drought

    a data.frame with 4 or 6 columns :

      -
    • PFG,

    • +

    (see Details)

    +
    mat.PFG.drought
    +

    a data.frame with 4 or 6 columns :

    • PFG,

    • threshold_moderate, threshold_severe,

    • counter_recovery, counter_sens, counter_cum (or strategy_drou)

    • -
    opt.folder.name

    (optional)
    a string corresponding + +

    opt.folder.name
    +

    (optional)
    a string corresponding to the name of the folder that will be created into the -name.simulation/DATA/PFGS/DROUGHT/ directory to store the results

    - -

    Value

    - +name.simulation/DATA/PFGS/DROUGHT/ directory to store the results

    +
    +
    +

    Value

    A .txt file per PFG into the name.simulation/DATA/PFGS/DROUGHT/ directory with the following -parameters :

    -
    -
    BREAK_AGE

    ages at which the PFG changes of response stage +parameters :

    BREAK_AGE
    +

    ages at which the PFG changes of response stage (in years)

    -
    RESPR_AGE

    resprouting age table (in a single row)
    + +

    RESPR_AGE
    +

    resprouting age table (in a single row)
    This is a vector of no.DIST (=2) * no.responseStages numbers - corresponding
    to the age at which the PFG can be rejuvenated - (younger than the actual one) : -

      -
    • at different response stages (RS)

    • + corresponding
      to the age at which the PFG can be rejuvenated + (younger than the actual one) :

      • at different response stages (RS)

      • for each disturbance (DI).

      • -
      - These parameters should be given in this order (e.g. with 3 response - stages) :
      DI1_RS1, DI1_RS2, DI1_RS3, DI2_RS1... (in +

    These parameters should be given in this order (e.g. with 3 response + stages) :
    DI1_RS1, DI1_RS2, DI1_RS3, DI2_RS1... (in years).

    -
    FATES

    disturbance response table (in a single row)
    + +

    FATES
    +

    disturbance response table (in a single row)
    This is a vector of no.DIST (=2) * no.responseStages * 2 numbers - corresponding
    to the proportion of individuals : -

      -
    • that will be killed (Ki) or resprout + corresponding
      to the proportion of individuals :

      • that will be killed (Ki) or resprout (Re)

      • at different response stages (RS)

      • for each disturbance (DI).

      • -
      - These parameters should be given in this order (e.g. with 3 response - stages) :
      DI1_RS1_Ki, DI1_RS1_Re, DI1_RS2_Ki, DI1_RS2_Re, +

    These parameters should be given in this order (e.g. with 3 response + stages) :
    DI1_RS1_Ki, DI1_RS1_Re, DI1_RS2_Ki, DI1_RS2_Re, DI1_RS3_Ki, DI1_RS3_Re, DI2_RS1_Ki... -
    (from 0 to 10, corresponding to 0 to 100%).

    -
    PROP_KILLED

    proportion of propagules killed by each disturbance
    - (from 0 to 10, corresponding to 0 to 100%)

    -
    ACTIVATED_SEED

    proportion of seeds activated by each disturbance
    - (from 0 to 10, corresponding to 0 to 100%)

    -
    THRESHOLD_MOD

    threshold below which the PFG will experience - moderate drought
    (same unit as that of the map given with the +
    (from 0 to 10, corresponding to 0 to 100%).

    + +
    PROP_KILLED
    +

    proportion of propagules killed by each disturbance
    (from 0 to 10, corresponding to 0 to 100%)

    + +
    ACTIVATED_SEED
    +

    proportion of seeds activated by each disturbance
    (from 0 to 10, corresponding to 0 to 100%)

    + +
    THRESHOLD_MOD
    +

    threshold below which the PFG will experience + moderate drought
    (same unit as that of the map given with the DROUGHT_MASK flag in - PRE_FATE.params_globalParameters)

    -
    THRESHOLD_SEV

    threshold below which the PFG will experience - severe drought
    (same unit as that of the map given with the + PRE_FATE.params_globalParameters)

    + +
    THRESHOLD_SEV
    +

    threshold below which the PFG will experience + severe drought
    (same unit as that of the map given with the DROUGHT_MASK flag in - PRE_FATE.params_globalParameters)

    -
    COUNTER_RECOVERY

    number of years removed from the PFG counter of + PRE_FATE.params_globalParameters)

    + +
    COUNTER_RECOVERY
    +

    number of years removed from the PFG counter of cumulated consecutive years of drought events, during non-drought years

    -
    COUNTER_SENS

    number of consecutive years of drought the PFG must - experience before suffering severe effects due to a severe drought
    + +

    COUNTER_SENS
    +

    number of consecutive years of drought the PFG must + experience before suffering severe effects due to a severe drought
    (sensitivity to severe drought)

    -
    COUNTER_CUM

    number of consecutive years of drought the PFG must + +

    COUNTER_CUM
    +

    number of consecutive years of drought the PFG must experience before any subsequent drought event start having severe effects -
    (cumulative drought response)

    +
    (cumulative drought response)

    -
    -

    A DROUGHT_COMPLETE_TABLE.csv file summarizing information for all -groups into the name.simulation/DATA/PFGS/ directory.

    -

    If the opt.folder.name has been used, the files will be into the +

    A DROUGHT_COMPLETE_TABLE.csv file summarizing information for all +groups into the name.simulation/DATA/PFGS/ directory. +If the opt.folder.name has been used, the files will be into the folder name.simulation/DATA/PFGS/DROUGHT/opt.folder.name/.

    -

    Details

    - +
    +
    +

    Details

    The drought disturbance module is a specific case of the disturbance module. It also allows the user to simulate spatial perturbation(s) that will impact each PFG in terms of resprouting and mortality at different response stages, but with specific rules to determine when the PFG is affected (see -PRE_FATE.params_globalParameters).

    +PRE_FATE.params_globalParameters).

    Several parameters, given within mat.PFG.dist or mat.PFG.tol, are required for each PFG in order to set up these responses. The explanations are the same than those that can be found in -PRE_FATE.params_PFGdisturbance function. Therefore, +PRE_FATE.params_PFGdisturbance function. Therefore, only parameters whose values or descriptions change are detailed below :

    -
    -
    nameDist

    a string to choose the concerned drought - disturbance :
    immediate or delayed

    +
    nameDist
    +

    a string to choose the concerned drought + disturbance :
    immediate or delayed

    + -
    (strategy_tol)

    a string to choose the response to - drought strategy :
    herbs_cham_1, herbs_cham_2, +

    (strategy_tol)
    +

    a string to choose the response to + drought strategy :
    herbs_cham_1, herbs_cham_2, herbs_cham_3, trees_1, trees_2, trees_3 -

    - -
    +

    -

    These values will allow to calculate or define a set of characteristics for +

    These values will allow to calculate or define a set of characteristics for each PFG :

    -
    -
    FATES

    = proportion of killed and resprouting individuals
    - = for each disturbance and for each response stage

    - Two methods to define these tolerances are available : -

      -
    • from predefined scenarios (using - strategy_tol) :

        -
      • the values give the percentage of killed or resprouting +

        FATES
        +

        = proportion of killed and resprouting individuals
        + = for each disturbance and for each response stage

        + Two methods to define these tolerances are available :

        • from predefined scenarios (using + strategy_tol) :

          • the values give the percentage of killed or resprouting individuals

          • with 1, 2, 3, 4: response classes

          • with K: killed individuals, R: resprouting - individuals

          • -

          -

          | ___1___ | ___2___ | ___3___ | ___4___ |

          -

          | _K_ _R_ | _K_ _R_ | _K_ _R_ | _K_ _R_ |

          -

          ________________IMMEDIATE________________

          -
          herbs_cham_1

          | 10% _0_ | _0_ _0_ | _0_ _0_ | _0_ _0_ |

          -
          herbs_cham_2

          | 20% _0_ | _0_ _0_ | _0_ _0_ | 10% _0_ |

          -
          herbs_cham_3

          | 40% _0_ | 10% _0_ | 10% _0_ | 20% _0_ |

          -
          trees_1

          | 10% _0_ | _0_ _0_ | _0_ 40% | _0_ 40% |

          -
          trees_2

          | 20% _0_ | _0_ 10% | _0_ 50% | 10% 50% |

          -
          trees_3

          | 40% _0_ | 10% 40% | 10% 80% | 20% 80% |

          -

          _________________DELAYED_________________

          -
          herbs_cham_1

          | _0_ _0_ | _0_ 10% | _0_ 10% | _0_ 10% |

          -
          herbs_cham_2

          | _0_ _0_ | _0_ 10% | _0_ 10% | _0_ 10% |

          -
          herbs_cham_3

          | _0_ _0_ | _0_ 10% | _0_ 10% | _0_ 10% |

          -
          trees_1

          | _0_ _0_ | _0_ 10% | _0_ 40% | _0_ 40% |

          -
          trees_2

          | 10% _0_ | _0_ 40% | _0_ 40% | _0_ 40% |

          -
          trees_3

          | 20% _0_ | 10% 40% | 10% 50% | 10% 50% |

        • -
        • from user data :
          - with the values contained within the responseStage, - killedIndiv and resproutIndiv columns, if provided
          + individuals

        • +

        +

        | ___1___ | ___2___ | ___3___ | ___4___ |

        + +
        +

        | _K_ _R_ | _K_ _R_ | _K_ _R_ | _K_ _R_ |

        + +
        +

        ________________IMMEDIATE________________

        + +
        herbs_cham_1
        +

        | 10% _0_ | _0_ _0_ | _0_ _0_ | _0_ _0_ |

        + +
        herbs_cham_2
        +

        | 20% _0_ | _0_ _0_ | _0_ _0_ | 10% _0_ |

        + +
        herbs_cham_3
        +

        | 40% _0_ | 10% _0_ | 10% _0_ | 20% _0_ |

        + +
        trees_1
        +

        | 10% _0_ | _0_ _0_ | _0_ 40% | _0_ 40% |

        + +
        trees_2
        +

        | 20% _0_ | _0_ 10% | _0_ 50% | 10% 50% |

        + +
        trees_3
        +

        | 40% _0_ | 10% 40% | 10% 80% | 20% 80% |

        + +
        +

        _________________DELAYED_________________

        + +
        herbs_cham_1
        +

        | _0_ _0_ | _0_ 10% | _0_ 10% | _0_ 10% |

        + +
        herbs_cham_2
        +

        | _0_ _0_ | _0_ 10% | _0_ 10% | _0_ 10% |

        + +
        herbs_cham_3
        +

        | _0_ _0_ | _0_ 10% | _0_ 10% | _0_ 10% |

        + +
        trees_1
        +

        | _0_ _0_ | _0_ 10% | _0_ 40% | _0_ 40% |

        + +
        trees_2
        +

        | 10% _0_ | _0_ 40% | _0_ 40% | _0_ 40% |

        + +
        trees_3
        +

        | 20% _0_ | 10% 40% | 10% 50% | 10% 50% |

        + + +
      • +
      • from user data :
        with the values contained within the responseStage, + killedIndiv and resproutIndiv columns, if provided
        The PFG column can contain either the life form (H, C or P) or the PFG name. Both methods can be combined - (but are applied in the order given by the PFG column).

        -

      • -

    + (but are applied in the order given by the PFG column).

    +

    -
    - -

    Supplementary parameters related to drought, given within +

    Supplementary parameters related to drought, given within mat.PFG.drought, are required for each PFG :

    -
    -
    threshold_moderate

    a value corresponding to the threshold below +

    threshold_moderate
    +

    a value corresponding to the threshold below which the PFG will experience moderate drought (on the same scale than threshold_severe and the map given with the DROUGHT_MASK - flag in PRE_FATE.params_globalParameters)

    -
    threshold_severe

    a value corresponding to the threshold below + flag in PRE_FATE.params_globalParameters)

    + +
    threshold_severe
    +

    a value corresponding to the threshold below which the PFG will experience severe drought (on the same scale than threshold_moderate and the map given with the DROUGHT_MASK - flag in PRE_FATE.params_globalParameters). It should be - inferior or equal to threshold_moderate.

    -
    counter_recovery

    an integer corresponding to the number of + flag in PRE_FATE.params_globalParameters). It should be + inferior or equal to threshold_moderate.

    + +
    counter_recovery
    +

    an integer corresponding to the number of years removed from the PFG counter of cumulated consecutive years of drought events, during non-drought years

    -
    counter_sens

    an integer corresponding to the number of + +

    counter_sens
    +

    an integer corresponding to the number of consecutive years of drought the PFG must experience before suffering severe effects due to a severe drought (sensitivity to severe drought)

    -
    counter_cum

    an integer corresponding to the number of + +

    counter_cum
    +

    an integer corresponding to the number of consecutive years of drought the PFG must experience before any subsequent drought event start having severe effects (cumulative drought response). It should be superior or equal to counter_sens.

    -
    (strategy_drou)

    a string to choose the "counter" - strategy :
    herbs, chamaephytes, trees_shrubs -

    -
    +
    (strategy_drou)
    +

    a string to choose the "counter" + strategy :
    herbs, chamaephytes, trees_shrubs +

    -

    These values will allow to define a set of characteristics for each PFG :

    -
    sensitivity to values

    with the THRESHOLD_MODERATE and - THRESHOLD_SEVERE parameters

    -
    sensitivity through time

    with the COUNTER_RECOVERY, - COUNTER_SENS and COUNTER_CUM parameters

    -
    +

    These values will allow to define a set of characteristics for each PFG :

    sensitivity to values
    +

    with the THRESHOLD_MODERATE and + THRESHOLD_SEVERE parameters

    -

    See also

    +
    sensitivity through time
    +

    with the COUNTER_RECOVERY, + COUNTER_SENS and COUNTER_CUM parameters

    - -

    Author

    +
    + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation')
    -PRE_FATE.skeletonDirectory()
    -
    -
    -mat.char = data.frame(PFG = paste0('PFG', 1:6)
    -                      , type = c('C', 'C', 'H', 'H', 'P', 'P')
    -                      , maturity = c(5, 5, 3, 3, 8, 9)
    -                      , longevity = c(12, 200, 25, 4, 110, 70)
    -                      , age_above_150cm = c(1000, 100, 1000, 1000, 10, 12))
    -
    -mat.tol = data.frame(nameDist = 'immediate'
    -                     , PFG = paste0('PFG', 1:6)
    -                     , strategy_tol = c('herbs_cham_1', 'herbs_cham_2'
    -                                        , 'herbs_cham_2', 'herbs_cham_3'
    -                                        , 'trees_1', 'trees_3'))
    -
    -mat.drought = data.frame(PFG = paste0('PFG', 1:6)
    -                         , threshold_moderate = c(0.5, 0.2, 1, 1, 0.8, 0.5)
    -                         , threshold_severe = c(0.1, 0.1, 0.8, 0.9, 0.4, 0.2)
    -                         , strategy_drou = c('chamaephytes', 'trees_shrubs', 'herbs'
    -                                             , 'herbs', 'trees_shrubs', 'trees_shrubs'))
    -
    -## Create PFG response to drought parameter files (with PFG characteristics) -----------------
    -PRE_FATE.params_PFGdrought(name.simulation = 'FATE_simulation'
    -                           , mat.PFG.dist = mat.char
    -                           , mat.PFG.tol = mat.tol
    -                           , mat.PFG.drought = mat.drought)
    -
    -
    -## Create PFG response to drought parameter files (with all values) --------------------------
    -mat.tol = expand.grid(responseStage = 1:3
    -                      , PFG = paste0('PFG', 1:6)
    -                      , nameDist = 'delayed')
    -mat.tol$breakAge = c(1, 4, 10
    -                     , 1, 4, 10
    -                     , 1, 2, 50
    -                     , 1, 2, 20
    -                     , 2, 6, 95
    -                     , 3, 8, 55)
    -mat.tol$resproutAge = c(0, 0, 4
    -                        , 0, 0, 4
    -                        , 0, 0, 2
    -                        , 0, 0, 2
    -                        , 0, 2, 5
    -                        , 0, 4, 7)
    -mat.tol$killedIndiv = c(10, 10, 5
    -                        , 10, 10, 5
    -                        , 10, 10, 5
    -                        , 10, 10, 5
    -                        , 10, 7, 4
    -                        , 10, 6, 3)
    -mat.tol$resproutIndiv = c(0, 0, 5
    -                          , 0, 0, 5
    -                          , 0, 0, 3
    -                          , 0, 0, 3
    -                          , 0, 1, 4
    -                          , 0, 2, 5)
    -str(mat.tol)
    -
    -PRE_FATE.params_PFGdrought(name.simulation = 'FATE_simulation'
    -                           , mat.PFG.tol = mat.tol
    -                           , mat.PFG.drought = mat.drought)
    +    
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation')
    +PRE_FATE.skeletonDirectory()
    +
    +
    +mat.char = data.frame(PFG = paste0('PFG', 1:6)
    +                      , type = c('C', 'C', 'H', 'H', 'P', 'P')
    +                      , maturity = c(5, 5, 3, 3, 8, 9)
    +                      , longevity = c(12, 200, 25, 4, 110, 70)
    +                      , age_above_150cm = c(1000, 100, 1000, 1000, 10, 12))
    +
    +mat.tol = data.frame(nameDist = 'immediate'
    +                     , PFG = paste0('PFG', 1:6)
    +                     , strategy_tol = c('herbs_cham_1', 'herbs_cham_2'
    +                                        , 'herbs_cham_2', 'herbs_cham_3'
    +                                        , 'trees_1', 'trees_3'))
    +
    +mat.drought = data.frame(PFG = paste0('PFG', 1:6)
    +                         , threshold_moderate = c(0.5, 0.2, 1, 1, 0.8, 0.5)
    +                         , threshold_severe = c(0.1, 0.1, 0.8, 0.9, 0.4, 0.2)
    +                         , strategy_drou = c('chamaephytes', 'trees_shrubs', 'herbs'
    +                                             , 'herbs', 'trees_shrubs', 'trees_shrubs'))
    +
    +## Create PFG response to drought parameter files (with PFG characteristics) -----------------
    +PRE_FATE.params_PFGdrought(name.simulation = 'FATE_simulation'
    +                           , mat.PFG.dist = mat.char
    +                           , mat.PFG.tol = mat.tol
    +                           , mat.PFG.drought = mat.drought)
    +
    +
    +## Create PFG response to drought parameter files (with all values) --------------------------
    +mat.tol = expand.grid(responseStage = 1:3
    +                      , PFG = paste0('PFG', 1:6)
    +                      , nameDist = 'delayed')
    +mat.tol$breakAge = c(1, 4, 10
    +                     , 1, 4, 10
    +                     , 1, 2, 50
    +                     , 1, 2, 20
    +                     , 2, 6, 95
    +                     , 3, 8, 55)
    +mat.tol$resproutAge = c(0, 0, 4
    +                        , 0, 0, 4
    +                        , 0, 0, 2
    +                        , 0, 0, 2
    +                        , 0, 2, 5
    +                        , 0, 4, 7)
    +mat.tol$killedIndiv = c(10, 10, 5
    +                        , 10, 10, 5
    +                        , 10, 10, 5
    +                        , 10, 10, 5
    +                        , 10, 7, 4
    +                        , 10, 6, 3)
    +mat.tol$resproutIndiv = c(0, 0, 5
    +                          , 0, 0, 5
    +                          , 0, 0, 3
    +                          , 0, 0, 3
    +                          , 0, 1, 4
    +                          , 0, 2, 5)
    +str(mat.tol)
    +
    +PRE_FATE.params_PFGdrought(name.simulation = 'FATE_simulation'
    +                           , mat.PFG.tol = mat.tol
    +                           , mat.PFG.drought = mat.drought)
                                                             
                                                             
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/PRE_FATE.params_PFGlight.html b/docs/reference/PRE_FATE.params_PFGlight.html index 16c09d1..5e0e91a 100644 --- a/docs/reference/PRE_FATE.params_PFGlight.html +++ b/docs/reference/PRE_FATE.params_PFGlight.html @@ -1,69 +1,14 @@ - - - - - - - -Create LIGHT parameter files for a FATE simulation — PRE_FATE.params_PFGlight • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create LIGHT parameter files for a FATE simulation — PRE_FATE.params_PFGlight • RFate - - - - - - - - - - - + + - - -
    -
    - -
    - -
    +
    @@ -208,325 +142,388 @@

    Create LIGHT parameter files for a FATE simulation

    FATE
    .

    -
    PRE_FATE.params_PFGlight(
    -  name.simulation,
    -  mat.PFG.light,
    -  mat.PFG.tol = NULL,
    -  opt.folder.name = NULL
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    mat.PFG.light

    a data.frame with 2 to 6 columns :

      -
    • PFG,

    • +
      +
      PRE_FATE.params_PFGlight(
      +  name.simulation,
      +  mat.PFG.light,
      +  mat.PFG.tol = NULL,
      +  opt.folder.name = NULL
      +)
      +
      + +
      +

      Arguments

      +
      name.simulation
      +

      a string corresponding to the main directory +or simulation name of the FATE simulation

      +
      mat.PFG.light
      +

      a data.frame with 2 to 6 columns :

      • PFG,

      • type (or shade_factor)

      • type, (or active_germ_low, active_germ_medium, active_germ_high) (or strategy_ag)

      • type, light_need

      • -

      (see Details)

    mat.PFG.tol

    (optional)
    -a data.frame with 2 to 4 columns :

      -
    • PFG,

    • +

    (see Details)

    +
    mat.PFG.tol
    +

    (optional)
    +a data.frame with 2 to 4 columns :

    • PFG,

    • lifeStage, resources, tolerance (or strategy_tol)

    • -

    (see Details)

    opt.folder.name

    (optional)
    a string corresponding +

    (see Details)

    +
    opt.folder.name
    +

    (optional)
    a string corresponding to the name of the folder that will be created into the -name.simulation/DATA/PFGS/LIGHT/ directory to store the results

    - -

    Value

    - +name.simulation/DATA/PFGS/LIGHT/ directory to store the results

    +
    +
    +

    Value

    A .txt file per PFG into the name.simulation/DATA/PFGS/LIGHT/ directory with the following -parameters :

    -
    -
    NAME

    name of the PFG

    -
    LIGHT

    light value or strategy of the PFG

    -
    SHADE_FACTOR

    index of shade quantity to weight PFG abundance and +parameters :

    NAME
    +

    name of the PFG

    + +
    LIGHT
    +

    light value or strategy of the PFG

    + +
    SHADE_FACTOR
    +

    index of shade quantity to weight PFG abundance and transform it into light resources

    -
    ACTIVE_GERM

    germination rates depending on light conditions -
    (from 0 to 10, corresponding to 0 to 100%)

    -
    LIGHT_TOL

    light tolerance table (in a single row).
    + +

    ACTIVE_GERM
    +

    germination rates depending on light conditions +
    (from 0 to 10, corresponding to 0 to 100%)

    + +
    LIGHT_TOL
    +

    light tolerance table (in a single row).
    This is a vector of 9 numbers corresponding to the ability of the PFG to - survive or not : -

      -
    • at different life stages (Germinant (Ge), Immature + survive or not :

      • at different life stages (Germinant (Ge), Immature (Im), Mature (Ma))

      • under different light conditions (Low (L), Medium (M) or High (H)).

      • -
      - These parameters should be given in this order : GeL, GeM, GeH, ImL, +

    These parameters should be given in this order : GeL, GeM, GeH, ImL, ImM, ImH, MaL, MaM, MaH -
    (from 0 to 10, corresponding to 0 to 100%). -

    +
    (from 0 to 10, corresponding to 0 to 100%). +

    -
    -

    A LIGHT_COMPLETE_TABLE.csv file summarizing information for all -groups into the name.simulation/DATA/PFGS/ directory.

    -

    If the opt.folder.name has been used, the files will be into the +

    A LIGHT_COMPLETE_TABLE.csv file summarizing information for all +groups into the name.simulation/DATA/PFGS/ directory. +If the opt.folder.name has been used, the files will be into the folder name.simulation/DATA/PFGS/LIGHT/opt.folder.name/.

    -

    Details

    - +
    +
    +

    Details

    The light module allows the user to add the effect of -light interaction within a primary vegetation succession.

    +light interaction within a primary vegetation succession.

    Several parameters, given within mat.PFG.light or mat.PFG.tol, are required for each PFG in order to set up this light interaction :

    -
    -
    PFG

    the concerned plant functional group

    +
    PFG
    +

    the concerned plant functional group

    + -
    type

    or life-form, based on Raunkier.
    It should be either +

    type
    +

    or life-form, based on Raunkier.
    It should be either H (herbaceous), C (chamaephyte) or P (phanerophyte) - for now

    + for now

    + -
    (shade_factor)

    an integer between 1 and +

    (shade_factor)
    +

    an integer between 1 and Inf corresponding to an index of shade quantity to weight PFG abundance and transform it into light resources (e.g. if two PFG have shade factors of 1 and 5 respectively, for the same abundances, the second PFG will produce 5 times more shade than the first - one)

    + one
    )

    + -
    (active_germ_low)

    an integer between 0 and +

    (active_germ_low)
    +

    an integer between 0 and 10 corresponding to the proportion of seeds that will germinate for Low light condition

    -
    (active_germ_medium)

    an integer between 0 and + +

    (active_germ_medium)
    +

    an integer between 0 and 10 corresponding to the proportion of seeds that will germinate for Medium light condition

    -
    (active_germ_high)

    an integer between 0 and + +

    (active_germ_high)
    +

    an integer between 0 and 10 corresponding to the proportion of seeds that will germinate for High light condition

    -
    (strategy_ag)

    a string to choose the germination - strategy :
    light_lover, indifferent, shade_lover -

    + +
    (strategy_ag)
    +

    a string to choose the germination + strategy :
    light_lover, indifferent, shade_lover +

    + -
    (light_need)

    an integer between 0 and 5 +

    (light_need)
    +

    an integer between 0 and 5 corresponding to the light preference of the PFG (e.g. from Flora - Indicativa)

    + Indicativa)

    + -
    lifeStage

    the concerned life stage (Germinant, +

    lifeStage
    +

    the concerned life stage (Germinant, Immature, Mature)

    -
    resources

    the concerned light condition (Low, + +

    resources
    +

    the concerned light condition (Low, Medium, High)

    -
    tolerance

    an integer between 0 and 10 + +

    tolerance
    +

    an integer between 0 and 10 corresponding to the proportion of surviving individuals

    -
    (strategy_tol)

    a string to choose the tolerance - strategy :
    full_light, pioneer, ubiquist, - semi_shade, undergrowth

    -
    +
    (strategy_tol)
    +

    a string to choose the tolerance + strategy :
    full_light, pioneer, ubiquist, + semi_shade, undergrowth

    -

    These values will allow to calculate or define a set of characteristics for +

    These values will allow to calculate or define a set of characteristics for each PFG :

    -
    -
    SHADE_FACTOR

    index of shade quantity to weight PFG abundance and - transform it into light resources

    - Two methods to define these proportions are available : -

      -
    • from predefined rules (using type) :

        -
      • for H (herbaceous) : 1

      • +
        SHADE_FACTOR
        +

        index of shade quantity to weight PFG abundance and + transform it into light resources

        + Two methods to define these proportions are available :

        • from predefined rules (using type) :

          • for H (herbaceous) : 1

          • for C (chamaephyte) : 5

          • for P (phanerophyte) : 20

        • -
        • from user data :
          - with the values contained within the shade_factor column, - if provided

        • -

        - -
        ACTIVE_GERM

        proportion of seeds that will germinate for each light - condition (Low, Medium, High)

        - Three methods to define these proportions are available : -

          -
        • from predefined scenarios (using strategy_ag) :

          -

          | _L_ _M_ _H_ |

          -

          _______________

          -
          light_lover

          | 50% 80% 90% |

          -
          indifferent

          | 90% 90% 90% |

          -
          shade_lover

          | 90% 80% 50% |

        • -
        • from predefined rules (using type) :

            -
          • for H (herbaceous) : 50%, 80%, 90%

          • +
          • from user data :
            with the values contained within the shade_factor column, + if provided

          • +

        + + +
        ACTIVE_GERM
        +

        proportion of seeds that will germinate for each light + condition (Low, Medium, High)

        + Three methods to define these proportions are available :

        • from predefined scenarios (using strategy_ag) :

          +

          | _L_ _M_ _H_ |

          + +
          +

          _______________

          + +
          light_lover
          +

          | 50% 80% 90% |

          + +
          indifferent
          +

          | 90% 90% 90% |

          + +
          shade_lover
          +

          | 90% 80% 50% |

          + + +
        • +
        • from predefined rules (using type) :

          • for H (herbaceous) : 50%, 80%, 90%

          • for C (chamaephyte) or P (phanerophyte): 90%, 90%, 90%

        • -
        • from user data :
          - with the values contained within the active_germ_low, +

        • from user data :
          with the values contained within the active_germ_low, active_germ_medium and active_germ_high columns, if - provided

        • -

        - -
        LIGHT_TOL

        defined for each life stage (Germinant, - Immature, Mature)
        and each soil condition (Low, - Medium, High)

        - Three methods to define these tolerances are available : -

          -
        • from predefined scenarios (using - strategy_tol) :

            -
          • . means Not tolerant, 1 means + provided

          • +

        + + +
        LIGHT_TOL
        +

        defined for each life stage (Germinant, + Immature, Mature)
        and each soil condition (Low, + Medium, High)

        + Three methods to define these tolerances are available :

        • from predefined scenarios (using + strategy_tol) :

          • . means Not tolerant, 1 means Tolerant (100%)

          • with g: Germinant, i: Immature, m: Mature

          • with L: low light, M: medium light, H: - high light

          • -

          -

          | _ g _ | _ i _ | _ m _ |

          -

          | L M H | L M H | L M H |

          -

          _________________________

          -
          full_light

          | 1 1 1 | . . 1 | . . 1 |

          -
          pioneer

          | 1 1 1 | . 1 1 | . 1 1 |

          -
          ubiquist

          | 1 1 1 | 1 1 1 | 1 1 1 |

          -
          semi_shade

          | 1 1 . | 1 1 . | 1 1 1 |

          -
          undergrowth

          | 1 1 . | 1 1 . | 1 1 . |

        • + high light

          +

        +

        | _ g _ | _ i _ | _ m _ |

        + +
        +

        | L M H | L M H | L M H |

        + +
        +

        _________________________

        + +
        full_light
        +

        | 1 1 1 | . . 1 | . . 1 |

        + +
        pioneer
        +

        | 1 1 1 | . 1 1 | . 1 1 |

        + +
        ubiquist
        +

        | 1 1 1 | 1 1 1 | 1 1 1 |

        + +
        semi_shade
        +

        | 1 1 . | 1 1 . | 1 1 1 |

        + +
        undergrowth
        +

        | 1 1 . | 1 1 . | 1 1 . |

        + + +
      • from predefined rules (using type and - light_need):

        -
        (A)

        PFG are tolerant to Low light if light <= 2

        -
        (A)

        PFG are tolerant to Medium light if + light_need):

        (A)
        +

        PFG are tolerant to Low light if light <= 2

        + +
        (A)
        +

        PFG are tolerant to Medium light if 2 <= light <= 4

        -
        (A)

        PFG are tolerant to High light if + +

        (A)
        +

        PFG are tolerant to High light if light >= 3

        -
        (B)

        all germinants are assumed to be tolerant to Low + +

        (B)
        +

        all germinants are assumed to be tolerant to Low light

        -
        (C)

        all mature trees or chamaephytes are assumed to be + +

        (C)
        +

        all mature trees or chamaephytes are assumed to be tolerant to Medium and High light conditions

        -
        (D)

        all immature trees that grow in the + +

        (D)
        +

        all immature trees that grow in the penultimate stratum are assumed to be tolerant to High light - !! desactivated !!

          -
        • . means Not tolerant

        • + !! desactivated !!

        + + +

        • . means Not tolerant

        • A, B, C, D mean Tolerant according to one of the rule defined above

        • with g: Germinant, i: Immature, m: Mature

        • with L: low light, M: medium light, H: - high light

        • -

        -

        | _ g _ | _ i _ | _ m _ |

        -

        | L M H | L M H | L M H |

        -

        _________________________

        -
        1

        | A . . | A . D | A C C |

        -
        2

        | A A . | A A D | A A C |

        -
        3

        | B A . | . A D | . A C |

        -
        4

        | B A A | . A A | . A A |

        -
        5

        | B . A | . . A | . C A |

      • -
      • from user data :
        - with the values contained within the lifeStage, - resources and tolerance columns, if provided

      • -

    + high light

    +

    +

    | _ g _ | _ i _ | _ m _ |

    + +
    +

    | L M H | L M H | L M H |

    + +
    +

    _________________________

    + +
    1
    +

    | A . . | A . D | A C C |

    -
    +
    2
    +

    | A A . | A A D | A A C |

    -

    See also

    +
    3
    +

    | B A . | . A D | . A C |

    - -

    Author

    +
    4
    +

    | B A A | . A A | . A A |

    +
    5
    +

    | B . A | . . A | . C A |

    + + +
    +
  • from user data :
    with the values contained within the lifeStage, + resources and tolerance columns, if provided

  • +

    + + +
    + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation')
    -PRE_FATE.skeletonDirectory()
    +    
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation')
    +PRE_FATE.skeletonDirectory()
     
     
    -## Create PFG light parameter files (with strategies) ----------------------------------------
    -mat.ag = data.frame(PFG = paste0('PFG', 1:6)
    -                    , type = c('C', 'C', 'H', 'H', 'P', 'P')
    -                    , strategy_ag = c('shade_lover', 'indifferent'
    -                                      , 'indifferent', 'shade_lover'
    -                                      , 'light_lover', 'light_lover'))
    +## Create PFG light parameter files (with strategies) ----------------------------------------
    +mat.ag = data.frame(PFG = paste0('PFG', 1:6)
    +                    , type = c('C', 'C', 'H', 'H', 'P', 'P')
    +                    , strategy_ag = c('shade_lover', 'indifferent'
    +                                      , 'indifferent', 'shade_lover'
    +                                      , 'light_lover', 'light_lover'))
     
    -mat.tol = data.frame(PFG = paste0('PFG', 1:6)
    -                     , strategy_tol = c('undergrowth', 'ubiquist'
    -                                        , 'ubiquist', 'semi_shade'
    -                                        , 'pioneer', 'full_light'))
    +mat.tol = data.frame(PFG = paste0('PFG', 1:6)
    +                     , strategy_tol = c('undergrowth', 'ubiquist'
    +                                        , 'ubiquist', 'semi_shade'
    +                                        , 'pioneer', 'full_light'))
     
    -PRE_FATE.params_PFGlight(name.simulation = 'FATE_simulation'
    -                         , mat.PFG.light = mat.ag
    -                         , mat.PFG.tol = mat.tol)
    +PRE_FATE.params_PFGlight(name.simulation = 'FATE_simulation'
    +                         , mat.PFG.light = mat.ag
    +                         , mat.PFG.tol = mat.tol)
                                                             
     
    -## Create PFG light parameter files (with all values) ----------------------------------------
    -mat.ag = data.frame(PFG = paste0('PFG', 1:6)
    -                    , shade_factor = c(5, 3, 1, 1, 12, 18)
    -                    , active_germ_low = c(9, 8, 8, 8, 5, 5)
    -                    , active_germ_medium = rep(8, 6)
    -                    , active_germ_high = c(4, 8, 8, 5, 9, 9))
    -
    -mat.tol = expand.grid(resources = c('Low', 'Medium', 'High')
    -                      , lifeStage = c('Germinant', 'Immature', 'Mature')
    -                      , PFG = paste0('PFG', 1:6))
    -mat.tol$tolerance = c(1, 1, 0, 1, 0, 0, 1, 0, 0
    -                      , rep(1, 9)
    -                      , rep(1, 9)
    -                      , 1, 1, 1, 1, 1, 1, 1, 0, 0
    -                      , 1, 1, 1, 0, 1, 1, 0, 0, 1
    -                      , 1, 1, 1, 0, 0, 1, 0, 0, 1)
    -
    -PRE_FATE.params_PFGlight(name.simulation = 'FATE_simulation'
    -                         , mat.PFG.light = mat.ag
    -                         , mat.PFG.tol = mat.tol)
    +## Create PFG light parameter files (with all values) ----------------------------------------
    +mat.ag = data.frame(PFG = paste0('PFG', 1:6)
    +                    , shade_factor = c(5, 3, 1, 1, 12, 18)
    +                    , active_germ_low = c(9, 8, 8, 8, 5, 5)
    +                    , active_germ_medium = rep(8, 6)
    +                    , active_germ_high = c(4, 8, 8, 5, 9, 9))
    +
    +mat.tol = expand.grid(resources = c('Low', 'Medium', 'High')
    +                      , lifeStage = c('Germinant', 'Immature', 'Mature')
    +                      , PFG = paste0('PFG', 1:6))
    +mat.tol$tolerance = c(1, 1, 0, 1, 0, 0, 1, 0, 0
    +                      , rep(1, 9)
    +                      , rep(1, 9)
    +                      , 1, 1, 1, 1, 1, 1, 1, 0, 0
    +                      , 1, 1, 1, 0, 1, 1, 0, 0, 1
    +                      , 1, 1, 1, 0, 0, 1, 0, 0, 1)
    +
    +PRE_FATE.params_PFGlight(name.simulation = 'FATE_simulation'
    +                         , mat.PFG.light = mat.ag
    +                         , mat.PFG.tol = mat.tol)
                                                             
                                                             
    -## -------------------------------------------------------------------------------------------
    +## -------------------------------------------------------------------------------------------
     
    -## Load example data
    -Champsaur_params = .loadData('Champsaur_params', 'RData')
    +## Load example data
    +Champsaur_params = .loadData('Champsaur_params', 'RData')
     
    -## Create a skeleton folder
    -PRE_FATE.skeletonDirectory(name.simulation = 'FATE_Champsaur')
    +## Create a skeleton folder
    +PRE_FATE.skeletonDirectory(name.simulation = 'FATE_Champsaur')
     
     
    -## PFG traits for light
    -tab.light = Champsaur_params$tab.LIGHT
    -str(tab.light)
    +## PFG traits for light
    +tab.light = Champsaur_params$tab.LIGHT
    +str(tab.light)
     
    -## Create PFG light parameter files ----------------------------------------------------------
    -PRE_FATE.params_PFGlight(name.simulation = 'FATE_Champsaur'
    -                         , mat.PFG.light = tab.light[, c('PFG', 'type')]
    -                         , mat.PFG.tol = tab.light[, c('PFG', 'strategy_tol')])
    +## Create PFG light parameter files ----------------------------------------------------------
    +PRE_FATE.params_PFGlight(name.simulation = 'FATE_Champsaur'
    +                         , mat.PFG.light = tab.light[, c('PFG', 'type')]
    +                         , mat.PFG.tol = tab.light[, c('PFG', 'strategy_tol')])
     
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/PRE_FATE.params_PFGsoil.html b/docs/reference/PRE_FATE.params_PFGsoil.html index 480b86d..7913736 100644 --- a/docs/reference/PRE_FATE.params_PFGsoil.html +++ b/docs/reference/PRE_FATE.params_PFGsoil.html @@ -1,69 +1,14 @@ - - - - - - - -Create SOIL parameter files for a FATE simulation — PRE_FATE.params_PFGsoil • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create SOIL parameter files for a FATE simulation — PRE_FATE.params_PFGsoil • RFate - - - - - - - - - - - - - + + -
    -
    - -
    - -
    +
    @@ -208,311 +142,376 @@

    Create SOIL parameter files for a FATE simulation

    used in the soil module of FATE.

    -
    PRE_FATE.params_PFGsoil(
    -  name.simulation,
    -  mat.PFG.soil,
    -  mat.PFG.tol = NULL,
    -  opt.folder.name = NULL
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    mat.PFG.soil

    a data.frame with 3 to 7 columns :

      -
    • PFG,

    • +
      +
      PRE_FATE.params_PFGsoil(
      +  name.simulation,
      +  mat.PFG.soil,
      +  mat.PFG.tol = NULL,
      +  opt.folder.name = NULL
      +)
      +
      + +
      +

      Arguments

      +
      name.simulation
      +

      a string corresponding to the main directory +or simulation name of the FATE simulation

      +
      mat.PFG.soil
      +

      a data.frame with 3 to 7 columns :

      • PFG,

      • type, (or active_germ_low, active_germ_medium, active_germ_high) (or strategy_ag)

      • soil_contrib, soil_tol_min, soil_tol_max (or strategy_contrib)

      • -

      (see Details)

    mat.PFG.tol

    (optional)
    -a data.frame with 2 to 4 columns :

      -
    • PFG,

    • +

    (see Details)

    +
    mat.PFG.tol
    +

    (optional)
    +a data.frame with 2 to 4 columns :

    • PFG,

    • lifeStage, resources, tolerance (or strategy_tol)

    • -

    (see Details)

    opt.folder.name

    (optional)
    a string corresponding +

    (see Details)

    +
    opt.folder.name
    +

    (optional)
    a string corresponding to the name of the folder that will be created into the -name.simulation/DATA/PFGS/SOIL/ directory to store the results

    - -

    Value

    - +name.simulation/DATA/PFGS/SOIL/ directory to store the results

    +
    +
    +

    Value

    A .txt file per PFG into the name.simulation/DATA/PFGS/SOIL/ directory with the following -parameters :

    -
    -
    NAME

    name of the PFG

    -
    ACTIVE_GERM

    germination rates depending on soil conditions -
    (from 0 to 10, corresponding to 0 to 100%)

    -
    SOIL_CONTRIB

    contribution to the soil value of the pixel

    -
    SOIL_LOW

    lower value of soil supported by the PFG,
    +parameters :

    NAME
    +

    name of the PFG

    + +
    ACTIVE_GERM
    +

    germination rates depending on soil conditions +
    (from 0 to 10, corresponding to 0 to 100%)

    + +
    SOIL_CONTRIB
    +

    contribution to the soil value of the pixel

    + +
    SOIL_LOW
    +

    lower value of soil supported by the PFG,
    defining the limit between Low and Medium soil resources for this PFG

    -
    SOIL_HIGH

    upper value of soil supported by the PFG,
    + +

    SOIL_HIGH
    +

    upper value of soil supported by the PFG,
    defining the limit between Medium and High soil resources for this PFG

    -
    SOIL_TOL

    soil tolerance table (in a single row).
    + +

    SOIL_TOL
    +

    soil tolerance table (in a single row).
    This is a vector of 9 numbers corresponding to the ability of the PFG to - survive or not : -

      -
    • at different life stages (Germinant (Ge), Immature + survive or not :

      • at different life stages (Germinant (Ge), Immature (Im), Mature (Ma))

      • under different soil conditions (Low (L), Medium (M) or High (H)).

      • -
      - These parameters should be given in this order : GeL, GeM, GeH, ImL, +

    These parameters should be given in this order : GeL, GeM, GeH, ImL, ImM, ImH, MaL, MaM, MaH -
    (from 0 to 10, corresponding to 0 to 100%). -

    +
    (from 0 to 10, corresponding to 0 to 100%). +

    -
    -

    A SOIL_COMPLETE_TABLE.csv file summarizing information for all -groups into the name.simulation/DATA/PFGS/ directory.

    -

    If the opt.folder.name has been used, the files will be into the +

    A SOIL_COMPLETE_TABLE.csv file summarizing information for all +groups into the name.simulation/DATA/PFGS/ directory. +If the opt.folder.name has been used, the files will be into the folder name.simulation/DATA/PFGS/SOIL/opt.folder.name/.

    -

    Details

    - +
    +
    +

    Details

    The soil module allows the user to add the effect of -soil interaction within a primary vegetation succession.

    +soil interaction within a primary vegetation succession.

    Several parameters, given within mat.PFG.soil or mat.PFG.tol, are required for each PFG in order to set up the soil interaction :

    -
    -
    PFG

    the concerned plant functional group

    +
    PFG
    +

    the concerned plant functional group

    + -
    type

    or life-form, based on Raunkier.
    It should be either +

    type
    +

    or life-form, based on Raunkier.
    It should be either H (herbaceous), C (chamaephyte) or P (phanerophyte) for now

    -
    (active_germ_low)

    an integer between 0 and + +

    (active_germ_low)
    +

    an integer between 0 and 10 corresponding to the proportion of seeds that will germinate for Low soil condition

    -
    (active_germ_medium)

    an integer between 0 and + +

    (active_germ_medium)
    +

    an integer between 0 and 10 corresponding to the proportion of seeds that will germinate for Medium soil condition

    -
    (active_germ_high)

    an integer between 0 and + +

    (active_germ_high)
    +

    an integer between 0 and 10 corresponding to the proportion of seeds that will germinate for High soil condition

    -
    (strategy_ag)

    a string to choose the germination - strategy :
    poor_lover, indifferent, rich_lover -

    + +
    (strategy_ag)
    +

    a string to choose the germination + strategy :
    poor_lover, indifferent, rich_lover +

    + -
    soil_contrib

    a value corresponding to the PFG preference for soil - fertility
    (e.g. nitrogen value from Ellenberg or Flora Indicativa)

    -
    soil_tol_min

    the minimum soil value tolerated by the PFG (on the +

    soil_contrib
    +

    a value corresponding to the PFG preference for soil + fertility
    (e.g. nitrogen value from Ellenberg or Flora Indicativa)

    + +
    soil_tol_min
    +

    the minimum soil value tolerated by the PFG (on the same scale than soil_contrib)

    -
    soil_tol_max

    the maximum soil value tolerated by the PFG (on the + +

    soil_tol_max
    +

    the maximum soil value tolerated by the PFG (on the same scale than soil_contrib)

    -
    (strategy_contrib)

    a string to choose the - contribution strategy :
    oligotrophic, mesotrophic, - eutrophic

    + +
    (strategy_contrib)
    +

    a string to choose the + contribution strategy :
    oligotrophic, mesotrophic, + eutrophic

    + -
    lifeStage

    the concerned life stage (Germinant, +

    lifeStage
    +

    the concerned life stage (Germinant, Immature, Mature)

    -
    resources

    the concerned soil condition (Low, + +

    resources
    +

    the concerned soil condition (Low, Medium, High)

    -
    tolerance

    an integer between 0 and 10 + +

    tolerance
    +

    an integer between 0 and 10 corresponding to the proportion of surviving individuals

    -
    (strategy_tol)

    a string to choose the tolerance - strategy :
    poor_lover, ubiquist, rich_lover -

    -
    +
    (strategy_tol)
    +

    a string to choose the tolerance + strategy :
    poor_lover, ubiquist, rich_lover +

    -

    These values will allow to calculate or define a set of characteristics for + +

    These values will allow to calculate or define a set of characteristics for each PFG :

    -
    -
    ACTIVE_GERM

    proportion of seeds that will germinate for each soil - condition (Low, Medium, High)

    - Three methods to define these proportions are available : -

      -
    • from predefined scenarios (using strategy_ag) :

      -

      | _L_ _M_ _H_ |

      -

      _______________

      -
      poor_lover

      | 80% 90% 50% |

      -
      indifferent

      | 90% 90% 90% |

      -
      rich_lover

      | 50% 90% 80% |

    • -
    • from predefined rules (using type) :

        -
      • for H (herbaceous) : 80%, 100%, 50%

      • +
        ACTIVE_GERM
        +

        proportion of seeds that will germinate for each soil + condition (Low, Medium, High)

        + Three methods to define these proportions are available :

        • from predefined scenarios (using strategy_ag) :

          +

          | _L_ _M_ _H_ |

          + +
          +

          _______________

          + +
          poor_lover
          +

          | 80% 90% 50% |

          + +
          indifferent
          +

          | 90% 90% 90% |

          + +
          rich_lover
          +

          | 50% 90% 80% |

          + + +
        • +
        • from predefined rules (using type) :

          • for H (herbaceous) : 80%, 100%, 50%

          • for C (chamaephyte) or P (phanerophyte) : 90%, 100%, 90%

        • -
        • from user data :
          - with the values contained within the active_germ_low, +

        • from user data :
          with the values contained within the active_germ_low, active_germ_medium and active_germ_high columns, if - provided

        • -

        + provided

        +

    + -
    SOIL_CONTRIB
    SOIL_LOW
    SOIL_HIGH

    Two methods to define these values are available : -

      -
    • from predefined scenarios (using - strategy_contrib) :

        -
      • the values give the soil_tol_min, soil_contrib +

        SOIL_CONTRIB
        SOIL_LOW
        SOIL_HIGH
        +

        Two methods to define these values are available :

        • from predefined scenarios (using + strategy_contrib) :

          • the values give the soil_tol_min, soil_contrib and soil_tol_max

          • with L: low soil, M: medium soil, H: - high soil

          • -

          -

          | ___ L ___ | ___ M ___ | ___ H ___ |

          -

          _____________________________________

          -
          oligotrophic

          __________ 1 ___ 1.5 ___ 2 __________

          -
          mesotrophic

          __________ 1.5 _ 2.5 _ 4.5 __________

          -
          eutrophic

          __________ 3 ____ 4 ____ 5 __________

        • -
        • from user data :
          - with the values contained within the soil_contrib, - soil_tol_min and soil_tol_max columns, if provided

        • -

        + high soil

      • +

      +

      | ___ L ___ | ___ M ___ | ___ H ___ |

      + +
      +

      _____________________________________

      + +
      oligotrophic
      +

      __________ 1 ___ 1.5 ___ 2 __________

      + +
      mesotrophic
      +

      __________ 1.5 _ 2.5 _ 4.5 __________

      + +
      eutrophic
      +

      __________ 3 ____ 4 ____ 5 __________

      + + +
    • +
    • from user data :
      with the values contained within the soil_contrib, + soil_tol_min and soil_tol_max columns, if provided

    • +

    + -
    SOIL_TOL

    defined for each life stage (Germinant, - Immature, Mature)
    and each soil condition (Low, - Medium, High)

    - Three methods to define these tolerances are available : -

      -
    • from predefined scenarios (using - strategy_tol) :

        -
      • the values give the percentage of surviving individuals to the +

        SOIL_TOL
        +

        defined for each life stage (Germinant, + Immature, Mature)
        and each soil condition (Low, + Medium, High)

        + Three methods to define these tolerances are available :

        • from predefined scenarios (using + strategy_tol) :

          • the values give the percentage of surviving individuals to the concerned conditions

          • with g: Germinant, i: Immature, m: Mature

          • with L: low soil, M: medium soil, H: - high soil

          • -

          -

          | ___ g ___ | ___ i ___ | ___ m ___ |

          -

          | _L _M_ H_ | _L _M_ H_ | _L _M_ H_ |

          -

          _____________________________________

          -
          poor_lover

          | 30 100 10 | 60 100 40 | 90 100 70 |

          -
          ubiquist

          | 90 100 80 | 90 100 80 | 90 100 80 |

          -
          rich_lover

          | 10 100 30 | 40 100 60 | 70 100 90 |

        • + high soil

          +

        +

        | ___ g ___ | ___ i ___ | ___ m ___ |

        + +
        +

        | _L _M_ H_ | _L _M_ H_ | _L _M_ H_ |

        + +
        +

        _____________________________________

        + +
        poor_lover
        +

        | 30 100 10 | 60 100 40 | 90 100 70 |

        + +
        ubiquist
        +

        | 90 100 80 | 90 100 80 | 90 100 80 |

        + +
        rich_lover
        +

        | 10 100 30 | 40 100 60 | 70 100 90 |

        + + +
      • from predefined rules (corresponding to the - poor_lover strategy) :

        -
        (A)

        germinants are severely impacted by wrong soil conditions

        -
        (B)

        immatures are half impacted by wrong soil conditions

        -
        (C)

        matures are little impacted by wrong soil conditions

        -
        (D)

        for all life stages, not enough is better than too much

          -
        • the values give the percentage of surviving individuals to the + poor_lover strategy) :

          (A)
          +

          germinants are severely impacted by wrong soil conditions

          + +
          (B)
          +

          immatures are half impacted by wrong soil conditions

          + +
          (C)
          +

          matures are little impacted by wrong soil conditions

          + +
          (D)
          +

          for all life stages, not enough is better than too much

          + + +

          • the values give the percentage of surviving individuals to the concerned conditions

          • with g: Germinant, i: Immature, m: Mature

          • with L: low soil, M: medium soil, H: - high soil

          • -

          -

          | ___ g ___ | ___ i ___ | ___ m ___ |

          -

          | _L _M_ H_ | _L _M_ H_ | _L _M_ H_ |

          -

          _____________________________________

          -

          | 30 100 10 | 60 100 40 | 90 100 70 |

        • -
        • from user data :
          - with the values contained within the lifeStage, - resources and tolerance columns, if provided

        • -

    + high soil

    +

    +

    | ___ g ___ | ___ i ___ | ___ m ___ |

    + +
    +

    | _L _M_ H_ | _L _M_ H_ | _L _M_ H_ |

    + +
    +

    _____________________________________

    -
    +
    +

    | 30 100 10 | 60 100 40 | 90 100 70 |

    -

    See also

    + +
    +
  • from user data :
    with the values contained within the lifeStage, + resources and tolerance columns, if provided

  • +

    - -

    Author

    +
    + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    +    
    +

    Examples

    +
    
                                                         
    -## Create a skeleton folder with the default name ('FATE_simulation')
    -PRE_FATE.skeletonDirectory()
    -
    -## Create PFG soil parameter files (with strategies) -----------------------------------------
    -tab.soil = data.frame(PFG = paste0('PFG', 1:6)
    -                      , strategy_ag = c('rich_lover', 'indifferent' , 'indifferent'
    -                                        , 'rich_lover', 'indifferent', 'poor_lover')
    -                      , strategy_contrib = c('eutrophic', 'mesotrophic', 'mesotrophic'
    -                                             , 'mesotrophic', 'mesotrophic', 'oligotrophic'))
    -tab.tol = data.frame(PFG = paste0('PFG', 1:6)
    -                     , strategy_tol = c('rich_lover', 'ubiquist', 'poor_lover'
    -                                        , 'ubiquist', 'poor_lover', 'poor_lover'))
    -
    -PRE_FATE.params_PFGsoil(name.simulation = 'FATE_simulation'
    -                        , mat.PFG.soil = tab.soil
    -                        , mat.PFG.tol = tab.tol)
    +## Create a skeleton folder with the default name ('FATE_simulation')
    +PRE_FATE.skeletonDirectory()
    +
    +## Create PFG soil parameter files (with strategies) -----------------------------------------
    +tab.soil = data.frame(PFG = paste0('PFG', 1:6)
    +                      , strategy_ag = c('rich_lover', 'indifferent' , 'indifferent'
    +                                        , 'rich_lover', 'indifferent', 'poor_lover')
    +                      , strategy_contrib = c('eutrophic', 'mesotrophic', 'mesotrophic'
    +                                             , 'mesotrophic', 'mesotrophic', 'oligotrophic'))
    +tab.tol = data.frame(PFG = paste0('PFG', 1:6)
    +                     , strategy_tol = c('rich_lover', 'ubiquist', 'poor_lover'
    +                                        , 'ubiquist', 'poor_lover', 'poor_lover'))
    +
    +PRE_FATE.params_PFGsoil(name.simulation = 'FATE_simulation'
    +                        , mat.PFG.soil = tab.soil
    +                        , mat.PFG.tol = tab.tol)
                                                             
     
    -## Create PFG soil parameter files (with all values) -----------------------------------------
    -tab.soil = data.frame(PFG = paste0('PFG', 1:6)
    -                      , active_germ_low = c(5, 8, 8, 6, 8, 8)
    -                      , active_germ_medium = rep(9, 6)
    -                      , active_germ_high = c(9, 8, 8, 9, 8, 4)
    -                      , strategy_contrib = c('eutrophic', 'mesotrophic', 'mesotrophic'
    -                                             , 'mesotrophic', 'mesotrophic', 'oligotrophic'))
    -tab.tol = expand.grid(resources = c('Low', 'Medium', 'High')
    -                      , lifeStage = c('Germinant', 'Immature', 'Mature')
    -                      , PFG = paste0('PFG', 1:6))
    -tab.tol$tolerance = c(8, 8, 4, 8, 5, 4, 9, 4, 4
    -                      , rep(9, 9)
    -                      , rep(9, 9)
    -                      , 8, 8, 6, 8, 6, 6, 9, 5, 5
    -                      , 8, 8, 8, 5, 6, 9, 3, 4, 9
    -                      , 8, 8, 8, 5, 5, 9, 5, 5, 9)
    -
    -PRE_FATE.params_PFGsoil(name.simulation = 'FATE_simulation'
    -                        , mat.PFG.soil = tab.soil
    -                        , mat.PFG.tol = tab.tol)
    +## Create PFG soil parameter files (with all values) -----------------------------------------
    +tab.soil = data.frame(PFG = paste0('PFG', 1:6)
    +                      , active_germ_low = c(5, 8, 8, 6, 8, 8)
    +                      , active_germ_medium = rep(9, 6)
    +                      , active_germ_high = c(9, 8, 8, 9, 8, 4)
    +                      , strategy_contrib = c('eutrophic', 'mesotrophic', 'mesotrophic'
    +                                             , 'mesotrophic', 'mesotrophic', 'oligotrophic'))
    +tab.tol = expand.grid(resources = c('Low', 'Medium', 'High')
    +                      , lifeStage = c('Germinant', 'Immature', 'Mature')
    +                      , PFG = paste0('PFG', 1:6))
    +tab.tol$tolerance = c(8, 8, 4, 8, 5, 4, 9, 4, 4
    +                      , rep(9, 9)
    +                      , rep(9, 9)
    +                      , 8, 8, 6, 8, 6, 6, 9, 5, 5
    +                      , 8, 8, 8, 5, 6, 9, 3, 4, 9
    +                      , 8, 8, 8, 5, 5, 9, 5, 5, 9)
    +
    +PRE_FATE.params_PFGsoil(name.simulation = 'FATE_simulation'
    +                        , mat.PFG.soil = tab.soil
    +                        , mat.PFG.tol = tab.tol)
                                                             
                                                             
    -## -------------------------------------------------------------------------------------------
    +## -------------------------------------------------------------------------------------------
     
    -## Load example data
    -Champsaur_params = .loadData('Champsaur_params', 'RData')
    +## Load example data
    +Champsaur_params = .loadData('Champsaur_params', 'RData')
     
    -## Create a skeleton folder
    -PRE_FATE.skeletonDirectory(name.simulation = 'FATE_Champsaur')
    +## Create a skeleton folder
    +PRE_FATE.skeletonDirectory(name.simulation = 'FATE_Champsaur')
     
     
    -## PFG traits for light
    -tab.soil = Champsaur_params$tab.SOIL
    -str(tab.soil)
    +## PFG traits for light
    +tab.soil = Champsaur_params$tab.SOIL
    +str(tab.soil)
     
    -## Create PFG soil parameter files -----------------------------------------------------------
    -PRE_FATE.params_PFGsoil(name.simulation = 'FATE_Champsaur'
    -                           , mat.PFG.soil = tab.soil)
    +## Create PFG soil parameter files -----------------------------------------------------------
    +PRE_FATE.params_PFGsoil(name.simulation = 'FATE_Champsaur'
    +                           , mat.PFG.soil = tab.soil)
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/PRE_FATE.params_PFGsuccession.html b/docs/reference/PRE_FATE.params_PFGsuccession.html index d960545..5d60d61 100644 --- a/docs/reference/PRE_FATE.params_PFGsuccession.html +++ b/docs/reference/PRE_FATE.params_PFGsuccession.html @@ -1,69 +1,14 @@ - - - - - - - -Create SUCCESSION parameter files for a FATE simulation — PRE_FATE.params_PFGsuccession • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create SUCCESSION parameter files for a FATE simulation — PRE_FATE.params_PFGsuccession • RFate - - - - - - - - - - + + - - - -
    -
    - -
    - -
    +
    @@ -208,289 +142,300 @@

    Create SUCCESSION parameter files for a FATE simulatio core module of FATE.

    -
    PRE_FATE.params_PFGsuccession(
    -  name.simulation,
    -  mat.PFG.succ,
    -  strata.limits = c(0, 20, 50, 150, 400, 1000, 2000, 5000, 10000),
    -  strata.limits_reduce = TRUE,
    -  opt.folder.name = NULL
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    mat.PFG.succ

    a data.frame with at least 5 columns :
    -PFG, type, height, maturity, longevity -
    (and optionally, max_abundance, potential_fecundity, +

    +
    PRE_FATE.params_PFGsuccession(
    +  name.simulation,
    +  mat.PFG.succ,
    +  strata.limits = c(0, 20, 50, 150, 400, 1000, 2000, 5000, 10000),
    +  strata.limits_reduce = TRUE,
    +  opt.folder.name = NULL
    +)
    +
    + +
    +

    Arguments

    +
    name.simulation
    +

    a string corresponding to the main directory +or simulation name of the FATE simulation

    +
    mat.PFG.succ
    +

    a data.frame with at least 5 columns :
    PFG, type, height, maturity, longevity +
    (and optionally, max_abundance, potential_fecundity, immature_size, is_alien, flammability) -
    (see Details)

    strata.limits

    a vector of integer containing values -among which height strata limits will be chosen

    strata.limits_reduce

    default TRUE.
    If TRUE, stratum +
    (see Details)

    +
    strata.limits
    +

    a vector of integer containing values +among which height strata limits will be chosen

    +
    strata.limits_reduce
    +

    default TRUE.
    If TRUE, stratum height limits are checked to try and bring several PFGs together in a same -stratum

    opt.folder.name

    (optional)
    a string corresponding +stratum

    +
    opt.folder.name
    +

    (optional)
    a string corresponding to the name of the folder that will be created into the -name.simulation/DATA/PFGS/SUCC/ directory to store the results

    - -

    Value

    - +name.simulation/DATA/PFGS/SUCC/ directory to store the results

    +
    +
    +

    Value

    A .txt file per PFG into the name.simulation/DATA/PFGS/SUCC/ directory with the following -parameters :

    -
    -
    NAME

    name of the PFG

    -
    TYPE

    PFG life-form (H: herbaceous C: +parameters :

    NAME
    +

    name of the PFG

    + +
    TYPE
    +

    PFG life-form (H: herbaceous C: chamaephyte P: phanerophyte)

    -
    HEIGHT

    PFG maximum height (in cm)

    -
    MATURITY

    PFG maturity age (in years)

    -
    LONGEVITY

    PFG life span (in years)

    -
    MAX_STRATUM

    maximum height stratum that the PFG can reach

    -
    MAX_ABUNDANCE

    maximum abundance / space (quantitative) that the - PFG is able to produce / occupy
    (1: Low 2: + +

    HEIGHT
    +

    PFG maximum height (in cm)

    + +
    MATURITY
    +

    PFG maturity age (in years)

    + +
    LONGEVITY
    +

    PFG life span (in years)

    + +
    MAX_STRATUM
    +

    maximum height stratum that the PFG can reach

    + +
    MAX_ABUNDANCE
    +

    maximum abundance / space (quantitative) that the + PFG is able to produce / occupy
    (1: Low 2: Medium 3: High)

    -
    IMM_SIZE

    PFG immature relative size (from 0 to + +

    IMM_SIZE
    +

    PFG immature relative size (from 0 to 10, corresponding to 0 to 100%)

    -
    CHANG_STR_AGES

    ages at which the PFG goes in the upper stratum -
    (in years, put a value higher than the PFG life span if it is + +

    CHANG_STR_AGES
    +

    ages at which the PFG goes in the upper stratum +
    (in years, put a value higher than the PFG life span if it is not supposed to rise a stratum)

    -
    SEED_POOL_LIFE

    maximum number of years seeds are able to survive + +

    SEED_POOL_LIFE
    +

    maximum number of years seeds are able to survive (for active and dormant pool)

    -
    SEED_DORMANCY

    are the seeds dormant or not (0: No + +

    SEED_DORMANCY
    +

    are the seeds dormant or not (0: No 1: Yes)

    -
    POTENTIAL_
    FECUNDITY

    maximum number of seeds produced by the + +

    POTENTIAL_
    FECUNDITY
    +

    maximum number of seeds produced by the PFG

    -
    IS_ALIEN

    is the PFG an alien or not (0: No 1: + +

    IS_ALIEN
    +

    is the PFG an alien or not (0: No 1: Yes)

    -
    FLAMMABILITY

    how easily the PFG burns (numeric) -

    -
    +
    FLAMMABILITY
    +

    how easily the PFG burns (numeric) +

    -

    A SUCC_COMPLETE_TABLE.csv file summarizing information for all -groups into the name.simulation/DATA/PFGS/ directory.
    -This file can be used to parameterize the disturbance files (see -PRE_FATE.params_PFGdisturbance).

    -

    If the opt.folder.name has been used, the files will be into the -folder name.simulation/DATA/PFGS/SUCC/opt.folder.name/.

    -

    Details

    +

    A SUCC_COMPLETE_TABLE.csv file summarizing information for all +groups into the name.simulation/DATA/PFGS/ directory.
    This file can be used to parameterize the disturbance files (see +PRE_FATE.params_PFGdisturbance). +If the opt.folder.name has been used, the files will be into the +folder name.simulation/DATA/PFGS/SUCC/opt.folder.name/.

    +
    +
    +

    Details

    The core module of FATE allows the user to simulate a -primary vegetation succession based on a demography model.

    +primary vegetation succession based on a demography model.

    Several parameters, given within mat.PFG.succ, are required for each PFG in order to set up this life cycle :

    -
    -
    type

    or life-form, based on Raunkier.
    It should be either +

    type
    +

    or life-form, based on Raunkier.
    It should be either H (herbaceous), C (chamaephyte) or P (phanerophyte) for now

    -
    height

    the maximum or average height that reach the PFG

    -
    maturity

    the age from which the PFG can reproduce

    -
    longevity

    the maximum or average lifespan of the PFG

    -
    (max_abundance)

    the maximum abundance of mature PFG

    -
    (potential_fecundity)

    the maximum number of seeds produced - by the PFG
    (otherwise the value is given within the global parameter - file, see PRE_FATE.params_globalParameters)

    -
    (immature_size)

    the relative size of immature versus mature + +

    height
    +

    the maximum or average height that reach the PFG

    + +
    maturity
    +

    the age from which the PFG can reproduce

    + +
    longevity
    +

    the maximum or average lifespan of the PFG

    + +
    (max_abundance)
    +

    the maximum abundance of mature PFG

    + +
    (potential_fecundity)
    +

    the maximum number of seeds produced + by the PFG
    (otherwise the value is given within the global parameter + file, see PRE_FATE.params_globalParameters)

    + +
    (immature_size)
    +

    the relative size of immature versus mature plants

    -
    (is_alien)

    if the PFG is to be considered as an alien + +

    (is_alien)
    +

    if the PFG is to be considered as an alien (1) or not (0)

    -
    (flammability)

    how easily the PFG burns

    -
    +
    (flammability)
    +

    how easily the PFG burns

    -

    These values will allow to calculate or define a set of characteristics for +

    These values will allow to calculate or define a set of characteristics for all PFG :

    -
    -
    STRATA_LIMITS

    = height values that define each stratum.

    - Two methods to define these limits are available : -

      -
    • from predefined rules (using strata.limits_reduce - = TRUE, strata.limits, height) :

        -
      • limits should go exponentially and are selected among +

        STRATA_LIMITS
        +

        = height values that define each stratum.

        + Two methods to define these limits are available :

        • from predefined rules (using strata.limits_reduce + = TRUE, strata.limits, height) :

          • limits should go exponentially and are selected among strata.limits

          • PFG are separated according to these strata.limits and then grouped making sure to have $$\text{number of PFG per stratum} \geq \sqrt{\text{total number of PFG}} - 2$$ - to try to homogenize the number of PFG within each stratum.

          • + to try to homogenize the number of PFG within each stratum.

        • from user data : (using strata.limits_reduce = - FALSE)
          - with the values contained within the strata.limits - column, if provided

        • -

        + FALSE)
        with the values contained within the strata.limits + column, if provided

      • +

    -
    -

    and a set of characteristics for each PFG :

    -
    -
    MAX_STRATUM

    = maximum stratum that each PFG can reach

    -
    MAX_ABUNDANCE

    = maximum abundance of mature PFG
    +

    and a set of characteristics for each PFG :

    +
    MAX_STRATUM
    +

    = maximum stratum that each PFG can reach

    + +
    MAX_ABUNDANCE
    +

    = maximum abundance of mature PFG
    = It can be seen as a proxy of maximum carrying capacity for mature - individuals
    (and therefore as a broad proxy of the amount + individuals
    (and therefore as a broad proxy of the amount of space a PFG can occupy within a pixel (herbaceous should be more - numerous than phanerophytes).

    - Two methods to define these abundances are available : -

      -
    • from predefined rules (using type, - MAX_STRATUM) :

      - - - - -
      MAX_STRATUM123+
      H (herbaceous)3322
      C (chamaephyte)3221
      P (phanerophyte)3211
      -
    • -
    • from user data :
      - with the values contained within the max_abundance - column, if provided

    • -

    -
    IMM_SIZE

    = relative size of immature versus mature plants
    + numerous than phanerophytes
    ).

    + Two methods to define these abundances are available :

    • from predefined rules (using type, + MAX_STRATUM) :

      MAX_STRATUM123+
      H (herbaceous)3322
      C (chamaephyte)3221
      P (phanerophyte)3211
    • +
    • from user data :
      with the values contained within the max_abundance + column, if provided

    • +

    + +
    IMM_SIZE
    +

    = relative size of immature versus mature plants
    = for example, immature herbaceous take as much space as mature herbaceous, while immature phanerophytes take less space (and - contribute to shade half less) than mature individuals

    - Two methods to define these sizes are available : -

      -
    • from predefined rules (using type, MAX_STRATUM) - :

      -

      - - - - -
      MAX_STRATUM123+
      H (herbaceous)100%80%50%50%
      C (chamaephyte)100%50%50%50%
      P (phanerophyte)50%50%50%10%
      -
    • -
    • from user data :
      - with the values contained within the immature_size - column, if provided

    • -

    -
    CHANG_STR_AGES

    = at what age each PFG goes into the upper stratum -

    It is defined using a logistic growth curve with 2 points to - parameterize it : -

      -
    1. at \(age = \text{maturity}/2\), \(height = \text{IMM_SIZE} * \text{height}\)

    2. -
    3. at \(age = \text{longevity}\), \(height = \text{height}\)

    4. -

    -
    POTENTIAL_FECUNDITY

    = maximum number of seeds produced by the PFG -

    - Two methods to define this number are available : -

      -
    • from predefined rules : same value for all PFG, given - within the global parameter file
      (see - PRE_FATE.params_globalParameters)

    • -
    • from user data :
      - with the values contained within the potential_fecundity - column, if provided

    • -

    -
    IS_ALIEN

    = if the PFG is to be considered as an alien (1) or + contribute to shade half less) than mature individuals

    + Two methods to define these sizes are available :

    • from predefined rules (using type, MAX_STRATUM) + :

      +

      MAX_STRATUM123+
      H (herbaceous)100%80%50%50%
      C (chamaephyte)100%50%50%50%
      P (phanerophyte)50%50%50%10%
    • +
    • from user data :
      with the values contained within the immature_size + column, if provided

    • +

    + +
    CHANG_STR_AGES
    +

    = at what age each PFG goes into the upper stratum +

    It is defined using a logistic growth curve with 2 points to + parameterize it :

    1. at \(age = \text{maturity}/2\), \(height = \text{IMM_SIZE} * \text{height}\)

    2. +
    3. at \(age = \text{longevity}\), \(height = \text{height}\)

    4. +

    + +
    POTENTIAL_FECUNDITY
    +

    = maximum number of seeds produced by the PFG +

    + Two methods to define this number are available :

    • from predefined rules : same value for all PFG, given + within the global parameter file
      (see + PRE_FATE.params_globalParameters)

    • +
    • from user data :
      with the values contained within the potential_fecundity + column, if provided

    • +

    + +
    IS_ALIEN
    +

    = if the PFG is to be considered as an alien (1) or not (0)

    -
    FLAMMABILITY

    = how easily the PFG burns

    - -
    -

    See also

    +
    FLAMMABILITY
    +

    = how easily the PFG burns

    - -

    Author

    +
    + +
    +

    Author

    Isabelle Boulangeat, Damien Georges, Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation')
    -PRE_FATE.skeletonDirectory()
    -
    -## Create PFG succession parameter files -----------------------------------------------------
    -PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_simulation'
    -                              , mat.PFG.succ = data.frame(PFG = paste0('PFG', 1:6)
    -                                                          , type = c('C', 'C', 'H', 'H', 'P', 'P')
    -                                                          , height = c(10, 250, 36, 68, 1250, 550)
    -                                                          , maturity = c(5, 5, 3, 3, 8, 9)
    -                                                          , longevity = c(12, 200, 25, 4, 110, 70)))
    +    
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation')
    +PRE_FATE.skeletonDirectory()
    +
    +## Create PFG succession parameter files -----------------------------------------------------
    +PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_simulation'
    +                              , mat.PFG.succ = data.frame(PFG = paste0('PFG', 1:6)
    +                                                          , type = c('C', 'C', 'H', 'H', 'P', 'P')
    +                                                          , height = c(10, 250, 36, 68, 1250, 550)
    +                                                          , maturity = c(5, 5, 3, 3, 8, 9)
    +                                                          , longevity = c(12, 200, 25, 4, 110, 70)))
                                                             
     
    -## Create PFG succession parameter files (with immature_size) --------------------------------
    -PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_simulation'
    -                              , mat.PFG.succ = data.frame(PFG = paste0('PFG', 1:6)
    -                                                          , type = c('C', 'C', 'H', 'H', 'P', 'P')
    -                                                          , height = c(10, 250, 36, 68, 1250, 550)
    -                                                          , maturity = c(5, 5, 3, 3, 8, 9)
    -                                                          , longevity = c(12, 200, 25, 4, 110, 70)
    -                                                          , immature_size = c(10, 8, 10, 10, 1, 5)))
    +## Create PFG succession parameter files (with immature_size) --------------------------------
    +PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_simulation'
    +                              , mat.PFG.succ = data.frame(PFG = paste0('PFG', 1:6)
    +                                                          , type = c('C', 'C', 'H', 'H', 'P', 'P')
    +                                                          , height = c(10, 250, 36, 68, 1250, 550)
    +                                                          , maturity = c(5, 5, 3, 3, 8, 9)
    +                                                          , longevity = c(12, 200, 25, 4, 110, 70)
    +                                                          , immature_size = c(10, 8, 10, 10, 1, 5)))
                                                             
                                                             
    -## -------------------------------------------------------------------------------------------
    +## -------------------------------------------------------------------------------------------
     
     
    -## Load example data
    -Champsaur_params = .loadData('Champsaur_params', 'RData')
    +## Load example data
    +Champsaur_params = .loadData('Champsaur_params', 'RData')
     
    -## Create a skeleton folder
    -PRE_FATE.skeletonDirectory(name.simulation = 'FATE_Champsaur')
    +## Create a skeleton folder
    +PRE_FATE.skeletonDirectory(name.simulation = 'FATE_Champsaur')
     
     
     
    -## PFG traits for succession
    -tab.succ = Champsaur_params$tab.SUCC
    -str(tab.succ)
    +## PFG traits for succession
    +tab.succ = Champsaur_params$tab.SUCC
    +str(tab.succ)
     
    -## Create PFG succession parameter files -----------------------------------------------------
    -PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_Champsaur'
    -                           , mat.PFG.succ = tab.succ)
    +## Create PFG succession parameter files -----------------------------------------------------
    +PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_Champsaur'
    +                           , mat.PFG.succ = tab.succ)
     
    -## Create PFG succession parameter files (fixing strata limits) ------------------------------
    -PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_Champsaur'
    -                              , mat.PFG.succ = tab.succ
    -                              , strata.limits = c(0, 20, 50, 150, 400, 1000, 2000)
    -                              , strata.limits_reduce = FALSE)
    +## Create PFG succession parameter files (fixing strata limits) ------------------------------
    +PRE_FATE.params_PFGsuccession(name.simulation = 'FATE_Champsaur'
    +                              , mat.PFG.succ = tab.succ
    +                              , strata.limits = c(0, 20, 50, 150, 400, 1000, 2000)
    +                              , strata.limits_reduce = FALSE)
                                                             
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/PRE_FATE.params_changingYears.html b/docs/reference/PRE_FATE.params_changingYears.html index 1f59c41..a9ac74b 100644 --- a/docs/reference/PRE_FATE.params_changingYears.html +++ b/docs/reference/PRE_FATE.params_changingYears.html @@ -1,72 +1,17 @@ - - - - - - - -Create SCENARIO parameter files for a FATE -simulation — PRE_FATE.params_changingYears • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create SCENARIO parameter files for a FATE +simulation — PRE_FATE.params_changingYears • RFate - - - - - - - - - + + - - - - -
    -
    - -
    - -
    +
    @@ -213,25 +147,22 @@

    Create SCENARIO parameter files for a FATE maps to be used.

    -
    PRE_FATE.params_changingYears(
    -  name.simulation,
    -  type.changing,
    -  mat.changing,
    -  opt.folder.name = NULL
    -)
    +
    +
    PRE_FATE.params_changingYears(
    +  name.simulation,
    +  type.changing,
    +  mat.changing,
    +  opt.folder.name = NULL
    +)
    +
    -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    type.changing

    a string to choose the concerned module :

      -
    • MASK (succession),

    • +
      +

      Arguments

      +
      name.simulation
      +

      a string corresponding to the main directory +or simulation name of the FATE simulation

      +
      type.changing
      +

      a string to choose the concerned module :

      • MASK (succession),

      • HABSUIT (habitat suitability),

      • DIST (disturbances),

      • DROUGHT (drought disturbance),

      • @@ -239,175 +170,176 @@

        Arg frequencies)

      • FIRE or FIRE_F (fire disturbance, masks or frequencies)

      • -

    mat.changing

    a data.frame with 3 columns :
    year, -order, new.value

    opt.folder.name

    (optional)
    a string corresponding + +

    mat.changing
    +

    a data.frame with 3 columns :
    year, +order, new.value

    +
    opt.folder.name
    +

    (optional)
    a string corresponding to the name of the folder that will be created into the -name.simulation/DATA/SCENARIO/ directory to store the results

    - -

    Value

    - +name.simulation/DATA/SCENARIO/ directory to store the results

    +
    +
    +

    Value

    Several .txt files into the -name.simulation/DATA/SCENARIO/ :

      -
    • ..type.changing...changingmask_years.txt : one line for each +name.simulation/DATA/SCENARIO/ :

      • ..type.changing...changingmask_years.txt : one line for each simulation year

      • ..type.changing...changingmask_files_t..year...txt : - one line for each new raster file

      • -
      - -

      OR

        -
      • ..type.changing...changingfreq_years.txt : one line for each + one line for each new raster file

      • +

      OR

      • ..type.changing...changingfreq_years.txt : one line for each simulation year

      • ..type.changing...changingfreq_files_t..year...txt : - one line for each new frequency

      • -
      - - -

      If the opt.folder.name has been used, the files will be into the folder + one line for each new frequency

    • +

    If the opt.folder.name has been used, the files will be into the folder name.simulation/DATA/SCENARIO/opt.folder.name/.

    -

    Details

    - +
    +
    +

    Details

    Several modules of the FATE software allow the user to simulate changes over time :

    -
    -
    succession

    the core module is based on a raster mask given +

    succession
    +

    the core module is based on a raster mask given within the Simul_parameters file with the MASK flag (see - PRE_FATE.params_simulParameters), with either + PRE_FATE.params_simulParameters), with either 0 or 1 within each pixel, 1 corresponding to the cells in which the PFG can try to colonize. The available pixels can change through time, to simulate habitat loss (e.g. urbanization) or gain (e.g. glacial retreat).

    -
    habitat suitability

    if this module is activated (see - PRE_FATE.params_globalParameters), PFG colonization depends + +

    habitat suitability
    +

    if this module is activated (see + PRE_FATE.params_globalParameters), PFG colonization depends on maps given for each PFG within the Simul_parameters file with the PFG_HAB_MASK flag (see - PRE_FATE.params_simulParameters).
    These maps must + PRE_FATE.params_simulParameters).
    These maps must contain values between 0 and 1 corresponding to the probability of presence of the PFG in each pixel. These probabilities can change through time, as they often come from Species Distribution Models (SDM) that can be based for example on climatic variables (e.g. simulating regional warming).

    -
    disturbances

    if this module is activated (see - PRE_FATE.params_globalParameters), each disturbance relies + +

    disturbances
    +

    if this module is activated (see + PRE_FATE.params_globalParameters), each disturbance relies on a raster given within the Simul_parameters file with the - DIST_MASK flag (see PRE_FATE.params_simulParameters). -
    As for succession, this mask is filled with either 0 + DIST_MASK flag (see PRE_FATE.params_simulParameters). +
    As for succession, this mask is filled with either 0 or 1 to define where the perturbation occurs. The impacted pixels can also change through time (e.g. change in forestry practices, expansion of grazing areas, etc).

    -
    drought disturbance

    if this module is activated (see - PRE_FATE.params_globalParameters), drought disturbance + +

    drought disturbance
    +

    if this module is activated (see + PRE_FATE.params_globalParameters), drought disturbance relies on a raster given within the Simul_parameters file with the DROUGHT_MASK flag (see - PRE_FATE.params_simulParameters). -
    This map contains values defining the drought intensity experienced by + PRE_FATE.params_simulParameters). +
    This map contains values defining the drought intensity experienced by the area. This intensity can change through time and space (e.g. regional warming, extreme years, change in agriculture practices that can leave a place more exposed, etc).

    -
    aliens introduction

    if this module is activated (see - PRE_FATE.params_globalParameters), aliens introduction + +

    aliens introduction
    +

    if this module is activated (see + PRE_FATE.params_globalParameters), aliens introduction depends on maps given for each alien within the Simul_parameters file with the PFG_ALIENS_MASK flag (see - PRE_FATE.params_simulParameters). -
    As for succession, these masks are filled with either 0 + PRE_FATE.params_simulParameters). +
    As for succession, these masks are filled with either 0 or 1 to define where the introductions occur. The impacted pixels can also change through time (e.g. colonization, eradication campaign, etc), as well as the frequencies of introduction (see ALIENS_FREQ - flag in PRE_FATE.params_globalParameters).

    + flag in PRE_FATE.params_globalParameters).

    + -
    fire disturbance

    if this module is activated (see - PRE_FATE.params_globalParameters), fire disturbance +

    fire disturbance
    +

    if this module is activated (see + PRE_FATE.params_globalParameters), fire disturbance can rely on a raster given within the Simul_parameters file with the FIRE_MASK flag (see - PRE_FATE.params_simulParameters).
    + PRE_FATE.params_simulParameters).
    As for succession, this mask is filled with either 0 or 1 to define where the perturbation occurs. The impacted pixels can also change through time (e.g. change in forestry practices, expansion of drought events, etc), as well as the frequencies of perturbations (see - FIRE_FREQ flag in PRE_FATE.params_globalParameters). -

    + FIRE_FREQ flag in PRE_FATE.params_globalParameters). +

    -
    -

    Several parameters, given within mat.changing, are required to set up +

    Several parameters, given within mat.changing, are required to set up these temporal changes :

    -
    -
    year

    all simulation years at which the raster files of a specific +

    year
    +

    all simulation years at which the raster files of a specific module (succession MASK, habitat suitability HABSUIT, disturbance DIST, drought DROUGHT, aliens introduction ALIENS or ALIENS_F, fire FIRE or FIRE_F) will be changed

    -
    new.value

    the names of the new raster files for each year of - change. It can be either .img or .tif.
    + +

    new.value
    +

    the names of the new raster files for each year of + change. It can be either .img or .tif.
    There is an exception if ALIENS_F or FIRE_F is selected : the values should be integer representing the frequencies of aliens introduction or fire perturbations.

    -
    order

    an integer associated to each new map in order to - always give the raster maps in the same order throughout the years

    -
    +
    order
    +

    an integer associated to each new map in order to + always give the raster maps in the same order throughout the years

    -

    See also

    - - -

    Author

    +
    + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation') ------------------------
    -PRE_FATE.skeletonDirectory()
    -
    -tab.changing = data.frame(year = c(50,50,80,80)
    -                          , order = c(1,2,1,2)
    -                          , new.value = c('FATE_simulation/DATA/MASK/MASK_DIST1_50.tif'
    -                                          , 'FATE_simulation/DATA/MASK/MASK_DIST2_50.tif'
    -                                          , 'FATE_simulation/DATA/MASK/MASK_DIST1_80.tif'
    -                                          , 'FATE_simulation/DATA/MASK/MASK_DIST2_80.tif'))
    -
    -## Create a Changing_times parameter file ----------------------------------------------------
    -PRE_FATE.params_changingYears(name.simulation = 'FATE_simulation'
    -                              , type.changing = 'DIST'
    -                              , mat.changing = tab.changing)
    -
    -
    +
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation') ------------------------
    +PRE_FATE.skeletonDirectory()
    +
    +tab.changing = data.frame(year = c(50,50,80,80)
    +                          , order = c(1,2,1,2)
    +                          , new.value = c('FATE_simulation/DATA/MASK/MASK_DIST1_50.tif'
    +                                          , 'FATE_simulation/DATA/MASK/MASK_DIST2_50.tif'
    +                                          , 'FATE_simulation/DATA/MASK/MASK_DIST1_80.tif'
    +                                          , 'FATE_simulation/DATA/MASK/MASK_DIST2_80.tif'))
    +
    +## Create a Changing_times parameter file ----------------------------------------------------
    +PRE_FATE.params_changingYears(name.simulation = 'FATE_simulation'
    +                              , type.changing = 'DIST'
    +                              , mat.changing = tab.changing)
    +
    +
    +
    +
    - - - + + diff --git a/docs/reference/PRE_FATE.params_globalParameters.html b/docs/reference/PRE_FATE.params_globalParameters.html index 020c0cd..9b83c09 100644 --- a/docs/reference/PRE_FATE.params_globalParameters.html +++ b/docs/reference/PRE_FATE.params_globalParameters.html @@ -20,7 +20,7 @@ RFate - 1.0.3 + 1.1.0 @@ -32,7 +32,7 @@ +

    If the opt.folder.name has been used, the files will be into the folder name.simulation/DATA/SAVE/opt.folder.name/.

    -

    Details

    - + +
    +

    Details

    FATE software allows the user to save two different types of outputs :

    -
    -
    Raster maps

    PFG abundance maps can be saved for all specified - simulation years.
    It includes maps per PFG per strata +

    Raster maps
    +

    PFG abundance maps can be saved for all specified + simulation years.
    It includes maps per PFG per strata (ABUND_perPFG_perStrata folder) and summary maps per PFG for all height strata combined (ABUND_perPFG_allStrata - folder).
    If the light and / or soil modules are activated (see - PRE_FATE.params_globalParameters), maps for light and / or - soil resources are also saved.
    Raster format used is depending on + folder).
    If the light and / or soil modules are activated (see + PRE_FATE.params_globalParameters), maps for light and / or + soil resources are also saved.
    Raster format used is depending on input data format. It can be either .img or .tif.

    -
    Model objects

    using BOOST library and its serialization + +

    Model objects
    +

    using BOOST library and its serialization functions, FATE is able to save a simulation at a specific time. This object allows the user to restart a simulation from this precise state by specifying its name within the Simul_parameters file with the SAVED_STATE flag (see - PRE_FATE.params_simulParameters).

    - -
    - -

    See also

    + PRE_FATE.params_simulParameters).

    - -

    Author

    +
    + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation') ------------------------
    -PRE_FATE.skeletonDirectory()
    +    
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation') ------------------------
    +PRE_FATE.skeletonDirectory()
     
    -## Create a SAVE_year_maps or/and SAVE_year_objects parameter file ---------------------------
    -PRE_FATE.params_savingYears(name.simulation = 'FATE_simulation'
    -                            , years.maps = c(100, 150, 200)
    -                            , years.objects = 200)
    +## Create a SAVE_year_maps or/and SAVE_year_objects parameter file ---------------------------
    +PRE_FATE.params_savingYears(name.simulation = 'FATE_simulation'
    +                            , years.maps = c(100, 150, 200)
    +                            , years.objects = 200)
     
    -
    +
    + + - - - + + diff --git a/docs/reference/PRE_FATE.params_simulParameters.html b/docs/reference/PRE_FATE.params_simulParameters.html index 892030f..8983352 100644 --- a/docs/reference/PRE_FATE.params_simulParameters.html +++ b/docs/reference/PRE_FATE.params_simulParameters.html @@ -1,70 +1,15 @@ - - - - - - - -Create Simul_parameters parameter file for a FATE -simulation — PRE_FATE.params_simulParameters • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create Simul_parameters parameter file for a FATE +simulation — PRE_FATE.params_simulParameters • RFate - - - - + + -
    -
    - -
    - -
    +
    @@ -209,123 +143,95 @@

    Create Simul_parameters parameter file for a FATE file containing PARAMETER FILENAMES used in FATE model.

    -
    PRE_FATE.params_simulParameters(
    -  name.simulation,
    -  name.MASK,
    -  name.SAVED_STATE = NULL,
    -  name.DIST = NULL,
    -  name.DROUGHT = NULL,
    -  name.ALIENS = NULL,
    -  name.FIRE = NULL,
    -  name.ELEVATION = NULL,
    -  name.SLOPE = NULL,
    -  opt.global.name = NULL,
    -  opt.folder.name = NULL
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    name.MASK

    a string corresponding to the file name of a raster +

    +
    PRE_FATE.params_simulParameters(
    +  name.simulation,
    +  name.MASK,
    +  name.SAVED_STATE = NULL,
    +  name.DIST = NULL,
    +  name.DROUGHT = NULL,
    +  name.ALIENS = NULL,
    +  name.FIRE = NULL,
    +  name.ELEVATION = NULL,
    +  name.SLOPE = NULL,
    +  opt.global.name = NULL,
    +  opt.folder.name = NULL
    +)
    +
    + +
    +

    Arguments

    +
    name.simulation
    +

    a string corresponding to the main directory +or simulation name of the FATE simulation

    +
    name.MASK
    +

    a string corresponding to the file name of a raster mask, with either 0 or 1 within each pixel, 1 corresponding to the cells of the studied area in which the succession (core) module of the FATE simulation will take place (see -PRE_FATE.params_globalParameters)

    name.SAVED_STATE

    (optional)
    a string corresponding +PRE_FATE.params_globalParameters)

    +
    name.SAVED_STATE
    +

    (optional)
    a string corresponding to the file name of a FATE object, obtained from a previous -simulation and from which to restart this new simulation

    name.DIST

    (optional)
    a string corresponding to the +simulation and from which to restart this new simulation

    +
    name.DIST
    +

    (optional)
    a string corresponding to the file name of a raster mask, with either 0 or 1 within each pixel, 1 corresponding to the cells of the studied area in which the disturbance module of the FATE simulation will take place (see -PRE_FATE.params_globalParameters)

    name.DROUGHT

    (optional)
    a string corresponding to +PRE_FATE.params_globalParameters)

    +
    name.DROUGHT
    +

    (optional)
    a string corresponding to the name of a raster file, with a numeric value within each pixel corresponding to the drought intensity experienced by this pixel through the drought (or fire) disturbance module of the FATE simulation (see -PRE_FATE.params_globalParameters)

    name.ALIENS

    (optional)
    a string corresponding to the +PRE_FATE.params_globalParameters)

    +
    name.ALIENS
    +

    (optional)
    a string corresponding to the file name of a raster mask, with either 0 or 1 within each pixel, 1 corresponding to the cells of the studied area in which the aliens introduction module of the FATE simulation will take place (see -PRE_FATE.params_globalParameters)

    name.FIRE

    (optional)
    a string corresponding to the +PRE_FATE.params_globalParameters)

    +
    name.FIRE
    +

    (optional)
    a string corresponding to the file name of a raster mask, with either 0 or 1 within each pixel, 1 corresponding to the cells of the studied area in which the fire disturbance module of the FATE simulation will take place (see -PRE_FATE.params_globalParameters)

    name.ELEVATION

    (optional)
    a string corresponding to +PRE_FATE.params_globalParameters)

    +
    name.ELEVATION
    +

    (optional)
    a string corresponding to the name of a raster file, with a numeric value within each pixel corresponding to the elevation of this pixel and used by the fire disturbance module of the FATE simulation (see -PRE_FATE.params_globalParameters)

    name.SLOPE

    (optional)
    a string corresponding to +PRE_FATE.params_globalParameters)

    +
    name.SLOPE
    +

    (optional)
    a string corresponding to the name of a raster file, with a numeric value within each pixel corresponding to the slope of this pixel and used by the fire disturbance module of the FATE simulation (see -PRE_FATE.params_globalParameters)

    opt.global.name

    (optional)
    a string corresponding +PRE_FATE.params_globalParameters)

    +
    opt.global.name
    +

    (optional)
    a string corresponding to the name of the global parameter file in the folder name.simulation/DATA/GLOBAL_PARAMETERS/ that will be used to build -the simulation parameter file

    opt.folder.name

    (optional)
    a string corresponding +the simulation parameter file

    +
    opt.folder.name
    +

    (optional)
    a string corresponding to the name of the folder in each name.simulation/DATA/PFGS/module/ from which PFG file names will be extracted to build the simulation -parameter file

    - -

    Value

    - -

    A .txt file into the name.simulation/PARAM_SIMUL/ -directory with the following parameters :

    -
      -
    • --GLOBAL_PARAMS--

    • -
    • --SAVING_DIR--

        -
      • --SAVED_STATE-- (optional)

      • +parameter file

        +
    +
    +

    Value

    +

    A .txt file into the name.simulation/PARAM_SIMUL/directory with the following parameters :

    • --GLOBAL_PARAMS--

    • +
    • --SAVING_DIR--

      • --SAVED_STATE-- (optional)

      • --SAVING_YEARS_MAPS-- (optional)

      • --SAVING_YEARS_OBJECTS-- (optional)

    • -
    • --MASK--

        -
      • --MASK_CHANGEMASK_YEARS-- (optional)

      • +
      • --MASK--

        • --MASK_CHANGEMASK_YEARS-- (optional)

        • --MASK_CHANGEMASK_FILES-- (optional)

      • -
      • --PFG_PARAMS_LIFE_HISTORY--

          -
        • --PFG_PARAMS_LIGHT-- (optional)

        • +
        • --PFG_PARAMS_LIFE_HISTORY--

          • --PFG_PARAMS_LIGHT-- (optional)

          • --PFG_PARAMS_SOIL-- (optional)

          • --PFG_PARAMS_DISPERSAL-- (optional)

          • --PFG_MASK_HABSUIT-- (optional)

          • @@ -354,174 +260,237 @@

            Value

          • --SLOPE_MASK-- (optional)

        • --END_OF_FILE--

        • -
        - -

        Details

        - +
    +
    +

    Details

    The FATE software takes only one input parameter : a file containing links to other files containing all the parameters and data needed by the program to run.

    -
    -
    GLOBAL_PARAMS

    file where parameters related to the simulation +

    GLOBAL_PARAMS
    +

    file where parameters related to the simulation definition are referred (e.g. number of PFG involved, number of height - strata, simulation duration, computer resources, modules loaded, etc)
    - (see PRE_FATE.params_globalParameters)

    -
    SAVING_DIR

    directory where simulation outputs will be stored

    -
    SAVED_STATE (optional)

    file containing the results of a + strata, simulation duration, computer resources, modules loaded, etc)
    + (see PRE_FATE.params_globalParameters)

    + +
    SAVING_DIR
    +

    directory where simulation outputs will be stored

    + +
    SAVED_STATE (optional)
    +

    file containing the results of a previous FATE simulation from which to restart this new simulation -

    -
    SAVING_YEARS_
    ARRAYS (optional)

    file containing the - years for which simulation maps will be saved
    - (see PRE_FATE.params_savingYears)

    -
    SAVING_YEARS_
    OBJECTS (optional)

    file containing the - years for which simulation outputs will be saved
    - (see PRE_FATE.params_savingYears)

    -
    MASK

    raster mask that will define the study area

    -
    MASK_CHANGEMASK_YEARS
    (optional)

    file containing the years - to change rasters for the succession module
    - (see PRE_FATE.params_changingYears)

    -
    MASK_CHANGEMASK_FILES
    (optional)

    file containing the files - to change rasters for the succession module
    - (see PRE_FATE.params_changingYears)

    -
    PFG_PARAMS_
    LIFE_HISTORY

    PFG life history related parameters - (one by PFG)
    - (see PRE_FATE.params_PFGsuccession)

    -
    PFG_PARAMS_
    LIGHT (optional)

    PFG light preferences and - tolerance related parameters (one by PFG)
    - (see PRE_FATE.params_PFGlight)

    -
    PFG_PARAMS_
    SOIL (optional)

    PFG soil contribution and - tolerance related parameters (one by PFG)
    - (see PRE_FATE.params_PFGsoil)

    -
    PFG_PARAMS_
    DISPERSAL (optional)

    PFG dispersal - capacity related parameters (one by PFG)
    - (see PRE_FATE.params_PFGdispersal)

    -
    PFG_MASK_HABSUIT
    (optional)

    raster masks (one by PFG) +

    + +
    SAVING_YEARS_
    ARRAYS (optional)
    +

    file containing the + years for which simulation maps will be saved
    + (see PRE_FATE.params_savingYears)

    + +
    SAVING_YEARS_
    OBJECTS (optional)
    +

    file containing the + years for which simulation outputs will be saved
    + (see PRE_FATE.params_savingYears)

    + +
    MASK
    +

    raster mask that will define the study area

    + +
    MASK_CHANGEMASK_YEARS
    (optional)
    +

    file containing the years + to change rasters for the succession module
    + (see PRE_FATE.params_changingYears)

    + +
    MASK_CHANGEMASK_FILES
    (optional)
    +

    file containing the files + to change rasters for the succession module
    + (see PRE_FATE.params_changingYears)

    + +
    PFG_PARAMS_
    LIFE_HISTORY
    +

    PFG life history related parameters + (one by PFG)
    + (see PRE_FATE.params_PFGsuccession)

    + +
    PFG_PARAMS_
    LIGHT (optional)
    +

    PFG light preferences and + tolerance related parameters (one by PFG)
    + (see PRE_FATE.params_PFGlight)

    + +
    PFG_PARAMS_
    SOIL (optional)
    +

    PFG soil contribution and + tolerance related parameters (one by PFG)
    + (see PRE_FATE.params_PFGsoil)

    + +
    PFG_PARAMS_
    DISPERSAL (optional)
    +

    PFG dispersal + capacity related parameters (one by PFG)
    + (see PRE_FATE.params_PFGdispersal)

    + +
    PFG_MASK_HABSUIT
    (optional)
    +

    raster masks (one by PFG) containing PFG habitat suitability for the study area

    -
    HABSUIT_CHANGEMASK_YEARS
    (optional)

    file containing the years - to change rasters for the habitat suitability module
    - (see PRE_FATE.params_changingYears)

    -
    HABSUIT_CHANGEMASK_FILES
    (optional)

    file containing the files - to change rasters for the habitat suitability module
    - (see PRE_FATE.params_changingYears)

    -
    PFG_PARAMS_
    DISTURBANCES (optional)

    PFG disturbance - related parameters in terms of resprouting and mortality (one by PFG)
    - (see PRE_FATE.params_PFGdisturbance)

    -
    DIST_MASK
    (optional)

    raster masks that will define the + +

    HABSUIT_CHANGEMASK_YEARS
    (optional)
    +

    file containing the years + to change rasters for the habitat suitability module
    + (see PRE_FATE.params_changingYears)

    + +
    HABSUIT_CHANGEMASK_FILES
    (optional)
    +

    file containing the files + to change rasters for the habitat suitability module
    + (see PRE_FATE.params_changingYears)

    + +
    PFG_PARAMS_
    DISTURBANCES (optional)
    +

    PFG disturbance + related parameters in terms of resprouting and mortality (one by PFG)
    + (see PRE_FATE.params_PFGdisturbance)

    + +
    DIST_MASK
    (optional)
    +

    raster masks that will define the disturbance areas

    -
    DIST_CHANGEMASK_YEARS
    (optional)

    file containing the years - to change rasters for the disturbance module
    - (see PRE_FATE.params_changingYears)

    -
    DIST_CHANGEMASK_FILES
    (optional)

    file containing the files - to change rasters for the disturbance module
    - (see PRE_FATE.params_changingYears)

    -
    PFG_PARAMS_DROUGHT
    (optional)

    PFG drought disturbance - related parameters in terms of resprouting and mortality (one by PFG)
    - (see PRE_FATE.params_PFGdrought)

    -
    DROUGHT_MASK
    (optional)

    raster mask that will define the + +

    DIST_CHANGEMASK_YEARS
    (optional)
    +

    file containing the years + to change rasters for the disturbance module
    + (see PRE_FATE.params_changingYears)

    + +
    DIST_CHANGEMASK_FILES
    (optional)
    +

    file containing the files + to change rasters for the disturbance module
    + (see PRE_FATE.params_changingYears)

    + +
    PFG_PARAMS_DROUGHT
    (optional)
    +

    PFG drought disturbance + related parameters in terms of resprouting and mortality (one by PFG)
    + (see PRE_FATE.params_PFGdrought)

    + +
    DROUGHT_MASK
    (optional)
    +

    raster mask that will define the drought intensity of the area

    -
    DROUGHT_CHANGEMASK_YEARS
    (optional)

    file containing the - years to change rasters for the drought disturbances module
    - (see PRE_FATE.params_changingYears)

    -
    DROUGHT_CHANGEMASK_FILES
    (optional)

    file containing the - files to change rasters for the drought disturbances module
    - (see PRE_FATE.params_changingYears)

    -
    PFG_MASK_ALIENS
    (optional)

    raster masks (one by alien) + +

    DROUGHT_CHANGEMASK_YEARS
    (optional)
    +

    file containing the + years to change rasters for the drought disturbances module
    + (see PRE_FATE.params_changingYears)

    + +
    DROUGHT_CHANGEMASK_FILES
    (optional)
    +

    file containing the + files to change rasters for the drought disturbances module
    + (see PRE_FATE.params_changingYears)

    + +
    PFG_MASK_ALIENS
    (optional)
    +

    raster masks (one by alien) containing alien introduction zones for the study area

    -
    ALIENS_CHANGEMASK_YEARS
    (optional)

    file containing the - years to change rasters for the aliens introduction module
    - (see PRE_FATE.params_changingYears)

    -
    ALIENS_CHANGEMASK_FILES
    (optional)

    file containing the - files to change rasters for the aliens introduction module
    - (see PRE_FATE.params_changingYears)

    -
    ALIENS_CHANGEFREQ_YEARS
    (optional)

    file containing the - years to change frequencies for the aliens introduction module
    - (see PRE_FATE.params_changingYears)

    -
    ALIENS_CHANGEFREQ_FILES
    (optional)

    file containing the - files to change frequencies for the aliens introduction module
    - (see PRE_FATE.params_changingYears)

    -
    PFG_PARAMS_FIRE
    (optional)

    PFG fire disturbance - related parameters in terms of resprouting and mortality (one by PFG)
    - (see PRE_FATE.params_PFGdisturbance)

    -
    FIRE_MASK
    (optional)

    raster mask that will define the + +

    ALIENS_CHANGEMASK_YEARS
    (optional)
    +

    file containing the + years to change rasters for the aliens introduction module
    + (see PRE_FATE.params_changingYears)

    + +
    ALIENS_CHANGEMASK_FILES
    (optional)
    +

    file containing the + files to change rasters for the aliens introduction module
    + (see PRE_FATE.params_changingYears)

    + +
    ALIENS_CHANGEFREQ_YEARS
    (optional)
    +

    file containing the + years to change frequencies for the aliens introduction module
    + (see PRE_FATE.params_changingYears)

    + +
    ALIENS_CHANGEFREQ_FILES
    (optional)
    +

    file containing the + files to change frequencies for the aliens introduction module
    + (see PRE_FATE.params_changingYears)

    + +
    PFG_PARAMS_FIRE
    (optional)
    +

    PFG fire disturbance + related parameters in terms of resprouting and mortality (one by PFG)
    + (see PRE_FATE.params_PFGdisturbance)

    + +
    FIRE_MASK
    (optional)
    +

    raster mask that will define the fire disturbance areas

    -
    FIRE_CHANGEMASK_YEARS
    (optional)

    file containing the - years to change rasters for the fire disturbances module
    - (see PRE_FATE.params_changingYears)

    -
    FIRE_CHANGEMASK_FILES
    (optional)

    file containing the - files to change rasters for the fire disturbances module
    - (see PRE_FATE.params_changingYears)

    -
    FIRE_CHANGEFREQ_YEARS
    (optional)

    file containing the - years to change frequencies for the fire disturbances module
    - (see PRE_FATE.params_changingYears)

    -
    FIRE_CHANGEFREQ_FILES
    (optional)

    file containing the - files to change frequencies for the fire disturbances module
    - (see PRE_FATE.params_changingYears)

    -
    ELEVATION_MASK
    (optional)

    raster mask that will define + +

    FIRE_CHANGEMASK_YEARS
    (optional)
    +

    file containing the + years to change rasters for the fire disturbances module
    + (see PRE_FATE.params_changingYears)

    + +
    FIRE_CHANGEMASK_FILES
    (optional)
    +

    file containing the + files to change rasters for the fire disturbances module
    + (see PRE_FATE.params_changingYears)

    + +
    FIRE_CHANGEFREQ_YEARS
    (optional)
    +

    file containing the + years to change frequencies for the fire disturbances module
    + (see PRE_FATE.params_changingYears)

    + +
    FIRE_CHANGEFREQ_FILES
    (optional)
    +

    file containing the + files to change frequencies for the fire disturbances module
    + (see PRE_FATE.params_changingYears)

    + +
    ELEVATION_MASK
    (optional)
    +

    raster mask that will define the elevation of the area

    -
    SLOPE_MASK
    (optional)

    raster mask that will define the - slope of the area

    -
    +
    SLOPE_MASK
    (optional)
    +

    raster mask that will define the + slope of the area

    -

    Note

    +
    +
    +

    Note

    -
    + +
    +

    Author

    Maya Guéguen

    +
    +
    - - - + + diff --git a/docs/reference/PRE_FATE.selectDominant.html b/docs/reference/PRE_FATE.selectDominant.html index e299c80..08aab0f 100644 --- a/docs/reference/PRE_FATE.selectDominant.html +++ b/docs/reference/PRE_FATE.selectDominant.html @@ -1,68 +1,13 @@ - - - - - - - -Selection of dominant species from abundance releves — PRE_FATE.selectDominant • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Selection of dominant species from abundance releves — PRE_FATE.selectDominant • RFate - - - - + + -
    -
    - -
    - -
    +
    @@ -206,232 +140,259 @@

    Selection of dominant species from abundance releves

    abundance records, and habitat if the information is available.

    -
    PRE_FATE.selectDominant(
    -  mat.observations,
    -  doRuleA = TRUE,
    -  rule.A1 = 10,
    -  rule.A2_quantile = 0.9,
    -  doRuleB = TRUE,
    -  rule.B1_percentage = 0.25,
    -  rule.B1_number = 5,
    -  rule.B2 = 0.5,
    -  doRuleC = FALSE,
    -  opt.doRobustness = FALSE,
    -  opt.robustness_percent = seq(0.1, 0.9, 0.1),
    -  opt.robustness_rep = 10,
    -  opt.doSitesSpecies = TRUE,
    -  opt.doPlot = TRUE
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    mat.observations

    a data.frame with at least 3 columns :
    -sites, species, abund -
    (and optionally, habitat) -
    (see Details)

    doRuleA

    default TRUE.
    If TRUE, selection -is done including constraints on number of occurrences

    rule.A1

    default 10.
    If doRuleA = TRUE or -doRuleC = TRUE, minimum number of releves required for each species

    rule.A2_quantile

    default 0.9.
    If doRuleA = TRUE +

    +
    PRE_FATE.selectDominant(
    +  mat.observations,
    +  doRuleA = TRUE,
    +  rule.A1 = 10,
    +  rule.A2_quantile = 0.9,
    +  doRuleB = TRUE,
    +  rule.B1_percentage = 0.25,
    +  rule.B1_number = 5,
    +  rule.B2 = 0.5,
    +  doRuleC = FALSE,
    +  opt.doRobustness = FALSE,
    +  opt.robustness_percent = seq(0.1, 0.9, 0.1),
    +  opt.robustness_rep = 10,
    +  opt.doSitesSpecies = TRUE,
    +  opt.doPlot = TRUE
    +)
    +
    + +
    +

    Arguments

    +
    mat.observations
    +

    a data.frame with at least 3 columns :
    sites, species, abund +
    (and optionally, habitat) +
    (see Details)

    +
    doRuleA
    +

    default TRUE.
    If TRUE, selection +is done including constraints on number of occurrences

    +
    rule.A1
    +

    default 10.
    If doRuleA = TRUE or +doRuleC = TRUE, minimum number of releves required for each species

    +
    rule.A2_quantile
    +

    default 0.9.
    If doRuleA = TRUE or doRuleC = TRUE, quantile corresponding to the minimum number of -total occurrences required for each species (between 0 and 1)

    doRuleB

    default FALSE.
    If TRUE, selection is done -including constraints on relative abundances

    rule.B1_percentage

    default 0.25.
    If doRuleB = TRUE, +total occurrences required for each species (between 0 and 1)

    +
    doRuleB
    +

    default FALSE.
    If TRUE, selection is done +including constraints on relative abundances

    +
    rule.B1_percentage
    +

    default 0.25.
    If doRuleB = TRUE, minimum relative abundance required for each species in at least -rule.B1_number sites (between 0 and 1)

    rule.B1_number

    default 5.
    If doRuleB = TRUE, +rule.B1_number sites (between 0 and 1)

    +
    rule.B1_number
    +

    default 5.
    If doRuleB = TRUE, minimum number of sites in which each species has relative abundance ->= rule.B1_percentage

    rule.B2

    default 0.5.
    If doRuleB = TRUE, minimum +>= rule.B1_percentage

    +
    rule.B2
    +

    default 0.5.
    If doRuleB = TRUE, minimum average relative abundance required for each species (between 0 and -1)

    doRuleC

    default FALSE.
    If TRUE, selection is done +1)

    +
    doRuleC
    +

    default FALSE.
    If TRUE, selection is done including constraints on number of occurrences at the habitat level (with -the values of rule.A1 and rule.A2_quantile)

    opt.doRobustness

    (optional) default FALSE.
    +the values of rule.A1 and rule.A2_quantile)

    +
    opt.doRobustness
    +

    (optional) default FALSE.
    If TRUE, selection is also done on subsets of mat.observations, keeping only a percentage of releves or sites, to -visualize the robustness of the selection

    opt.robustness_percent

    (optional) default c(0.1, 0.2, -0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9).
    If opt.doRobustness = TRUE, +visualize the robustness of the selection

    +
    opt.robustness_percent
    +

    (optional) default c(0.1, 0.2, +0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9).
    If opt.doRobustness = TRUE, vector containing values between 0 and 1 corresponding -to the percentages with which to build subsets to evaluate robustness

    opt.robustness_rep

    (optional) default 10.
    If +to the percentages with which to build subsets to evaluate robustness

    +
    opt.robustness_rep
    +

    (optional) default 10.
    If opt.doRobustness = TRUE, number of repetitions for each percentage -value defined by opt.robustness_percent to evaluate robustness

    opt.doSitesSpecies

    (optional) default TRUE.
    +value defined by opt.robustness_percent to evaluate robustness

    +
    opt.doSitesSpecies
    +

    (optional) default TRUE.
    If TRUE, building of abundances / occurrences tables for selected -species will be processed, saved and returned.

    opt.doPlot

    (optional) default TRUE.
    If TRUE, +species will be processed, saved and returned.

    +
    opt.doPlot
    +

    (optional) default TRUE.
    If TRUE, plot(s) will be processed, otherwise only the calculation and reorganization -of outputs will occur, be saved and returned.

    - -

    Value

    - +of outputs will occur, be saved and returned.

    +
    +
    +

    Value

    A list containing one vector, four or five data.frame objects with the following columns, and up to five -ggplot2 objects :

    -
    -
    species.selected

    the names of the selected species

    -
    tab.rules

    -
    A1,A2,B1,B2, hab

    if the rule has been used, if the +ggplot2 objects :

    species.selected
    +

    the names of the selected species

    + +
    tab.rules
    +

    A1,A2,B1,B2, hab
    +

    if the rule has been used, if the species fullfills this condition or not

    -
    species

    the concerned species

    -
    SELECTION

    the summary of rules with which the species + +

    species
    +

    the concerned species

    + +
    SELECTION
    +

    the summary of rules with which the species was selected, or not

    -
    SELECTED

    TRUE if the species fullfills A1 - and at least one other condition, FALSE otherwise

    -
    tab.robustness

    -
    ...

    same as tab.rules

    -
    type

    the type of subset (either releves or + +

    SELECTED
    +

    TRUE if the species fullfills A1 + and at least one other condition, FALSE otherwise

    + + +

    + +
    tab.robustness
    +

    ...
    +

    same as tab.rules

    + +
    type
    +

    the type of subset (either releves or sites)

    -
    percent

    the concerned percentage of values extraction

    -
    rep

    the repetition ID

    -
    tab.dom.AB

    table containing sums of abundances for all selected + +

    percent
    +

    the concerned percentage of values extraction

    + +
    rep
    +

    the repetition ID

    + + +

    + +
    tab.dom.AB
    +

    table containing sums of abundances for all selected species (sites in rows, species in columns)

    -
    tab.dom.PA

    table containing counts of presences for all selected + +

    tab.dom.PA
    +

    table containing counts of presences for all selected species (sites in rows, species in columns)

    -
    plot.A

    ggplot2 object, representing the selection of + +

    plot.A
    +

    ggplot2 object, representing the selection of species according to rules A1 and A2

    -
    plot.B

    ggplot2 object, representing the selection of + +

    plot.B
    +

    ggplot2 object, representing the selection of species according to rules B

    -
    plot.C

    ggplot2 object, representing the selection of + +

    plot.C
    +

    ggplot2 object, representing the selection of species according to rules C (A1 and A2 per habitat)

    -
    plot.pco

    ggplot2 object, representing selected species with - Principal Coordinates Analysis (see dudi.pco)

    -
    plot.robustness

    ggplot2 object, representing the robustness - of the selection of species for each rule

    -
    +
    plot.pco
    +

    ggplot2 object, representing selected species with + Principal Coordinates Analysis (see dudi.pco)

    +
    plot.robustness
    +

    ggplot2 object, representing the robustness + of the selection of species for each rule

    -

    The information is written in -PRE_FATE_DOMINANT_[...].csv files :

    -
    TABLE_complete

    the complete table of all species and the +

    The information is written in +PRE_FATE_DOMINANT_[...].csv files :

    TABLE_complete
    +

    the complete table of all species and the selection rules described above (tab.rules)

    -
    TABLE_species

    only the names / ID of the species selected

    -
    TABLE_sitesXspecies_AB

    abundances table of selected species

    -
    TABLE_sitesXspecies_PA

    presence/absence table of selected + +

    TABLE_species
    +

    only the names / ID of the species selected

    + +
    TABLE_sitesXspecies_AB
    +

    abundances table of selected species

    + +
    TABLE_sitesXspecies_PA
    +

    presence/absence table of selected species

    -
    -

    Up to six PRE_FATE_DOMINANT_[...].pdf files are also created :

    -
    STEP_1_rule_A

    STEP_2_selectedSpecies_PHYLO

    -
    STEP_1_rule_B

    STEP_2_selectedSpecies_PCO

    -
    STEP_1_rule_C

    STEP_2_selectedSpecies_robustness

    +

    Up to six PRE_FATE_DOMINANT_[...].pdf files are also created :

    STEP_1_rule_A
    +

    STEP_2_selectedSpecies_PHYLO

    -
    +
    STEP_1_rule_B
    +

    STEP_2_selectedSpecies_PCO

    -

    Details

    +
    STEP_1_rule_C
    +

    STEP_2_selectedSpecies_robustness

    + +
    +
    +

    Details

    This function provides a way to select dominant species based on -presence/abundance sampling information.

    +presence/abundance sampling information
    .

    Three rules can be applied to make the species selection :

    -
    -
    A. Presence releves

    both conditions must be fullfilled -

    -
    on number of releves

    the species should be found a minimum +

    A. Presence releves
    +

    both conditions must be fullfilled

    on number of releves
    +

    the species should be found a minimum number of times (rule.A1) -
    This should ensure that the species has been given sufficient +
    This should ensure that the species has been given sufficient minimum sampling effort. This criterion MUST ALWAYS be fullfilled.

    -
    on number of sites

    the species should be found in a certain + +

    on number of sites
    +

    the species should be found in a certain number of sites, which corresponds to the quantile rule.A2_quantile of the total number of records per species -
    This should ensure that the species is covering all the +
    This should ensure that the species is covering all the studied area (or at least a determining part of it, assuming that - the releves are well distributed throughout the area).

    -
    B. Abundance releves :

    at least one of the two conditions is - required -

    -
    on dominancy

    the species should be dominant (i.e. represent at + the releves are well distributed throughout the area).

    + + +

    + +
    B. Abundance releves :
    +

    at least one of the two conditions is + required

    on dominancy
    +

    the species should be dominant (i.e. represent at least rule.B1_percentage % of the coverage of the site) in at least rule.B1_number sites -
    This should ensure the selection of species frequently +
    This should ensure the selection of species frequently abundant.

    -
    on average abundance

    the species should have a mean relative + +

    on average abundance
    +

    the species should have a mean relative abundance superior or equal to rule.B2 -
    This should ensure the selection of species not frequent but - representative of the sites in which it is found.

    -
    C. Presence releves
    per habitat :

    If habitat information is +
    This should ensure the selection of species not frequent but + representative of the sites in which it is found.

    + + +

    + +
    C. Presence releves
    per habitat :
    +

    If habitat information is available (e.g. type of environment : urban, desert, grassland... ; type of vegetation : shrubs, forest, alpine grasslands... ; etc), the same rules than A can be applied but for each habitat. -
    This should help to keep species that are not dominant at the - large scale but could be representative of a specific habitat.

    +
    This should help to keep species that are not dominant at the + large scale but could be representative of a specific habitat.

    -
    - -

    A table is created containing for each species whether or not it fullfills +

    A table is created containing for each species whether or not it fullfills the conditions selected, for example :

    -

    -

    | ___A1 ___A2 ___B1 ___B2 grass lands |

    -

    _______________________________________

    -
    species a

    | _TRUE FALSE FALSE _TRUE _TRUE FALSE |

    -
    species b

    | _TRUE _TRUE _TRUE FALSE FALSE FALSE |

    -
    species c

    | FALSE FALSE FALSE FALSE FALSE _TRUE |

    - -
    +

    +

    | ___A1 ___A2 ___B1 ___B2 grass lands |

    + +
    +

    _______________________________________

    + +
    species a
    +

    | _TRUE FALSE FALSE _TRUE _TRUE FALSE |

    + +
    species b
    +

    | _TRUE _TRUE _TRUE FALSE FALSE FALSE |

    + +
    species c
    +

    | FALSE FALSE FALSE FALSE FALSE _TRUE |

    + -

    This table is transformed into Euclidean distance matrix (with -gowdis and quasieuclid functions)
    +

    This table is transformed into Euclidean distance matrix (with +gowdis and quasieuclid functions)
    to cluster and represent species (see -.pdf output files) :

      -
    • through phylogenetic tree (with hclust and - as.phylo functions)

    • +.pdf output files) :

      • through phylogenetic tree (with hclust and + as.phylo functions)

      • through Principal Component Analysis (with - dudi.pco)

      • -

      according to their selection rules :

        -
      • A2 : spatial dominancy (widespread but poorly + dudi.pco)

      • +

      according to their selection rules :

      • A2 : spatial dominancy (widespread but poorly abundant)

      • B1 : local dominancy (relatively abundant or dominant in a certain number of sites)

      • @@ -443,107 +404,106 @@

        Details
      • A2 & B2 : (widespread and dominant in few sites)

      • A2 & B1 & B2 : (widespread and dominant)

      • B1 & B2 : (relatively widespread but dominant) -

      • -

      - -

      NB :
      +

      +

    NB :
    Species not meeting any criteria or only A1 are considered as -"Not selected".
    Priority is set to A2, B1 and B2 rules, rather +"Not selected".
    Priority is set to A2, B1 and B2 rules, rather than C. Hence, species selected according to A2, B1 and/or B2 can also meet criterion C while species selected according to C do not meet any of the -three criteria.
    Species selected according to one (or more) criterion +three criteria.
    Species selected according to one (or more) criterion but not meeting criterion A1 are also considered as "Not selected".

    -

    See also

    - - -

    Author

    - +
    + +
    +

    Author

    Isabelle Boulangeat, Maya Guéguen

    +
    -

    Examples

    -
    -## Load example data
    -Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
    -
    -## Species observations
    -tab = Champsaur_PFG$sp.observations
    -
    -## No habitat, no robustness -------------------------------------------------
    -tab.occ = tab[, c('sites', 'species', 'abund')]
    -sp.SELECT = PRE_FATE.selectDominant(mat.observations = tab.occ)
    -names(sp.SELECT)
    -str(sp.SELECT$tab.rules)
    -str(sp.SELECT$tab.dom.PA)
    -plot(sp.SELECT$plot.A)
    -plot(sp.SELECT$plot.B$abs)
    -plot(sp.SELECT$plot.B$rel)
    -
    -## Habitat, change parameters, no robustness (!quite long!) --------------------
    -if (FALSE) {
    -tab.occ = tab[, c('sites', 'species', 'abund', 'habitat')]
    -sp.SELECT = PRE_FATE.selectDominant(mat.observations = tab.occ
    -                                    , doRuleA = TRUE
    -                                    , rule.A1 = 10
    -                                    , rule.A2_quantile = 0.9
    -                                    , doRuleB = TRUE
    -                                    , rule.B1_percentage = 0.2
    -                                    , rule.B1_number = 10
    -                                    , rule.B2 = 0.4
    -                                    , doRuleC = TRUE)
    -names(sp.SELECT)
    -str(sp.SELECT$tab.rules)
    -plot(sp.SELECT$plot.C)
    -plot(sp.SELECT$plot.pco$Axis1_Axis2)
    -plot(sp.SELECT$plot.pco$Axis1_Axis3)
    -}
    -
    -## No habitat, robustness (!quite long!) --------------------
    -if (FALSE) {
    -tab.occ = tab[, c('sites', 'species', 'abund')]
    -sp.SELECT = PRE_FATE.selectDominant(mat.observations = tab.occ
    -                                    , opt.doSitesSpecies = FALSE
    -                                    , opt.doRobustness = TRUE
    -                                    , opt.robustness_percent = seq(0.1,0.9,0.1)
    -                                    , opt.robustness_rep = 10)
    -names(sp.SELECT)
    -str(sp.SELECT$tab.robustness)
    -names(sp.SELECT$plot.robustness)
    -plot(sp.SELECT$plot.robustness$`All dataset`)
    -}
    +    
    +

    Examples

    +
    
    +## Load example data
    +Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
    +
    +## Species observations
    +tab = Champsaur_PFG$sp.observations
    +
    +## No habitat, no robustness -------------------------------------------------
    +tab.occ = tab[, c('sites', 'species', 'abund')]
    +sp.SELECT = PRE_FATE.selectDominant(mat.observations = tab.occ)
    +names(sp.SELECT)
    +str(sp.SELECT$tab.rules)
    +str(sp.SELECT$tab.dom.PA)
    +plot(sp.SELECT$plot.A)
    +plot(sp.SELECT$plot.B$abs)
    +plot(sp.SELECT$plot.B$rel)
    +
    +## Habitat, change parameters, no robustness (!quite long!) --------------------
    +if (FALSE) {
    +tab.occ = tab[, c('sites', 'species', 'abund', 'habitat')]
    +sp.SELECT = PRE_FATE.selectDominant(mat.observations = tab.occ
    +                                    , doRuleA = TRUE
    +                                    , rule.A1 = 10
    +                                    , rule.A2_quantile = 0.9
    +                                    , doRuleB = TRUE
    +                                    , rule.B1_percentage = 0.2
    +                                    , rule.B1_number = 10
    +                                    , rule.B2 = 0.4
    +                                    , doRuleC = TRUE)
    +names(sp.SELECT)
    +str(sp.SELECT$tab.rules)
    +plot(sp.SELECT$plot.C)
    +plot(sp.SELECT$plot.pco$Axis1_Axis2)
    +plot(sp.SELECT$plot.pco$Axis1_Axis3)
    +}
    +
    +## No habitat, robustness (!quite long!) --------------------
    +if (FALSE) {
    +tab.occ = tab[, c('sites', 'species', 'abund')]
    +sp.SELECT = PRE_FATE.selectDominant(mat.observations = tab.occ
    +                                    , opt.doSitesSpecies = FALSE
    +                                    , opt.doRobustness = TRUE
    +                                    , opt.robustness_percent = seq(0.1,0.9,0.1)
    +                                    , opt.robustness_rep = 10)
    +names(sp.SELECT)
    +str(sp.SELECT$tab.robustness)
    +names(sp.SELECT$plot.robustness)
    +plot(sp.SELECT$plot.robustness$`All dataset`)
    +}
                                         
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/PRE_FATE.skeletonDirectory.html b/docs/reference/PRE_FATE.skeletonDirectory.html index da8a783..ccefc67 100644 --- a/docs/reference/PRE_FATE.skeletonDirectory.html +++ b/docs/reference/PRE_FATE.skeletonDirectory.html @@ -18,7 +18,7 @@ RFate - 1.0.3 + 1.1.0 @@ -30,7 +30,7 @@ + +

    Functional distances and niche overlap informations are then combined according to the following formula :

    $$\text{mat.DIST}_{sub-group} = \frac{[\text{wei.FUNC} * \text{mat.FUNCTIONAL}_{sub-group} + \text{wei.OVER} * @@ -348,89 +273,90 @@

    Details $$\text{wei.OVER} = 1$$

    meaning that distance matrix obtained from functional information is weighted by the number of traits used.

    -

    See also

    - - -

    Author

    - + + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Load example data
    -Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
    -
    -## Species traits
    -tab.traits = Champsaur_PFG$sp.traits
    -tab.traits = tab.traits[, c('species', 'GROUP', 'MATURITY', 'LONGEVITY'
    -                            , 'HEIGHT', 'DISPERSAL', 'LIGHT', 'NITROGEN')]
    -str(tab.traits)
    -
    -## Species niche overlap (dissimilarity distances)
    -tab.overlap = 1 - Champsaur_PFG$mat.overlap ## transform into similarity
    -tab.overlap[1:5, 1:5]
    -
    -## Give warnings -------------------------------------------------------------
    -sp.DIST = PRE_FATE.speciesDistance(mat.traits = tab.traits
    -                                   , mat.overlap.option = 'dist'
    -                                   , mat.overlap.object = tab.overlap)
    -str(sp.DIST)
    -
    -## Change parameters to allow more NAs (and change traits used) --------------
    -sp.DIST = PRE_FATE.speciesDistance(mat.traits = tab.traits
    -                                   , mat.overlap.option = 'dist'
    -                                   , mat.overlap.object = tab.overlap
    -                                   , opt.maxPercent.NA = 0.05
    -                                   , opt.maxPercent.similarSpecies = 0.3
    -                                   , opt.min.sd = 0.3)
    -str(sp.DIST)
    -
    -if (FALSE) {
    -require(foreach); require(ggplot2); require(ggdendro)
    -pp = foreach(x = names(sp.DIST$mat.ALL)) %do%
    -  {
    -    hc = hclust(sp.DIST$mat.ALL[[x]])
    -    pp = ggdendrogram(hc, rotate = TRUE) +
    -      labs(title = paste0('Hierarchical clustering based on species distance '
    -                          , ifelse(length(names(sp.DIST$mat.ALL)) > 1
    -                                   , paste0('(group ', x, ')')
    -                                   , '')))
    -    return(pp)
    -  }
    -plot(pp[[1]])
    -plot(pp[[2]])
    -plot(pp[[3]])
    -}
    -
    -
    -
    +
    +

    Examples

    +
    
    +## Load example data
    +Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
    +
    +## Species traits
    +tab.traits = Champsaur_PFG$sp.traits
    +tab.traits = tab.traits[, c('species', 'GROUP', 'MATURITY', 'LONGEVITY'
    +                            , 'HEIGHT', 'DISPERSAL', 'LIGHT', 'NITROGEN')]
    +str(tab.traits)
    +
    +## Species niche overlap (dissimilarity distances)
    +tab.overlap = 1 - Champsaur_PFG$mat.overlap ## transform into similarity
    +tab.overlap[1:5, 1:5]
    +
    +## Give warnings -------------------------------------------------------------
    +sp.DIST = PRE_FATE.speciesDistance(mat.traits = tab.traits
    +                                   , mat.overlap.option = 'dist'
    +                                   , mat.overlap.object = tab.overlap)
    +str(sp.DIST)
    +
    +## Change parameters to allow more NAs (and change traits used) --------------
    +sp.DIST = PRE_FATE.speciesDistance(mat.traits = tab.traits
    +                                   , mat.overlap.option = 'dist'
    +                                   , mat.overlap.object = tab.overlap
    +                                   , opt.maxPercent.NA = 0.05
    +                                   , opt.maxPercent.similarSpecies = 0.3
    +                                   , opt.min.sd = 0.3)
    +str(sp.DIST)
    +
    +if (FALSE) {
    +require(foreach); require(ggplot2); require(ggdendro)
    +pp = foreach(x = names(sp.DIST$mat.ALL)) %do%
    +  {
    +    hc = hclust(sp.DIST$mat.ALL[[x]])
    +    pp = ggdendrogram(hc, rotate = TRUE) +
    +      labs(title = paste0('Hierarchical clustering based on species distance '
    +                          , ifelse(length(names(sp.DIST$mat.ALL)) > 1
    +                                   , paste0('(group ', x, ')')
    +                                   , '')))
    +    return(pp)
    +  }
    +plot(pp[[1]])
    +plot(pp[[2]])
    +plot(pp[[3]])
    +}
    +
    +
    +
    +
    + - - - + + diff --git a/docs/reference/PRE_FATE.speciesDistanceCombine.html b/docs/reference/PRE_FATE.speciesDistanceCombine.html index 71b7515..1c3695e 100644 --- a/docs/reference/PRE_FATE.speciesDistanceCombine.html +++ b/docs/reference/PRE_FATE.speciesDistanceCombine.html @@ -18,7 +18,7 @@ RFate - 1.0.3 + 1.1.0 @@ -30,7 +30,7 @@
  • (raster option) a data.frame with 2 columns :

    species
    +

    the ID of each studied species

    -

    Value

    +
    raster
    +

    path to raster file with species distribution

    + +
  • + + +
    +

    Value

    A matrix containing overlap distances between each pair of species, calculated as 1 - Schoeners D.

    -

    Details

    - +
    +
    +

    Details

    This function allows to obtain a distance matrix between species (1 - Schoeners D), based on niche overlap information :

    -
    + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Load example data
    -Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
    -
    -## Prepare sites x species table
    -## Add absences in community sites
    -sites = Champsaur_PFG$sp.observations
    -tab.dom.PA = Champsaur_PFG$tab.dom.PA
    -for (si in sites$sites[which(sites$TYPE == "COMMUNITY")])
    -{
    -  ind = which(rownames(tab.dom.PA) == si)
    -  tab.dom.PA[ind, which(is.na(tab.dom.PA[ind, ]))] = 0
    -}
    -
    -## Prepare environmental table
    -tab.env = Champsaur_PFG$tab.env
    -
    -## Calculate niche overlap distances -----------------------------------------
    -list.over = list(tab.dom.PA[, 1:10], tab.env)
    -DIST.overlap = PRE_FATE.speciesDistanceOverlap(mat.overlap.option = "PCA"
    -                                               , mat.overlap.object = list.over)
    -(DIST.overlap[1:5, 1:5])
    -
    -
    +
    +

    Examples

    +
    
    +## Load example data
    +Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
    +
    +## Prepare sites x species table
    +## Add absences in community sites
    +sites = Champsaur_PFG$sp.observations
    +tab.dom.PA = Champsaur_PFG$tab.dom.PA
    +for (si in sites$sites[which(sites$TYPE == "COMMUNITY")])
    +{
    +  ind = which(rownames(tab.dom.PA) == si)
    +  tab.dom.PA[ind, which(is.na(tab.dom.PA[ind, ]))] = 0
    +}
    +
    +## Prepare environmental table
    +tab.env = Champsaur_PFG$tab.env
    +
    +## Calculate niche overlap distances -----------------------------------------
    +list.over = list(tab.dom.PA[, 1:10], tab.env)
    +DIST.overlap = PRE_FATE.speciesDistanceOverlap(mat.overlap.option = "PCA"
    +                                               , mat.overlap.object = list.over)
    +(DIST.overlap[1:5, 1:5])
    +
    +
    +
    + - - - + + diff --git a/docs/reference/PRE_FATE.speciesDistanceTraits.html b/docs/reference/PRE_FATE.speciesDistanceTraits.html index bf44a4e..0a0b01b 100644 --- a/docs/reference/PRE_FATE.speciesDistanceTraits.html +++ b/docs/reference/PRE_FATE.speciesDistanceTraits.html @@ -1,68 +1,13 @@ - - - - - - - -Computation of traits distances between species — PRE_FATE.speciesDistanceTraits • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Computation of traits distances between species — PRE_FATE.speciesDistanceTraits • RFate + + - - - - -
    -
    - -
    - -
    +
    @@ -206,150 +140,151 @@

    Computation of traits distances between species

    species, based on functional trait values.

    -
    PRE_FATE.speciesDistanceTraits(
    -  mat.traits,
    -  opt.maxPercent.NA = 0,
    -  opt.maxPercent.similarSpecies = 0.25,
    -  opt.min.sd = 0.3
    -)
    +
    +
    PRE_FATE.speciesDistanceTraits(
    +  mat.traits,
    +  opt.maxPercent.NA = 0,
    +  opt.maxPercent.similarSpecies = 0.25,
    +  opt.min.sd = 0.3
    +)
    +
    + +
    +

    Arguments

    +
    mat.traits
    +

    a data.frame with at least 3 columns :

    species
    +

    the ID of each studied species

    -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    mat.traits

    a data.frame with at least 3 columns :

    -
    species

    the ID of each studied species

    -
    GROUP

    a factor variable containing grouping information to +

    GROUP
    +

    a factor variable containing grouping information to divide the species into data subsets (see -Details)

    -
    ...

    one column for each functional trait

    +Details)

    -
    opt.maxPercent.NA

    (optional) default 0.
    Maximum -percentage of missing values (NA) allowed for each trait (between -0 and 1)

    opt.maxPercent.similarSpecies

    (optional) default 0.25. -
    Maximum percentage of similar species (same value) -allowed for each trait (between 0 and 1)

    opt.min.sd

    (optional) default 0.5.
    Minimum -standard deviation allowed for each trait (trait unit)

    +
    ...
    +

    one column for each functional trait

    -

    Value

    +
    +
    opt.maxPercent.NA
    +

    (optional) default 0.
    Maximum +percentage of missing values (NA) allowed for each trait (between +0 and 1)

    +
    opt.maxPercent.similarSpecies
    +

    (optional) default 0.25. +
    Maximum percentage of similar species (same value) +allowed for each trait (between 0 and 1)

    +
    opt.min.sd
    +

    (optional) default 0.5.
    Minimum +standard deviation allowed for each trait (trait unit)

    +
    +
    +

    Value

    A matrix containing functional distances between each pair of species, calculated as 1 - Schoeners D.

    -

    Details

    - +
    +
    +

    Details

    This function allows to obtain a distance matrix between species (1 - Schoeners D), based on functional traits information :

    -
      -
    • The GROUP column is required if species must be separated - to have one final distance matrix per GROUP value.
      If the +

      • The GROUP column is required if species must be separated + to have one final distance matrix per GROUP value.
        If the column is missing, all species will be considered as part of a unique dataset.

      • The traits can be qualitative or quantitative, but previously - identified as such
        (i.e. with the use of functions such as + identified as such
        (i.e. with the use of functions such as as.numeric, as.factor and ordered).

      • Functional distance matrix is calculated with Gower dissimilarity, - using the gowdis function.

      • -
      • This function allows NA values.
        However, too many + using the gowdis function.

      • +
      • This function allows NA values.
        However, too many missing values lead to misleading results. Hence, 3 parameters allow the user to play with the place given to missing values, and therefore the - selection of traits that will be used for the distance computation :

        -
        opt.maxPercent.NA

        traits with too many missing values are + selection of traits that will be used for the distance computation :

        opt.maxPercent.NA
        +

        traits with too many missing values are removed

        -
        opt.maxPercent
        .similarSpecies

        traits with too many - similar values are removed

        -
        opt.min.sd

        traits with too little variability are removed

        - -
      • -
      -

      See also

      +
      opt.maxPercent
      .similarSpecies
      +

      traits with too many + similar values are removed

      - -

      Author

      +
      opt.min.sd
      +

      traits with too little variability are removed

      + +
    • +
    +
    +

    See also

    + +
    +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Load example data
    -Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
    -
    -## Species traits
    -tab.traits = Champsaur_PFG$sp.traits
    -tab.traits = tab.traits[, c('species', 'GROUP', 'MATURITY', 'LONGEVITY'
    -                            , 'HEIGHT', 'DISPERSAL', 'LIGHT', 'NITROGEN')]
    -str(tab.traits)
    -
    -## Give warnings -------------------------------------------------------------
    -DIST.traits = PRE_FATE.speciesDistanceTraits(mat.traits = tab.traits)
    -str(DIST.traits)
    -
    -## Change parameters to allow more NAs (and change traits used) --------------
    -DIST.traits = PRE_FATE.speciesDistanceTraits(mat.traits = tab.traits
    -                                             , opt.maxPercent.NA = 0.05
    -                                             , opt.maxPercent.similarSpecies = 0.3
    -                                             , opt.min.sd = 0.3)
    -str(DIST.traits)
    -
    -if (FALSE) {
    -require(foreach); require(ggplot2); require(ggdendro)
    -pp = foreach(x = names(DIST.traits)) %do%
    -  {
    -    hc = hclust(as.dist(DIST.traits[[x]]))
    -    pp = ggdendrogram(hc, rotate = TRUE) +
    -      labs(title = paste0('Hierarchical clustering based on species distance '
    -                          , ifelse(length(names(DIST.traits)) > 1
    -                                   , paste0('(group ', x, ')')
    -                                   , '')))
    -    return(pp)
    -  }
    -plot(pp[[1]])
    -plot(pp[[2]])
    -plot(pp[[3]])
    -}
    -
    -
    -
    +
    +

    Examples

    +
    
    +## Load example data
    +Champsaur_PFG = .loadData('Champsaur_PFG', 'RData')
    +
    +## Species traits
    +tab.traits = Champsaur_PFG$sp.traits
    +tab.traits = tab.traits[, c('species', 'GROUP', 'MATURITY', 'LONGEVITY'
    +                            , 'HEIGHT', 'DISPERSAL', 'LIGHT', 'NITROGEN')]
    +str(tab.traits)
    +
    +## Give warnings -------------------------------------------------------------
    +DIST.traits = PRE_FATE.speciesDistanceTraits(mat.traits = tab.traits)
    +str(DIST.traits)
    +
    +## Change parameters to allow more NAs (and change traits used) --------------
    +DIST.traits = PRE_FATE.speciesDistanceTraits(mat.traits = tab.traits
    +                                             , opt.maxPercent.NA = 0.05
    +                                             , opt.maxPercent.similarSpecies = 0.3
    +                                             , opt.min.sd = 0.3)
    +str(DIST.traits)
    +
    +if (FALSE) {
    +require(foreach); require(ggplot2); require(ggdendro)
    +pp = foreach(x = names(DIST.traits)) %do%
    +  {
    +    hc = hclust(as.dist(DIST.traits[[x]]))
    +    pp = ggdendrogram(hc, rotate = TRUE) +
    +      labs(title = paste0('Hierarchical clustering based on species distance '
    +                          , ifelse(length(names(DIST.traits)) > 1
    +                                   , paste0('(group ', x, ')')
    +                                   , '')))
    +    return(pp)
    +  }
    +plot(pp[[1]])
    +plot(pp[[2]])
    +plot(pp[[3]])
    +}
    +
    +
    +
    +
    +
    - - - + + diff --git a/docs/reference/RFATE.html b/docs/reference/RFATE.html index 5cce873..9f968ae 100644 --- a/docs/reference/RFATE.html +++ b/docs/reference/RFATE.html @@ -1,72 +1,17 @@ - - - - - - - -Shiny application to apply RFate functions and run -FATE simulation — RFATE • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Shiny application to apply RFate functions and run +FATE simulation — RFATE • RFate - - - - - - - - - - + + - - - -
    -
    - -
    - -
    +
    @@ -213,38 +147,36 @@

    Shiny application to apply RFate functions and run output files.

    -
    RFATE()
    - - -

    Author

    +
    +
    RFATE()
    +
    +
    +

    Author

    Maya Guéguen

    +
    +
    - - - + + diff --git a/docs/reference/SAVE_FATE.step1_PFG.html b/docs/reference/SAVE_FATE.step1_PFG.html index b903ce5..b46ee3c 100644 --- a/docs/reference/SAVE_FATE.step1_PFG.html +++ b/docs/reference/SAVE_FATE.step1_PFG.html @@ -1,68 +1,13 @@ - - - - - - - -Save data to reproduce building of Plant Functional Groups — SAVE_FATE.step1_PFG • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Save data to reproduce building of Plant Functional Groups — SAVE_FATE.step1_PFG • RFate + + - - - - -
    -
    - -
    - -
    +
    @@ -206,216 +140,259 @@

    Save data to reproduce building of Plant Functional Groups

    used to build a set of Plant Functional Groups.

    -
    SAVE_FATE.step1_PFG(
    -  name.dataset,
    -  mat.observations,
    -  rules.selectDominant = c(doRuleA = NA, rule.A1 = NA, rule.A2_quantile = NA, doRuleB =
    -    NA, rule.B1_percentage = NA, rule.B1_number = NA, rule.B2 = NA, doRuleC = NA),
    -  mat.traits,
    -  mat.overlap = NA,
    -  rules.speciesDistance = c(opt.maxPercent.NA = NA, opt.maxPercent.similarSpecies = NA,
    -    opt.min.sd = NA),
    -  mat.species.DIST,
    -  clust.evaluation = NA,
    -  no.clusters,
    -  determ.all,
    -  mat.traits.PFG
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    name.dataset

    a string corresponding to the name to give to -archive folder

    mat.observations

    a data.frame with at least 3 columns :
    -sites, species, abund (and optionally, -habitat) (see PRE_FATE.selectDominant)

    rules.selectDominant

    (optional) default NA.
    A +

    +
    SAVE_FATE.step1_PFG(
    +  name.dataset,
    +  mat.observations,
    +  rules.selectDominant = c(doRuleA = NA, rule.A1 = NA, rule.A2_quantile = NA, doRuleB =
    +    NA, rule.B1_percentage = NA, rule.B1_number = NA, rule.B2 = NA, doRuleC = NA),
    +  mat.traits,
    +  mat.overlap = NA,
    +  rules.speciesDistance = c(opt.maxPercent.NA = NA, opt.maxPercent.similarSpecies = NA,
    +    opt.min.sd = NA),
    +  mat.species.DIST,
    +  clust.evaluation = NA,
    +  no.clusters,
    +  determ.all,
    +  mat.traits.PFG
    +)
    +
    + +
    +

    Arguments

    +
    name.dataset
    +

    a string corresponding to the name to give to +archive folder

    +
    mat.observations
    +

    a data.frame with at least 3 columns :
    sites, species, abund (and optionally, +habitat) (see PRE_FATE.selectDominant)

    +
    rules.selectDominant
    +

    (optional) default NA.
    A vector containing all the parameter values given to the -PRE_FATE.selectDominant function, if used +PRE_FATE.selectDominant function, if used (doRuleA, rule.A1, rule.A2_quantile, doRuleB, rule.B1_percentage, rule.B1_number, rule.B2, -doRuleC).

    mat.traits

    a data.frame with at least 3 columns : +doRuleC).

    +
    mat.traits
    +

    a data.frame with at least 3 columns : species, GROUP, ... (one column for each functional -trait)
    (see PRE_FATE.speciesDistance)

    mat.overlap

    (optional) default NA.
    -Otherwise, two options :

      -
    • a data.frame with 2 columns : species, raster

    • +trait)
      (see PRE_FATE.speciesDistance)

      +
      mat.overlap
      +

      (optional) default NA.
      +Otherwise, two options :

      • a data.frame with 2 columns : species, raster

      • a dissimilarity structure representing the niche overlap between - each pair of species.
        It can be a dist object, a + each pair of species.
        It can be a dist object, a niolap object, or simply a matrix.

      • -

      (see PRE_FATE.speciesDistance)

    rules.speciesDistance

    (optional) default NA.
    A +

    (see PRE_FATE.speciesDistance)

    +
    rules.speciesDistance
    +

    (optional) default NA.
    A vector containing all the parameter values given to the -PRE_FATE.speciesDistance function, if used
    ( +PRE_FATE.speciesDistance function, if used
    ( opt.maxPercent.NA, opt.maxPercent.similarSpecies, -opt.min.sd).

    mat.species.DIST

    a dist object, or a list of +opt.min.sd).

    +
    mat.species.DIST
    +

    a dist object, or a list of dist objects (one for each GROUP value), corresponding to the -distance between each pair of species.
    Such an object can be obtained -with the PRE_FATE.speciesDistance function.

    clust.evaluation

    (optional) default NA.
    A -data.frame with 4 columns :
    -GROUP, no.clusters, variable, value.
    Such an +distance between each pair of species.
    Such an object can be obtained +with the PRE_FATE.speciesDistance function.

    +
    clust.evaluation
    +

    (optional) default NA.
    A +data.frame with 4 columns :
    GROUP, no.clusters, variable, value.
    Such an object can be obtained with the -PRE_FATE.speciesClustering_step1 function.

    no.clusters

    an integer, or a vector of integer +PRE_FATE.speciesClustering_step1 function.

    +
    no.clusters
    +

    an integer, or a vector of integer (one for each GROUP value), with the number of clusters to be kept -(see PRE_FATE.speciesClustering_step2)

    determ.all

    a data.frame with 6 or 10 columns :
    -PFG, GROUP, ID.cluster, species, -ID.species, DETERMINANT
    (and optionally, +(see PRE_FATE.speciesClustering_step2)

    +
    determ.all
    +

    a data.frame with 6 or 10 columns :
    PFG, GROUP, ID.cluster, species, +ID.species, DETERMINANT
    (and optionally, sp.mean.dist, allSp.mean, allSp.min, -allSp.max).
    Such an object can be obtained -with the PRE_FATE.speciesClustering_step2 function.

    mat.traits.PFG

    a data.frame with at least 3 columns : +allSp.max).
    Such an object can be obtained +with the PRE_FATE.speciesClustering_step2 function.

    +
    mat.traits.PFG
    +

    a data.frame with at least 3 columns : PFG, no.species, ... (one column for each functional trait, computed as the mean (for numeric traits) or the median (for categorical traits) of the values of the determinant species of this -PFG).
    Such an object can be obtained with the -PRE_FATE.speciesClustering_step3 function.

    +PFG).
    Such an object can be obtained with the +PRE_FATE.speciesClustering_step3 function.

    +
    +
    +

    Value

    +

    A list containing all the elements given to the function and +checked :

    name.dataset
    +

    name of the dataset

    -

    Value

    +
    mat.observations
    +

    (see PRE_FATE.selectDominant)

    sites
    +

    name of sampling site

    -

    A list containing all the elements given to the function and -checked :

    -
    -
    name.dataset

    name of the dataset

    -
    mat.observations

    (see PRE_FATE.selectDominant)
    -

    -
    sites

    name of sampling site

    -
    (x, y)

    coordinates of sampling site

    -
    species

    name of the concerned species

    -
    abund

    abundance of the concerned species

    -
    (habitat)

    habitat of sampling site

    -
    rules.selectDominant

    a vector containing values for the +

    (x, y)
    +

    coordinates of sampling site

    + +
    species
    +

    name of the concerned species

    + +
    abund
    +

    abundance of the concerned species

    + +
    (habitat)
    +

    habitat of sampling site

    + + +

    + +
    rules.selectDominant
    +

    a vector containing values for the parameters doRuleA, rule.A1, rule.A2_quantile, doRuleB, rule.B1_percentage, rule.B1_number, - rule.B2, doRuleC (see PRE_FATE.selectDominant)

    -
    mat.traits

    (see PRE_FATE.speciesDistance)
    -

    -
    species

    name of the concerned species

    -
    GROUP

    name of the concerned data subset

    -
    ...

    one column for each functional trait

    -
    mat.overlap

    a dist object corresponding to the distance + rule.B2, doRuleC (see PRE_FATE.selectDominant)

    + +
    mat.traits
    +

    (see PRE_FATE.speciesDistance)

    species
    +

    name of the concerned species

    + +
    GROUP
    +

    name of the concerned data subset

    + +
    ...
    +

    one column for each functional trait

    + + +

    + +
    mat.overlap
    +

    a dist object corresponding to the distance between each pair of species in terms of niche overlap (see - PRE_FATE.speciesDistance)

    -
    rules.speciesDistance

    a vector containing values for the + PRE_FATE.speciesDistance)

    + +
    rules.speciesDistance
    +

    a vector containing values for the parameters opt.maxPercent.NA, opt.maxPercent.similarSpecies, - opt.min.sd (see PRE_FATE.speciesDistance)

    -
    mat.species.DIST

    a dist object corresponding to the distance + opt.min.sd (see PRE_FATE.speciesDistance)

    + +
    mat.species.DIST
    +

    a dist object corresponding to the distance between each pair of species, or a list of dist objects, one - for each GROUP value (see PRE_FATE.speciesDistance)

    -
    clust.evaluation

    (see PRE_FATE.speciesClustering_step1)
    -

    -
    GROUP

    name of data subset

    -
    no.clusters

    number of clusters used for the clustering

    -
    variable

    evaluation metrics' name

    -
    value

    value of evaluation metric

    -
    no.clusters

    number of clusters to be kept for each data subset

    -
    determ.all

    (see PRE_FATE.speciesClustering_step2)
    -

    -
    PFG

    ID of the plant functional group + for each GROUP value (see PRE_FATE.speciesDistance)

    + +
    clust.evaluation
    +

    (see PRE_FATE.speciesClustering_step1)

    GROUP
    +

    name of data subset

    + +
    no.clusters
    +

    number of clusters used for the clustering

    + +
    variable
    +

    evaluation metrics' name

    + +
    value
    +

    value of evaluation metric

    + + +

    + +
    no.clusters
    +

    number of clusters to be kept for each data subset

    + +
    determ.all
    +

    (see PRE_FATE.speciesClustering_step2)

    PFG
    +

    ID of the plant functional group (GROUP + ID.cluster)

    -
    GROUP

    name of data subset

    -
    ID.cluster

    cluster number

    -
    species

    name of species

    -
    ID.species

    species number in each PFG

    -
    DETERMINANT

    TRUE if determinant species, FALSE + +

    GROUP
    +

    name of data subset

    + +
    ID.cluster
    +

    cluster number

    + +
    species
    +

    name of species

    + +
    ID.species
    +

    species number in each PFG

    + +
    DETERMINANT
    +

    TRUE if determinant species, FALSE otherwise

    -
    (sp.mean.dist)

    species mean distance to other species of + +

    (sp.mean.dist)
    +

    species mean distance to other species of the same PFG

    -
    (allSp.mean)

    \(mean(\text{sp.mean.dist})\) within the PFG

    -
    (allSp.min)

    \(mean(\text{sp.mean.dist}) - 1.64 * + +

    (allSp.mean)
    +

    \(mean(\text{sp.mean.dist})\) within the PFG

    + +
    (allSp.min)
    +

    \(mean(\text{sp.mean.dist}) - 1.64 * + sd(\text{sp.mean.dist})\) within the PFG

    + +
    (allSp.max)
    +

    \(mean(\text{sp.mean.dist}) + 1.64 * sd(\text{sp.mean.dist})\) within the PFG

    -
    (allSp.max)

    \(mean(\text{sp.mean.dist}) + 1.64 * - sd(\text{sp.mean.dist})\) within the PFG

    -
    mat.traits.PFG

    (see PRE_FATE.speciesClustering_step3)
    -

    -
    PFG

    name of the concerned functional group

    -
    no.species

    number of species in the concerned PFG

    -
    ...

    one column for each functional trait

    - -
    - -

    The information is written in FATE_dataset_[name.dataset]_step1_PFG.RData file.

    -

    See also

    - - -

    Author

    + +

    + +
    mat.traits.PFG
    +

    (see PRE_FATE.speciesClustering_step3)

    PFG
    +

    name of the concerned functional group

    + +
    no.species
    +

    number of species in the concerned PFG

    + +
    ...
    +

    one column for each functional trait

    + + +

    + + +

    The information is written in FATE_dataset_[name.dataset]_step1_PFG.RData file.

    +
    + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Load example data
    +    
    +

    Examples

    +
    
    +## Load example data
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/SAVE_FATE.step2_parameters.html b/docs/reference/SAVE_FATE.step2_parameters.html index 379b472..d0e4664 100644 --- a/docs/reference/SAVE_FATE.step2_parameters.html +++ b/docs/reference/SAVE_FATE.step2_parameters.html @@ -1,68 +1,13 @@ - - - - - - - -Save data to reproduce building of parameter files — SAVE_FATE.step2_parameters • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Save data to reproduce building of parameter files — SAVE_FATE.step2_parameters • RFate - - + + - - -
    -
    - -
    - -
    +
    @@ -206,155 +140,119 @@

    Save data to reproduce building of parameter files

    used to build a FATE simulation folder.

    -
    SAVE_FATE.step2_parameters(
    -  name.dataset,
    -  name.simulation = NA,
    -  strata.limits,
    -  mat.PFG.succ,
    -  mat.PFG.light = NULL,
    -  mat.PFG.light.tol = NULL,
    -  mat.PFG.soil = NULL,
    -  mat.PFG.soil.tol = NULL,
    -  mat.PFG.disp = NULL,
    -  mat.PFG.dist = NULL,
    -  mat.PFG.dist.tol = NULL,
    -  mat.PFG.drought = NULL,
    -  mat.PFG.drought.tol = NULL,
    -  rasters = list(name.MASK = NA, name.DIST = NA, name.DROUGHT = NA, name.FIRE = NA,
    -    name.ELEVATION = NA, name.SLOPE = NA),
    -  multipleSet = list(name.simulation.1 = NA, name.simulation.2 = NA, file.simulParam.1
    -    = NA, file.simulParam.2 = NA, no_simulations = NA, opt.percent_maxAbund = NA,
    -    opt.percent_seeding = NA, opt.percent_light = NA, opt.percent_soil = NA,
    -    do.max_abund_low = NA, do.max_abund_medium = NA, do.max_abund_high = NA,
    -    do.seeding_duration = NA, do.seeding_timestep = NA, do.seeding_input = NA,
    -    do.no_strata = NA, do.LIGHT.thresh_medium = NA, do.LIGHT.thresh_low = NA,
    -    do.SOIL.init = NA, do.SOIL.retention = NA, do.DISPERSAL.mode = NA,     
    -    do.HABSUIT.mode = NA)
    -)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    name.dataset

    a string corresponding to the name to give to -archive folder

    name.simulation

    (optional) default NA.
    -A string corresponding to the name of the simulation folder

    strata.limits

    a vector of integer containing height -strata limits

    mat.PFG.succ

    a data.frame with at least 5 columns :
    -PFG, type, height, maturity, longevity -
    (and optionally, max_abundance, potential_fecundity, +

    +
    SAVE_FATE.step2_parameters(
    +  name.dataset,
    +  name.simulation = NA,
    +  strata.limits,
    +  mat.PFG.succ,
    +  mat.PFG.light = NULL,
    +  mat.PFG.light.tol = NULL,
    +  mat.PFG.soil = NULL,
    +  mat.PFG.soil.tol = NULL,
    +  mat.PFG.disp = NULL,
    +  mat.PFG.dist = NULL,
    +  mat.PFG.dist.tol = NULL,
    +  mat.PFG.drought = NULL,
    +  mat.PFG.drought.tol = NULL,
    +  rasters = list(name.MASK = NA, name.DIST = NA, name.DROUGHT = NA, name.FIRE = NA,
    +    name.ELEVATION = NA, name.SLOPE = NA),
    +  multipleSet = list(name.simulation.1 = NA, name.simulation.2 = NA, file.simulParam.1
    +    = NA, file.simulParam.2 = NA, no_simulations = NA, opt.percent_maxAbund = NA,
    +    opt.percent_seeding = NA, opt.percent_light = NA, opt.percent_soil = NA,
    +    do.max_abund_low = NA, do.max_abund_medium = NA, do.max_abund_high = NA,
    +    do.seeding_duration = NA, do.seeding_timestep = NA, do.seeding_input = NA,
    +    do.no_strata = NA, do.LIGHT.thresh_medium = NA, do.LIGHT.thresh_low = NA,
    +    do.SOIL.init = NA, do.SOIL.retention = NA, do.DISPERSAL.mode = NA, 
    +    
    +    do.HABSUIT.mode = NA)
    +)
    +
    + +
    +

    Arguments

    +
    name.dataset
    +

    a string corresponding to the name to give to +archive folder

    +
    name.simulation
    +

    (optional) default NA.
    +A string corresponding to the name of the simulation folder

    +
    strata.limits
    +

    a vector of integer containing height +strata limits

    +
    mat.PFG.succ
    +

    a data.frame with at least 5 columns :
    PFG, type, height, maturity, longevity +
    (and optionally, max_abundance, potential_fecundity, immature_size, is_alien, flammability) -
    (see PRE_FATE.params_PFGsuccession)

    mat.PFG.light

    (optional) default NA.
    -A data.frame with 2 to 6 columns :

    mat.PFG.light.tol

    (optional) default NA.
    -A data.frame with 2 to 4 columns :

      -
    • PFG,

    • +

    (see PRE_FATE.params_PFGlight)

    +
    mat.PFG.light.tol
    +

    (optional) default NA.
    +A data.frame with 2 to 4 columns :

    • PFG,

    • lifeStage, resources, tolerance (or strategy_tol)

    • -

    (see PRE_FATE.params_PFGlight)

    mat.PFG.soil

    (optional) default NA.
    -A data.frame with 3 to 7 columns :

      -
    • PFG,

    • +

    (see PRE_FATE.params_PFGlight)

    +
    mat.PFG.soil
    +

    (optional) default NA.
    +A data.frame with 3 to 7 columns :

    • PFG,

    • type, (or active_germ_low, active_germ_medium, active_germ_high) (or strategy_ag)

    • soil_contrib, soil_tol_min, soil_tol_max (or strategy_contrib)

    • -

    (see PRE_FATE.params_PFGsoil)

    mat.PFG.soil.tol

    (optional) default NA.
    -A data.frame with 2 to 4 columns :

      -
    • PFG,

    • +

    (see PRE_FATE.params_PFGsoil)

    +
    mat.PFG.soil.tol
    +

    (optional) default NA.
    +A data.frame with 2 to 4 columns :

    • PFG,

    • lifeStage, resources, tolerance (or strategy_tol)

    • -

    (see PRE_FATE.params_PFGsoil)

    mat.PFG.disp

    (optional) default NA.
    +

    (see PRE_FATE.params_PFGsoil)

    +
    mat.PFG.disp
    +

    (optional) default NA.
    A data.frame with 4 columns : PFG, d50, d99, -ldd (see PRE_FATE.params_PFGdispersal)

    mat.PFG.dist

    (optional) default NA.
    -A data.frame with 5 columns :
    -PFG, type, maturity, longevity, -age_above_150cm (see PRE_FATE.params_PFGdisturbance)

    mat.PFG.dist.tol

    (optional) default NA.
    -A data.frame with 3 to 7 columns :

    mat.PFG.drought

    (optional) default NA.
    -A data.frame with 4 or 6 columns :

      -
    • PFG,

    • +

    (see PRE_FATE.params_PFGdisturbance)

    +
    mat.PFG.drought
    +

    (optional) default NA.
    +A data.frame with 4 or 6 columns :

    • PFG,

    • threshold_moderate, threshold_severe,

    • counter_recovery, counter_sens, counter_cum (or strategy_drou)

    • -

    (see PRE_FATE.params_PFGdrought)

    mat.PFG.drought.tol

    (optional) default NA.
    -A data.frame with 3 to 7 columns :

      -
    • nameDist,

    • +

    (see PRE_FATE.params_PFGdrought)

    +
    mat.PFG.drought.tol
    +

    (optional) default NA.
    +A data.frame with 3 to 7 columns :

    • nameDist,

    • PFG,

    • (responseStage, breakAge, resproutAge),

    • responseStage, killedIndiv, resproutIndiv (or strategy_tol)

    • -

    (see PRE_FATE.params_PFGdrought)

    rasters

    a list containing all the rasters given to the -PRE_FATE.params_simulParameters function, if used +

    (see PRE_FATE.params_PFGdrought)

    +
    rasters
    +

    a list containing all the rasters given to the +PRE_FATE.params_simulParameters function, if used (name.MASK, name.DIST, name.DROUGHT, name.FIRE, -name.ELEVATION, name.SLOPE)

    multipleSet

    a list containing all the parameter values given -to the PRE_FATE.params_multipleSet function, if used +name.ELEVATION, name.SLOPE)

    +
    multipleSet
    +

    a list containing all the parameter values given +to the PRE_FATE.params_multipleSet function, if used (name.simulation.1, name.simulation.2, file.simulParam.1, file.simulParam.2, no_simulations, opt.percent_maxAbund, @@ -365,86 +263,115 @@

    Arg do.seeding_input, do.no_strata, do.LIGHT.thresh_medium, do.LIGHT.thresh_low, do.SOIL.init, do.SOIL.retention, -do.DISPERSAL.mode, do.HABSUIT.mode)

    +do.DISPERSAL.mode, do.HABSUIT.mode)

    +
    +
    +

    Value

    +

    A list containing all the elements given to the function and +checked, and two archive files :

    name.dataset
    +

    name of the dataset

    -

    Value

    +
    strata.limits
    +

    height strata limits

    -

    A list containing all the elements given to the function and -checked, and two archive files :

    -
    -
    name.dataset

    name of the dataset

    -
    strata.limits

    height strata limits

    -
    mat.PFG.succ

    -
    (mat.PFG.light)

    -
    (mat.PFG.light.tol)

    -
    (mat.PFG.soil)

    -
    (mat.PFG.soil.tol)

    -
    (mat.PFG.disp)

    -
    (mat.PFG.dist)

    -
    (mat.PFG.dist.tol)

    -
    (mat.PFG.drought)

    -
    (mat.PFG.drought.tol)

    -
    rasters

    raster files of all simulation masks

    -
    (multipleSet)



    -
    (name.simulation)

    name of the simulation folder

    -
    (DATA folder)

    contained in name.simulation folder +

    mat.PFG.succ
    +
    + +
    (mat.PFG.light)
    +
    + +
    (mat.PFG.light.tol)
    +
    + +
    (mat.PFG.soil)
    +
    + +
    (mat.PFG.soil.tol)
    +
    + +
    (mat.PFG.disp)
    +
    + +
    (mat.PFG.dist)
    +
    + +
    (mat.PFG.dist.tol)
    +
    + +
    (mat.PFG.drought)
    +
    + +
    (mat.PFG.drought.tol)
    +
    + +
    rasters
    +

    raster files of all simulation masks

    + +
    (multipleSet)
    +



    + +
    (name.simulation)
    +

    name of the simulation folder

    + +
    (DATA folder)
    +

    contained in name.simulation folder and archived

    -
    (PARAM_SIMUL folder)

    contained in name.simulation - folder and archived

    - -
    - -

    The information is written in FATE_dataset_[name.dataset]_step2_parameters.RData file.

    -

    See also

    - - -

    Author

    +
    (PARAM_SIMUL folder)
    +

    contained in name.simulation + folder and archived

    + + +

    The information is written in FATE_dataset_[name.dataset]_step2_parameters.RData file.

    +
    + +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Load example data
    +    
    +

    Examples

    +
    
    +## Load example data
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/beta.pair.html b/docs/reference/beta.pair.html index 85a0f53..c12cce7 100644 --- a/docs/reference/beta.pair.html +++ b/docs/reference/beta.pair.html @@ -1,67 +1,12 @@ - - - - - - - -From betapart package 1.5.4 : beta.pair and betapart.core functions — beta.pair • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -From betapart package 1.5.4 : beta.pair and betapart.core functions — beta.pair • RFate - + + - - - -
    -
    - -
    - -
    +
    @@ -204,54 +138,47 @@

    From betapart package 1.5.4 : beta.pair and betapart.core functions

    From betapart package 1.5.4 : beta.pair and betapart.core functions

    -
    betapart.core(x)
    -beta.pair(x, index.family = "sorensen")
    +
    +
    betapart.core(x)
    +beta.pair(x, index.family = "sorensen")
    +
    -

    Arguments

    - - - - - - - - - - -
    x

    data frame, where rows are sites and columns are species. +

    +

    Arguments

    +
    x
    +

    data frame, where rows are sites and columns are species. Alternatively x can be a betapart object derived from the betapart.core -function

    index.family

    family of dissimilarity indices, partial match of -"sorensen" or "jaccard".

    - -

    See also

    - -

    beta.pair

    +function

    +
    index.family
    +

    family of dissimilarity indices, partial match of +"sorensen" or "jaccard".

    +
    +
    +

    See also

    +

    beta.pair

    +
    +
    - - - + + diff --git a/docs/reference/cluster.stats.html b/docs/reference/cluster.stats.html index a61d073..6224f5f 100644 --- a/docs/reference/cluster.stats.html +++ b/docs/reference/cluster.stats.html @@ -17,7 +17,7 @@ RFate - 1.0.3 + 1.0.4 @@ -230,7 +230,7 @@

    See also

    -

    Site built with pkgdown 2.0.2.

    +

    Site built with pkgdown 2.0.1.

    diff --git a/docs/reference/designLHD.html b/docs/reference/designLHD.html index e279d8d..2496aa1 100644 --- a/docs/reference/designLHD.html +++ b/docs/reference/designLHD.html @@ -1,67 +1,12 @@ - - - - - - - -From SPOT package 2.5.0 : designLHD and designLHDNorm functions — designLHD • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -From SPOT package 2.5.0 : designLHD and designLHDNorm functions — designLHD • RFate - + + - - - -
    -
    - -
    - -
    +
    @@ -204,96 +138,75 @@

    From SPOT package 2.5.0 : designLHD and designLHDNorm functions

    From SPOT package 2.5.0 : designLHD and designLHDNorm functions

    -
    designLHDNorm(dim, size, calcMinDistance = FALSE, nested = NULL, 
    -inequalityConstraint = NULL)
    -designLHD(x = NULL, lower, upper, control = list())
    +
    +
    designLHDNorm(dim, size, calcMinDistance = FALSE, nested = NULL, 
    +inequalityConstraint = NULL)
    +designLHD(x = NULL, lower, upper, control = list())
    +
    -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    dim

    number, dimension of the problem (will be no. of columns of -the result matrix)

    size

    number of points with that dimension needed. (will be no. -of rows of the result matrix).

    calcMinDistance

    Boolean to indicate whether a minimal distance -should be calculated.

    nested

    nested design to be considered during distance calculation.

    inequalityConstraint

    inequality constraint function, smaller zero +

    +

    Arguments

    +
    dim
    +

    number, dimension of the problem (will be no. of columns of +the result matrix)

    +
    size
    +

    number of points with that dimension needed. (will be no. +of rows of the result matrix).

    +
    calcMinDistance
    +

    Boolean to indicate whether a minimal distance +should be calculated.

    +
    nested
    +

    nested design to be considered during distance calculation.

    +
    inequalityConstraint
    +

    inequality constraint function, smaller zero for infeasible points. Used to replace infeasible points with random -points. Has to evaluate points in interval [0;1].

    x

    optional matrix x, rows for points, columns for dimensions. This +points. Has to evaluate points in interval [0;1].

    +
    x
    +

    optional matrix x, rows for points, columns for dimensions. This can contain one or more points which are part of the design, but specified by the user. These points are added to the design, and are taken into account when calculating the pair-wise distances. They do not count for the design size. E.g., if x has two rows, control$replicates is one and control$size is ten, the returned design will have 12 points (12 rows). The first two rows will be identical to x. Only the remaining ten rows are -guaranteed to be a valid LHD.

    lower

    vector with lower boundary of the design variables (in case +guaranteed to be a valid LHD.

    +
    lower
    +

    vector with lower boundary of the design variables (in case of categorical parameters, please map the respective factor to a set of -contiguous integers, e.g., with lower = 1 and upper = number of levels)

    upper

    vector with upper boundary of the design variables (in case +contiguous integers, e.g., with lower = 1 and upper = number of levels)

    +
    upper
    +

    vector with upper boundary of the design variables (in case of categorical parameters, please map the respective factor to a set of -contiguous integers, e.g., with lower = 1 and upper = number of levels)

    control

    list of controls: see designLHD

    - -

    See also

    - -

    designLHD

    +contiguous integers, e.g., with lower = 1 and upper = number of levels)

    +
    control
    +

    list of controls: see designLHD

    +
    +
    +

    See also

    +

    designLHD

    +
    +
    - - - + + diff --git a/docs/reference/divLeinster.html b/docs/reference/divLeinster.html index 22931f0..fe7014d 100644 --- a/docs/reference/divLeinster.html +++ b/docs/reference/divLeinster.html @@ -1,69 +1,14 @@ - - - - - - - -Find cutoff to transform abundance values into binary values — divLeinster • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Find cutoff to transform abundance values into binary values — divLeinster • RFate - - - - - - - - - + + - - - - -
    -
    - -
    - -
    +

    This function calculates the diversity of each site of a site by species matrix according to the q parameter according to -Leinster & Cobbold 2012 Ecology.

    +Leinster & Cobbold 2012 Ecology.

    -
    divLeinster(spxp, Z = NULL, q = 2, check = TRUE)
    - -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    spxp

    a site (row) by species (cols) matrix with or without -rownames and colnames

    Z

    default NULL.
    A species by species similarity -matrix

    q

    default 2.
    An integer corresponding to the -importance attributed to relative abundances

    check

    (optional) default TRUE.
    If TRUE, the -given arguments will be checked

    +
    +
    divLeinster(spxp, Z = NULL, q = 2, check = TRUE)
    +
    +
    +

    Arguments

    +
    spxp
    +

    a site (row) by species (cols) matrix with or without +rownames and colnames

    +
    Z
    +

    default NULL.
    A species by species similarity +matrix

    +
    q
    +

    default 2.
    An integer corresponding to the +importance attributed to relative abundances

    +
    check
    +

    (optional) default TRUE.
    If TRUE, the +given arguments will be checked

    +
    +
    -
    - - + + diff --git a/docs/reference/dot-adaptMaps.html b/docs/reference/dot-adaptMaps.html index 877e765..c174816 100644 --- a/docs/reference/dot-adaptMaps.html +++ b/docs/reference/dot-adaptMaps.html @@ -1,71 +1,16 @@ - - - - - - - -Adapt all raster maps of a FATE simulation folder (change NA -to 0, and save as .tif) — .adaptMaps • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Adapt all raster maps of a FATE simulation folder (change NA +to 0, and save as .tif) — .adaptMaps • RFate - - - - - - - - - - + + - - - -
    -
    - -
    - -
    +
    @@ -211,79 +145,66 @@

    Adapt all raster maps of a FATE simulation folder (change NA a raster mask and save them with the specified extension.

    -
    .adaptMaps(
    -  name.simulation,
    -  opt.name.file = NULL,
    -  extension.old,
    -  extension.new = NULL,
    -  opt.name.MASK = NULL
    -)
    +
    +
    .adaptMaps(
    +  name.simulation,
    +  opt.name.file = NULL,
    +  extension.old,
    +  extension.new = NULL,
    +  opt.name.MASK = NULL
    +)
    +
    -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    opt.name.file

    (optional)
    a string corresponding +

    +

    Arguments

    +
    name.simulation
    +

    a string corresponding to the main directory +or simulation name of the FATE simulation

    +
    opt.name.file
    +

    (optional)
    a string corresponding to the complete or partial name of the file in which to search and change -the pattern

    extension.old

    a string corresponding to the extension of -raster files to be found

    extension.new

    (optional)
    a string (either +the pattern

    +
    extension.old
    +

    a string corresponding to the extension of +raster files to be found

    +
    extension.new
    +

    (optional)
    a string (either tif or img) corresponding to the new extension to save all -the maps

    opt.name.MASK

    (optional) default NULL.
    +the maps

    +
    opt.name.MASK
    +

    (optional) default NULL.
    A string corresponding to the file name of a raster mask, with either 0 or 1 within each pixel, 1 corresponding to the cells of the studied area in which the succession (core) module of the FATE simulation will take place (see -PRE_FATE.params_globalParameters)

    - -

    Author

    - +PRE_FATE.params_globalParameters)

    +
    +
    +

    Author

    Maya Guéguen

    +
    +
    - - - + + diff --git a/docs/reference/dot-createParams.html b/docs/reference/dot-createParams.html index c15f02d..1d917c5 100644 --- a/docs/reference/dot-createParams.html +++ b/docs/reference/dot-createParams.html @@ -1,68 +1,13 @@ - - - - - - - -Create a parameters file — .createParams • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create a parameters file — .createParams • RFate - + + - - - -
    -
    - -
    - -
    +
    @@ -206,58 +140,49 @@

    Create a parameters file

    parameters and separated by a same character value.

    -
    .createParams(params.file, params.list, separator = " ")
    +
    +
    .createParams(params.file, params.list, separator = " ")
    +
    -

    Arguments

    - - - - - - - - - - - - - - -
    params.file

    a string corresponding to the name of the file -that will be created

    params.list

    a list containing all the parameters that will be +

    +

    Arguments

    +
    params.file
    +

    a string corresponding to the name of the file +that will be created

    +
    params.list
    +

    a list containing all the parameters that will be included into params.file, and whose names correspond to the -parameter names

    separator

    a string to separate each parameter values within -the parameter file

    - -

    Author

    - +parameter names

    +
    separator
    +

    a string to separate each parameter values within +the parameter file

    +
    +
    +

    Author

    Maya Guéguen

    +
    +
    - - - + + diff --git a/docs/reference/dot-getCutoff.html b/docs/reference/dot-getCutoff.html index fd8d298..b3d53e5 100644 --- a/docs/reference/dot-getCutoff.html +++ b/docs/reference/dot-getCutoff.html @@ -1,69 +1,14 @@ - - - - - - - -Find cutoff to transform abundance values into binary values — .getCutoff • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Find cutoff to transform abundance values into binary values — .getCutoff • RFate - - - - - - - - - - - + + - - -
    -
    - -
    - -
    +
    @@ -208,63 +142,58 @@

    Find cutoff to transform abundance values into binary values

    values based on observations

    -
    .getCutoff(Obs, Fit)
    - -

    Arguments

    - - - - - - - - - - -
    Obs

    a vector containing binary observed values (0 or -1)

    Fit

    a vector containing relative abundance values (between -0 and 1)

    - -

    Author

    +
    +
    .getCutoff(Obs, Fit)
    +
    +
    +

    Arguments

    +
    Obs
    +

    a vector containing binary observed values (0 or +1)

    +
    Fit
    +

    a vector containing relative abundance values (between +0 and 1)

    +
    +
    +

    Author

    Damien Georges

    +
    -

    Examples

    -
    -vec.obs = c(rep(0, 60), rep(1, 40))
    -vec.pred = c(sample(x = seq(0, 0.2, 0.01), size = 50, replace = TRUE)
    -             , sample(x = seq(0.15, 1, 0.01), size = 50, replace = TRUE))
    -## plot(vec.obs, vec.pred)
    +    
    +

    Examples

    +
    
    +vec.obs = c(rep(0, 60), rep(1, 40))
    +vec.pred = c(sample(x = seq(0, 0.2, 0.01), size = 50, replace = TRUE)
    +             , sample(x = seq(0.15, 1, 0.01), size = 50, replace = TRUE))
    +## plot(vec.obs, vec.pred)
     
    -cutoff = .getCutoff(Obs = vec.obs, Fit = vec.pred)
    -str(cutoff)
    +cutoff = .getCutoff(Obs = vec.obs, Fit = vec.pred)
    +str(cutoff)
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/dot-getELLIPSE.html b/docs/reference/dot-getELLIPSE.html index e7fa8f3..4cb4239 100644 --- a/docs/reference/dot-getELLIPSE.html +++ b/docs/reference/dot-getELLIPSE.html @@ -1,67 +1,12 @@ - - - - - - - -Obtain ellipse coordinates from (PCO) X,Y and a factor value — .getELLIPSE • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Obtain ellipse coordinates from (PCO) X,Y and a factor value — .getELLIPSE • RFate + + - - - - -
    -
    - -
    - -
    +
    @@ -204,93 +138,67 @@

    Obtain ellipse coordinates from (PCO) X,Y and a factor value

    Obtain ellipse coordinates from (PCO) X,Y and a factor value

    -
    util.ELLIPSE2(mx, my, vx, cxy, vy, coeff)
    -util.ELLIPSE1(x, y, z)
    -.getELLIPSE(xy, fac)
    +
    +
    util.ELLIPSE2(mx, my, vx, cxy, vy, coeff)
    +util.ELLIPSE1(x, y, z)
    +.getELLIPSE(xy, fac)
    +
    -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    mx

    \(\Sigma x * \frac{z}{\Sigma z}\)

    my

    \(\Sigma y * \frac{z}{\Sigma z}\)

    vx

    \(\Sigma (x - mx) * (x - mx) * \frac{z}{\Sigma z}\)

    cxy

    \(\Sigma (x - mx) * (y - my) * \frac{z}{\Sigma z}\)

    vy

    \(\Sigma (y - my) * (y - my) * \frac{z}{\Sigma z}\)

    coeff

    default 1

    xy

    a data.frame or matrix with 2 columns corresponding +

    +

    Arguments

    +
    mx
    +

    \(\Sigma x * \frac{z}{\Sigma z}\)

    +
    my
    +

    \(\Sigma y * \frac{z}{\Sigma z}\)

    +
    vx
    +

    \(\Sigma (x - mx) * (x - mx) * \frac{z}{\Sigma z}\)

    +
    cxy
    +

    \(\Sigma (x - mx) * (y - my) * \frac{z}{\Sigma z}\)

    +
    vy
    +

    \(\Sigma (y - my) * (y - my) * \frac{z}{\Sigma z}\)

    +
    coeff
    +

    default 1

    +
    xy
    +

    a data.frame or matrix with 2 columns corresponding to individuals coordinates, extracted from example from -dudi.pco analysis

    fac

    a vector containing group labels for individuals (with -length(fac) = nrow(xy))

    x

    a vector corresponding to abscissa coordinates of -individuals (column 1 of xy)

    y

    a vector corresponding to ordinate coordinates of -individuals (column 2 of xy)

    z

    a data.frame with one column for each level represented in +dudi.pco analysis

    +
    fac
    +

    a vector containing group labels for individuals (with +length(fac) = nrow(xy))

    +
    x
    +

    a vector corresponding to abscissa coordinates of +individuals (column 1 of xy)

    +
    y
    +

    a vector corresponding to ordinate coordinates of +individuals (column 2 of xy)

    +
    z
    +

    a data.frame with one column for each level represented in fac and nrow(z) = length(fac) = nrow(xy). Values are corresponding to the relative representation of each level (for level -i : \(\frac{1}{N_i}\))

    - +i : \(\frac{1}{N_i}\))

    +
    +
    - - - + + diff --git a/docs/reference/dot-getOS.html b/docs/reference/dot-getOS.html index 418c0d8..43c7f90 100644 --- a/docs/reference/dot-getOS.html +++ b/docs/reference/dot-getOS.html @@ -1,69 +1,14 @@ - - - - - - - -Find operating system of your computer — .getOS • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Find operating system of your computer — .getOS • RFate - - - - - - - - - + + - - - - -
    -
    - -
    - -
    +
    @@ -208,35 +142,32 @@

    Find operating system of your computer

    parallelisation.

    -
    .getOS()
    - +
    +
    .getOS()
    +
    +
    - - - + + diff --git a/docs/reference/dot-getParam.html b/docs/reference/dot-getParam.html index 15b69b5..47ed3db 100644 --- a/docs/reference/dot-getParam.html +++ b/docs/reference/dot-getParam.html @@ -1,68 +1,13 @@ - - - - - - - -Extract parameter value(s) from a parameter file — .getParam • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Extract parameter value(s) from a parameter file — .getParam • RFate - - + + - - -
    -
    - -
    - -
    +
    @@ -206,98 +140,89 @@

    Extract parameter value(s) from a parameter file

    given parameter.

    -
    .getParam(params.lines, flag, flag.split, is.num = TRUE)
    +
    +
    .getParam(params.lines, flag, flag.split, is.num = TRUE)
    +
    -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    params.lines

    a string corresponding to the name of the file -from which to extract the parameter value

    flag

    a string corresponding to the parameter name to be -extracted and that must be present into the param.lines file

    flag.split

    a string to choose the concerned type of parameter +

    +

    Arguments

    +
    params.lines
    +

    a string corresponding to the name of the file +from which to extract the parameter value

    +
    flag
    +

    a string corresponding to the parameter name to be +extracted and that must be present into the param.lines file

    +
    flag.split
    +

    a string to choose the concerned type of parameter (either " " or "^--.*--$"), depending on the type of parameter -file (containing values or filenames)

    is.num

    default TRUE.
    If TRUE, the extracted -parameter is considered to be numeric and will be processed as such

    - -

    Value

    - -

    A vector containing one or more values of type string -(if is.num = FALSE) or numeric (if is.num = TRUE).

    -

    Author

    - +file (containing values or filenames)

    +
    is.num
    +

    default TRUE.
    If TRUE, the extracted +parameter is considered to be numeric and will be processed as such

    +
    +
    +

    Value

    +

    A vector containing one or more values of type string(if is.num = FALSE) or numeric (if is.num = TRUE).

    +
    +
    +

    Author

    Damien Georges, Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation')
    -if (dir.exists("FATE_simulation")) unlink("FATE_simulation", recursive = TRUE)
    -PRE_FATE.skeletonDirectory()
    -
    -## Create a Global_parameters file
    -PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
    -                                 , required.no_PFG = 6
    -                                 , required.no_strata = 5
    -                                 , required.simul_duration = 100
    -                                 , required.seeding_duration = c(10,50)
    -                                 , required.seeding_timestep = 1
    -                                 , required.seeding_input = 100
    -                                 , required.max_abund_low = 30000
    -                                 , required.max_abund_medium = 50000
    -                                 , required.max_abund_high = 90000)
    +    
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation')
    +if (dir.exists("FATE_simulation")) unlink("FATE_simulation", recursive = TRUE)
    +PRE_FATE.skeletonDirectory()
    +
    +## Create a Global_parameters file
    +PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
    +                                 , required.no_PFG = 6
    +                                 , required.no_strata = 5
    +                                 , required.simul_duration = 100
    +                                 , required.seeding_duration = c(10,50)
    +                                 , required.seeding_timestep = 1
    +                                 , required.seeding_input = 100
    +                                 , required.max_abund_low = 30000
    +                                 , required.max_abund_medium = 50000
    +                                 , required.max_abund_high = 90000)
                                      
    -## Extract number of PFG
    -.getParam(params.lines = "FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt"
    -          , flag = "NO_PFG"
    -          , flag.split = " "
    -          , is.num = TRUE)
    +## Extract number of PFG
    +.getParam(params.lines = "FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt"
    +          , flag = "NO_PFG"
    +          , flag.split = " "
    +          , is.num = TRUE)
     
     
    -## ----------------------------------------------------------------------------------------- ##
    +## ----------------------------------------------------------------------------------------- ##
                                     
    -## Load example data
    +## Load example data
               
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/dot-loadData.html b/docs/reference/dot-loadData.html index fefb7e2..bafc374 100644 --- a/docs/reference/dot-loadData.html +++ b/docs/reference/dot-loadData.html @@ -1,79 +1,32 @@ - - - - - - +Load a dataset — .loadData • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Champsaur_params +all necessary files to build the FATE + simulation folder as well as the parameter files + Champsaur_simul_[...] +simulation folder and outputs obtained + from FATE simulation + Champsaur_results_[...] +results obtained from outputs and + POST_FATE functions - - - - - - + + - - -
    -
    - -
    - -
    +

    This function loads one of the available datasets :

    -
    -
    Champsaur_PFG

    elements to create Plant Functional Groups (PFG) - over Champsaur valley, in Ecrins National Park (PNE)

    -
    Champsaur_params

    all necessary files to build the FATE - simulation folder as well as the parameter files

    -
    Champsaur_simul_[...]

    simulation folder and outputs obtained +

    Champsaur_PFG
    +

    elements to create Plant Functional Groups (PFG) + over Champsaur valley, in Ecrins National Park (PNE)

    + +
    Champsaur_params
    +

    all necessary files to build the FATE + simulation folder as well as the parameter files

    + +
    Champsaur_simul_[...]
    +

    simulation folder and outputs obtained from FATE simulation

    -
    Champsaur_results_[...]

    results obtained from outputs and - POST_FATE functions

    -
    +
    Champsaur_results_[...]
    +

    results obtained from outputs and + POST_FATE functions

    + +
    + +
    +
    .loadData(data.name, format.name)
    -
    .loadData(data.name, format.name)
    - -

    Arguments

    - - - - - - - - - - -
    data.name

    a string corresponding to the name of the dataset -that will be loaded

    format.name

    a string corresponding to the downloading format way -(either RData or 7z)

    - -

    Value

    - -

    Note : references to habitat refer to habitat map and classes from -CESBIO2018. -Some class gathering has been made, as follow :

    -
      -
    • 0 - Other : 0, 21, 22, 23

    • +
      +

      Arguments

      +
      data.name
      +

      a string corresponding to the name of the dataset +that will be loaded

      +
      format.name
      +

      a string corresponding to the downloading format way +(either RData or 7z)

      +
      +
      +

      Value

      +

      Note :references to habitat refer to habitat map and classes from +CESBIO2018. +Some class gathering has been made, as follow :

      • 0 - Other : 0, 21, 22, 23

      • 1 - Urban : 1, 2, 3, 4

      • 2 - Crops : 5, 6, 7, 8, 9, 10, 11, 12

      • 3 - Prairies : 13

      • @@ -260,45 +201,42 @@

        Value

      • 6 - Coniferous forests : 17

      • 7 - Natural grasslands : 18

      • 8 - Woody heathlands : 19

      • -
      • 9 - Mineral surfaces : 20

      • -
      - - -



      #########################################################
      ->>> Champsaur_PFG -
      #########################################################

      -

      A list object with 18 elements to help building the Plant Functional -Group :

      -

      -
      name.dataset

      Champsaur

      -
      sp.observations

      a data.frame of dimension - 127257 x 6
      - containing releves data about plant species in Champsaur
      - to be used with the PRE_FATE.selectDominant function -

        -
      • sites : sites ID

      • +
      • 9 - Mineral surfaces : 20

      • +



      #########################################################
      >>> Champsaur_PFG
      #########################################################

      A list object with 18 elements to help building the Plant Functional +Group :

      name.dataset
      +

      Champsaur

      + +
      sp.observations
      +

      a data.frame of dimension + 127257 x 6
      + containing releves data about plant species in Champsaur
      + to be used with the PRE_FATE.selectDominant function

      • sites : sites ID

      • species : species ID

      • abundBB : Braun-Blanquet abundance

      • abund : relative abundance obtained from the abund_BB column with the - PRE_FATE.abundBraunBlanquet function

      • + PRE_FATE.abundBraunBlanquet function

      • habitat : habitat classes (gathered) from CESBIO2018

      • TYPE : type of records, exhaustive (COMMUNITY) or single occurrences (OCCURRENCE)

      • -

      -
      rules.selectDominant

      a vector containing parameters to be - given to the PRE_FATE.selectDominant function
      +

    + +
    rules.selectDominant
    +

    a vector containing parameters to be + given to the PRE_FATE.selectDominant function
    (doRuleA, rule.A1, rule.A2_quantile, doRuleB, rule.B1_percentage, rule.B1_number, rule.B2, doRuleC)

    -
    sp.SELECT

    a list obtained from the - PRE_FATE.selectDominant function run with + +

    sp.SELECT
    +

    a list obtained from the + PRE_FATE.selectDominant function run with sp.observations and rules.selectDominant

    -
    sp.traits

    a data.frame of dimension 250 x 23
    - containing traits for dominant species
    - to be used with the PRE_FATE.speciesDistance function -

      -
    • species : species ID

    • + +
      sp.traits
      +

      a data.frame of dimension 250 x 23
      + containing traits for dominant species
      + to be used with the PRE_FATE.speciesDistance function

      • species : species ID

      • GROUP : rough generalization of Raunkier life-forms (Phanerophyte, Chamaephyte, Herbaceous)

      • MATURITY / LONGEVITY / HEIGHT / LDMC / LNC / SEEDM / @@ -308,66 +246,86 @@

        Value

      • DISPERSAL / LIGHT / NITROGEN / NITROGEN_TOLERANCE / MOISTURE / GRAZ_MOW_TOLERANCE / HABITAT / STRATEGY : categorical values

      • -

      -
      sp.traits.P

      a data.frame of dimension 20 x 9
      +

    + +
    sp.traits.P
    +

    a data.frame of dimension 20 x 9
    a subset of sp.traits (LONGEVITY_log / HEIGHT_log / SEEDM_log / SLA_log / DISPERSAL / LIGHT / NITROGEN) for Phanerophyte dominant - species
    - to be used with the PRE_FATE.speciesDistance function

    -
    sp.traits.C

    a data.frame of dimension 38 x 6
    + species
    + to be used with the PRE_FATE.speciesDistance function

    + +
    sp.traits.C
    +

    a data.frame of dimension 38 x 6
    a subset of sp.traits (HEIGHT_log / SEEDM_log / LIGHT / - NITROGEN) for Chamaephyte dominant species
    - to be used with the PRE_FATE.speciesDistance function

    -
    sp.traits.H

    a data.frame of dimension 192 x 7
    + NITROGEN) for Chamaephyte dominant species
    + to be used with the PRE_FATE.speciesDistance function

    + +
    sp.traits.H
    +

    a data.frame of dimension 192 x 7
    a subset of sp.traits (HEIGHT_log / LDMC_log / - LNC_log / SLA_log / LIGHT) for Herbaceous dominant species
    - to be used with the PRE_FATE.speciesDistance function

    -
    mat.habitat

    matrix of dimension 240 x 240
    + LNC_log / SLA_log / LIGHT) for Herbaceous dominant species
    + to be used with the PRE_FATE.speciesDistance function

    + +
    mat.habitat
    +

    matrix of dimension 240 x 240
    containing habitat dissimilarity distance (1 - Schoeners' D, obtained - with gowdis) for dominant species
    - to be used with the PRE_FATE.speciesDistance function

    -
    tab.dom.PA

    matrix of dimension 13654 x 264
    + with gowdis) for dominant species
    + to be used with the PRE_FATE.speciesDistance function

    + +
    tab.dom.PA
    +

    matrix of dimension 13654 x 264
    containing dominant species occurrences and absences (obtained from sp.SELECT object and absences corrected with the TYPE information of sp.observations), to build mat.overlap

    -
    tab.env

    matrix of dimension 13590 x 5
    + +

    tab.env
    +

    matrix of dimension 13590 x 5
    containing environmental values (bio1, bio12, slope, dem, CESBIO2018) for sites, to build mat.overlap

    -
    mat.overlap

    matrix of dimension 244 x 244
    + +

    mat.overlap
    +

    matrix of dimension 244 x 244
    containing niche overlap distance (1 - Schoeners' D, obtained with - ecospat.niche.overlap) for dominant species
    - to be used with the PRE_FATE.speciesDistance function

    -
    sp.DIST.P

    a list obtained from the - PRE_FATE.speciesDistance function run for Phanerophyte + ecospat.niche.overlap) for dominant species
    + to be used with the PRE_FATE.speciesDistance function

    + +
    sp.DIST.P
    +

    a list obtained from the + PRE_FATE.speciesDistance function run for Phanerophyte dominant species with mat.habitat, mat.overlap and sp.traits.P parameters

    -
    sp.DIST.C

    a list obtained from the - PRE_FATE.speciesDistance function run for Chamaephyte + +

    sp.DIST.C
    +

    a list obtained from the + PRE_FATE.speciesDistance function run for Chamaephyte dominant species with mat.habitat, mat.overlap and sp.traits.C parameters

    -
    sp.DIST.H

    a list obtained from the - PRE_FATE.speciesDistance function run for Herbaceous + +

    sp.DIST.H
    +

    a list obtained from the + PRE_FATE.speciesDistance function run for Herbaceous dominant species with mat.habitat, mat.overlap and sp.traits.H parameters

    -
    PFG.species

    data.frame of dimension 224 x 5
    - containing dominant species information relative to PFG
    - obtained from the PRE_FATE.speciesClustering_step2 - function -

      -
    • PFG : name of assigned Plant Functional Group

    • + +
      PFG.species
      +

      data.frame of dimension 224 x 5
      + containing dominant species information relative to PFG
      + obtained from the PRE_FATE.speciesClustering_step2 + function

      • PFG : name of assigned Plant Functional Group

      • DETERMINANT : is the species kept as determinant - species within the PFG ?
        (see - PRE_FATE.speciesClustering_step2 function for details)

      • + species within the PFG ?
        (see + PRE_FATE.speciesClustering_step2 function for details)

      • species : species ID

      • species_name : species name (taxonomic)

      • species_genus : species genus (taxonomic)

      • -

      -
      PFG.traits

      data.frame of dimension 15 x 12
      - containing traits for plant functional groups
      - obtained from the PRE_FATE.speciesClustering_step3 - function -

        -
      • PFG : Plant Functional Group short name

      • +

      + +
      PFG.traits
      +

      data.frame of dimension 15 x 12
      + containing traits for plant functional groups
      + obtained from the PRE_FATE.speciesClustering_step3 + function

      • PFG : Plant Functional Group short name

      • no.species : number of species within each group

      • maturity : MEAN age of maturity

      • longevity : MEAN age of lifespan

      • @@ -384,124 +342,124 @@

        Value

        preference (based on Landolt indicators, from 1 to 5)

      • LDMC : MEAN LDMC

      • LNC : MEAN LNC

      • -

      -
      PFG.PA

      data.frame of dimension 13654 x 15
      +

    + +
    PFG.PA
    +

    data.frame of dimension 13654 x 15
    containing PFG occurrences and absences obtained from tab.dom.PA object and the - PRE_FATE.speciesClustering_step3 function

    - - + PRE_FATE.speciesClustering_step3 function

    + -



    #########################################################
    ->>> Champsaur_params -
    #########################################################

    -

    A list object with 12 elements to help building the simulation -files and folders to run a FATE simulation :

    -

    -
    name.dataset

    Champsaur

    -
    tab.occ

    a data.frame of dimension 13590 x 15
    +



    #########################################################
    >>> Champsaur_params
    #########################################################

    A list object with 12 elements to help building the simulation +files and folders to run a FATE simulation :

    name.dataset
    +

    Champsaur

    + +
    tab.occ
    +

    a data.frame of dimension 13590 x 15
    containing presence / absence (NA, 0 or 1) values per site for each PFG

    -
    tab.env

    a data.frame of dimension 13590 x 5
    + +

    tab.env
    +

    a data.frame of dimension 13590 x 5
    containing environmental values (bio1, bio12, slope, dem, CESBIO2018) for each site

    -
    tab.xy

    a data.frame of dimension 13590 x 2
    + +

    tab.xy
    +

    a data.frame of dimension 13590 x 2
    containing coordinates for each site

    -
    stk.var

    a stack object of dimension + +

    stk.var
    +

    a stack object of dimension 358 x 427 with a resolution of 100m and ETRS89 projection, containing 3 layers with environmental values (bio1, slope, CESBIO2018) and to be used to produce PFG SDM

    -
    tab.SUCC

    a data.frame of dimension 15 x 5
    - containing PFG characteristics (type, height, maturity, longevity)
    - to be used with the PRE_FATE.params_PFGsuccession + +

    tab.SUCC
    +

    a data.frame of dimension 15 x 5
    + containing PFG characteristics (type, height, maturity, longevity)
    + to be used with the PRE_FATE.params_PFGsuccession function

    -
    tab.DISP

    a data.frame of dimension 15 x 4
    - containing PFG characteristics (d50, d99, ldd)
    - to be used with the PRE_FATE.params_PFGdispersal + +

    tab.DISP
    +

    a data.frame of dimension 15 x 4
    + containing PFG characteristics (d50, d99, ldd)
    + to be used with the PRE_FATE.params_PFGdispersal function

    -
    tab.DIST

    a data.frame of dimension 15 x 3
    - containing PFG characteristics (nameDist, strategy_tol)
    - to be used with the PRE_FATE.params_PFGdisturbance + +

    tab.DIST
    +

    a data.frame of dimension 15 x 3
    + containing PFG characteristics (nameDist, strategy_tol)
    + to be used with the PRE_FATE.params_PFGdisturbance function

    -
    tab.LIGHT

    a data.frame of dimension 15 x 3
    - containing PFG characteristics (type, strategy_tol)
    - to be used with the PRE_FATE.params_PFGlight + +

    tab.LIGHT
    +

    a data.frame of dimension 15 x 3
    + containing PFG characteristics (type, strategy_tol)
    + to be used with the PRE_FATE.params_PFGlight function

    -
    tab.SOIL

    a data.frame of dimension 15 x 5
    + +

    tab.SOIL
    +

    a data.frame of dimension 15 x 5
    containing PFG characteristics (type, soil_contrib, soil_tol_min, - soil_tol_max)
    - to be used with the PRE_FATE.params_PFGsoil + soil_tol_max)
    + to be used with the PRE_FATE.params_PFGsoil function

    -
    stk.wmean

    a stack object of dimension + +

    stk.wmean
    +

    a stack object of dimension 358 x 427 with a resolution of 100m and ETRS89 projection, containing 15 layers with habitat suitability values (between 0 and 1) for each PFG

    -
    stk.mask

    a stack object of dimension + +

    stk.mask
    +

    a stack object of dimension 358 x 427 with a resolution of 100m and ETRS89 projection, containing 4 mask layers with binary values (0 or 1) or - categorical values (habitat) to be used in a FATE simulation : -

      -
    • Champsaur : simulation map, where occurs succession

    • + categorical values (habitat) to be used in a FATE simulation :

      • Champsaur : simulation map, where occurs succession

      • noDisturb : perturbation map, when there is none

      • mowing : perturbation map, where occurs mowing

      • habitat : habitat map, from CESBIO2018 (gathered)

      • -

    - -
    +

    -



    #########################################################
    ->>> Champsaur_simul_[...] -
    #########################################################

    -

    A 7z file containing one FATE simulation result folder.
    -4 simulations are available :

    -
      -
    1. a simulation with only basic modules activated :
      CORE +



      #########################################################
      >>> Champsaur_simul_[...]
      #########################################################

      A 7z file containing one FATE simulation result folder.
      4 simulations are available :

      1. a simulation with only basic modules activated :
        CORE (succession) module, dispersal module, and habitat suitability module

      2. the same as V1 + the light module

      3. the same as V1 + the soil module

      4. the same as V1 + the light and soil modules

      5. -
      - - -



      #########################################################
      ->>> Champsaur_results_[...] -
      #########################################################

      -

      A 7z file containing .csv and .pdf files -obtained from the corresponding FATE simulation result folder +



    #########################################################
    >>> Champsaur_results_[...]
    #########################################################

    A 7z file containing .csv and .pdf filesobtained from the corresponding FATE simulation result folder (4 simulations available) with the help of -POST_FATE functions.

    -

    Author

    - +POST_FATE functions.

    +
    +
    +

    Author

    Maya Guéguen

    +
    +
    - - - + + diff --git a/docs/reference/dot-loadPackage.html b/docs/reference/dot-loadPackage.html index f2486a3..d9918d1 100644 --- a/docs/reference/dot-loadPackage.html +++ b/docs/reference/dot-loadPackage.html @@ -1,68 +1,13 @@ - - - - - - - -Load a R package and install it if necessary — .loadPackage • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Load a R package and install it if necessary — .loadPackage • RFate + + - - - - -
    -
    - -
    - -
    +
    @@ -206,44 +140,38 @@

    Load a R package and install it if necessary

    necessary.

    -
    .loadPackage(package.name)
    - -

    Arguments

    - - - - - - -
    package.name

    a string corresponding to the name of the package -that will be loaded or installed

    +
    +
    .loadPackage(package.name)
    +
    +
    +

    Arguments

    +
    package.name
    +

    a string corresponding to the name of the package +that will be loaded or installed

    +
    +
    - - - + + diff --git a/docs/reference/dot-scaleMaps.html b/docs/reference/dot-scaleMaps.html index 55d886c..d5342a7 100644 --- a/docs/reference/dot-scaleMaps.html +++ b/docs/reference/dot-scaleMaps.html @@ -1,71 +1,16 @@ - - - - - - - -Upscale / downscale / crop all raster maps of a FATE -simulation folder — .scaleMaps • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Upscale / downscale / crop all raster maps of a FATE +simulation folder — .scaleMaps • RFate - - - - - - - - - - + + - - - -
    -
    - -
    - -
    +
    @@ -211,58 +145,49 @@

    Upscale / downscale / crop all raster maps of a FATE to the specified resolution / extent.

    -
    .scaleMaps(name.simulation, resolution)
    -.cropMaps(name.simulation, extent)
    - -

    Arguments

    - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    resolution

    an integer corresponding to the new resolution to -upscale/downscale all the maps

    extent

    a vector of 4 numeric values corresponding to -the new extent to crop all the maps

    - -

    Author

    +
    +
    .scaleMaps(name.simulation, resolution)
    +.cropMaps(name.simulation, extent)
    +
    +
    +

    Arguments

    +
    name.simulation
    +

    a string corresponding to the main directory +or simulation name of the FATE simulation

    +
    resolution
    +

    an integer corresponding to the new resolution to +upscale/downscale all the maps

    +
    extent
    +

    a vector of 4 numeric values corresponding to +the new extent to crop all the maps

    +
    +
    +

    Author

    Maya Guéguen

    +
    +
    - - - + + diff --git a/docs/reference/dot-setParam.html b/docs/reference/dot-setParam.html index 6a5a8cd..7559113 100644 --- a/docs/reference/dot-setParam.html +++ b/docs/reference/dot-setParam.html @@ -1,68 +1,13 @@ - - - - - - - -Replace parameter value(s) from a parameter file — .setParam • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Replace parameter value(s) from a parameter file — .setParam • RFate - - - - + + -
    -
    - -
    - -
    +
    @@ -206,98 +140,89 @@

    Replace parameter value(s) from a parameter file

    parameter, and replace it with new value(s).

    -
    .setParam(params.lines, flag, flag.split, value)
    +
    +
    .setParam(params.lines, flag, flag.split, value)
    +
    -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    params.lines

    a string corresponding to the name of the file -from which to replace the parameter value

    flag

    a string corresponding to the parameter name to be -extracted and that must be present into the param.lines file

    flag.split

    a string to choose the concerned type of parameter +

    +

    Arguments

    +
    params.lines
    +

    a string corresponding to the name of the file +from which to replace the parameter value

    +
    flag
    +

    a string corresponding to the parameter name to be +extracted and that must be present into the param.lines file

    +
    flag.split
    +

    a string to choose the concerned type of parameter (either " " or "^--.*--$"), depending on the type of parameter -file (containing values or filenames)

    value

    a string or a numeric value (it can also be a -vector) containing the new value of the parameter to be changed

    - -

    Author

    - +file (containing values or filenames)

    +
    value
    +

    a string or a numeric value (it can also be a +vector) containing the new value of the parameter to be changed

    +
    +
    +

    Author

    Damien Georges, Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation')
    -if (dir.exists("FATE_simulation")) unlink("FATE_simulation", recursive = TRUE)
    -PRE_FATE.skeletonDirectory()
    -
    -## Create a Global_parameters file
    -PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
    -                                 , required.no_PFG = 6
    -                                 , required.no_strata = 5
    -                                 , required.simul_duration = 100
    -                                 , required.seeding_duration = c(10,50)
    -                                 , required.seeding_timestep = 1
    -                                 , required.seeding_input = 100
    -                                 , required.max_abund_low = 30000
    -                                 , required.max_abund_medium = 50000
    -                                 , required.max_abund_high = 90000)
    +    
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation')
    +if (dir.exists("FATE_simulation")) unlink("FATE_simulation", recursive = TRUE)
    +PRE_FATE.skeletonDirectory()
    +
    +## Create a Global_parameters file
    +PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
    +                                 , required.no_PFG = 6
    +                                 , required.no_strata = 5
    +                                 , required.simul_duration = 100
    +                                 , required.seeding_duration = c(10,50)
    +                                 , required.seeding_timestep = 1
    +                                 , required.seeding_input = 100
    +                                 , required.max_abund_low = 30000
    +                                 , required.max_abund_medium = 50000
    +                                 , required.max_abund_high = 90000)
                                      
    -readLines("FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt")
    +readLines("FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt")
                                      
    -## Change number of PFG
    -.setParam(params.lines = "FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt"
    -          , flag = "NO_PFG"
    -          , flag.split = " "
    -          , value = 14)
    +## Change number of PFG
    +.setParam(params.lines = "FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt"
    +          , flag = "NO_PFG"
    +          , flag.split = " "
    +          , value = 14)
               
    -readLines("FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt")
    +readLines("FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt")
     
     
    -## ----------------------------------------------------------------------------------------- ##
    +## ----------------------------------------------------------------------------------------- ##
                                     
    -## Load example data
    +## Load example data
     
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/dot-setPattern.html b/docs/reference/dot-setPattern.html index bbc01e4..05d4761 100644 --- a/docs/reference/dot-setPattern.html +++ b/docs/reference/dot-setPattern.html @@ -1,70 +1,15 @@ - - - - - - - -Replace a pattern with a new within all parameter files of a -FATE simulation folder — .setPattern • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Replace a pattern with a new within all parameter files of a +FATE simulation folder — .setPattern • RFate - - - - + + -
    -
    - -
    - -
    +
    @@ -209,103 +143,94 @@

    Replace a pattern with a new within all parameter files of a simulation folder to find a specific pattern and replace it with a new one

    -
    .setPattern(
    -  name.simulation,
    -  opt.name.file = NULL,
    -  pattern.tofind,
    -  pattern.toreplace
    -)
    +
    +
    .setPattern(
    +  name.simulation,
    +  opt.name.file = NULL,
    +  pattern.tofind,
    +  pattern.toreplace
    +)
    +
    -

    Arguments

    - - - - - - - - - - - - - - - - - - -
    name.simulation

    a string corresponding to the main directory -or simulation name of the FATE simulation

    opt.name.file

    (optional)
    a string corresponding +

    +

    Arguments

    +
    name.simulation
    +

    a string corresponding to the main directory +or simulation name of the FATE simulation

    +
    opt.name.file
    +

    (optional)
    a string corresponding to the complete or partial name of the file in which to search and change -the pattern

    pattern.tofind

    a string corresponding to the pattern to find

    pattern.toreplace

    a string corresponding to the pattern to -replace

    - -

    Author

    - +the pattern

    +
    pattern.tofind
    +

    a string corresponding to the pattern to find

    +
    pattern.toreplace
    +

    a string corresponding to the pattern to +replace

    +
    +
    +

    Author

    Maya Guéguen

    +
    -

    Examples

    -
    -## Create a skeleton folder with the default name ('FATE_simulation')
    -if (dir.exists("FATE_simulation")) unlink("FATE_simulation", recursive = TRUE)
    -PRE_FATE.skeletonDirectory()
    -
    -## Create a Global_parameters file
    -PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
    -                                 , required.no_PFG = 6
    -                                 , required.no_strata = 5
    -                                 , required.simul_duration = 100
    -                                 , required.seeding_duration = c(10,50)
    -                                 , required.seeding_timestep = 1
    -                                 , required.seeding_input = 100
    -                                 , required.max_abund_low = 30000
    -                                 , required.max_abund_medium = 50000
    -                                 , required.max_abund_high = 90000)
    +    
    +

    Examples

    +
    
    +## Create a skeleton folder with the default name ('FATE_simulation')
    +if (dir.exists("FATE_simulation")) unlink("FATE_simulation", recursive = TRUE)
    +PRE_FATE.skeletonDirectory()
    +
    +## Create a Global_parameters file
    +PRE_FATE.params_globalParameters(name.simulation = "FATE_simulation"
    +                                 , required.no_PFG = 6
    +                                 , required.no_strata = 5
    +                                 , required.simul_duration = 100
    +                                 , required.seeding_duration = c(10,50)
    +                                 , required.seeding_timestep = 1
    +                                 , required.seeding_input = 100
    +                                 , required.max_abund_low = 30000
    +                                 , required.max_abund_medium = 50000
    +                                 , required.max_abund_high = 90000)
                                      
                                      
    -## Change number of PFG
    -readLines("FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt")
    +## Change number of PFG
    +readLines("FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt")
     
    -.setPattern(name.simul = "FATE_simulation"
    -            , opt.name.file = "Global_parameters_V1.txt"
    -            , pattern.tofind = "NO_PFG 6"
    -            , pattern.toreplace = "NO_PFG 14")
    +.setPattern(name.simul = "FATE_simulation"
    +            , opt.name.file = "Global_parameters_V1.txt"
    +            , pattern.tofind = "NO_PFG 6"
    +            , pattern.toreplace = "NO_PFG 14")
               
    -readLines("FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt")
    +readLines("FATE_simulation/DATA/GLOBAL_PARAMETERS/Global_parameters_V1.txt")
     
     
    -## ----------------------------------------------------------------------------------------- ##
    +## ----------------------------------------------------------------------------------------- ##
                                     
    -## Load example data
    +## Load example data
     
     
    -
    +
    +
    + - - - + + diff --git a/docs/reference/dot-unzip_ALL.html b/docs/reference/dot-unzip_ALL.html index 3a1b799..1e9ceaa 100644 --- a/docs/reference/dot-unzip_ALL.html +++ b/docs/reference/dot-unzip_ALL.html @@ -1,70 +1,15 @@ - - - - - - - -Compress (.tif, .img) or decompress (.gz) files -contained in results folder — .unzip_ALL • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Compress (.tif, .img) or decompress (.gz) files +contained in results folder — .unzip_ALL • RFate - + + - - - -
    -
    - -
    - -
    +
    @@ -209,61 +143,52 @@

    Compress (.tif, .img) or decompress (.gz.gz) files contained in a given folder.

    -
    .unzip_ALL(folder_name, no_cores)
    -.unzip(folder_name, list_files, no_cores)
    -.zip_ALL(folder_name, no_cores)
    -.zip(folder_name, list_files, no_cores)
    +
    +
    .unzip_ALL(folder_name, no_cores)
    +.unzip(folder_name, list_files, no_cores)
    +.zip_ALL(folder_name, no_cores)
    +.zip(folder_name, list_files, no_cores)
    +
    -

    Arguments

    - - - - - - - - - - - - - - -
    folder_name

    a string corresponding to the directory to be -scanned

    list_files

    a vector containing filenames to be compress or -decompress, in order not to scan the whole given directory

    no_cores

    default 1.
    an integer corresponding to the +

    +

    Arguments

    +
    folder_name
    +

    a string corresponding to the directory to be +scanned

    +
    list_files
    +

    a vector containing filenames to be compress or +decompress, in order not to scan the whole given directory

    +
    no_cores
    +

    default 1.
    an integer corresponding to the number of computing resources that can be used to parallelize the -(de)compression

    - -

    Author

    - +(de)compression

    +
    +
    +

    Author

    Maya Guéguen

    +
    +
    - - - + + diff --git a/docs/reference/dunn.html b/docs/reference/dunn.html index 7133c65..70cd7aa 100644 --- a/docs/reference/dunn.html +++ b/docs/reference/dunn.html @@ -1,67 +1,12 @@ - - - - - - - -From clValid package O.7 : dunn function — dunn • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -From clValid package O.7 : dunn function — dunn • RFate - + + - - - -
    -
    - -
    - -
    +
    @@ -204,51 +138,44 @@

    From clValid package O.7 : dunn function

    From clValid package O.7 : dunn function

    -
    dunn(distance = NULL, clusters)
    - -

    Arguments

    - - - - - - - - - - -
    distance

    The distance matrix (as a matrix object) of the clustered -observations. Required if Data is NULL.

    clusters

    An integer vector indicating the cluster partitioning

    - -

    See also

    +
    +
    dunn(distance = NULL, clusters)
    +
    -

    dunn

    +
    +

    Arguments

    +
    distance
    +

    The distance matrix (as a matrix object) of the clustered +observations. Required if Data is NULL.

    +
    clusters
    +

    An integer vector indicating the cluster partitioning

    +
    +
    +

    See also

    +

    dunn

    +
    +
    - - - + + diff --git a/docs/reference/ecospat.niche.overlap.html b/docs/reference/ecospat.niche.overlap.html index 34e0c63..71c0685 100644 --- a/docs/reference/ecospat.niche.overlap.html +++ b/docs/reference/ecospat.niche.overlap.html @@ -1,73 +1,18 @@ - - - - - - - -From ecospat package 3.2 : ecospat.kd, ecospat.grid.clim.dyn and +<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>From ecospat package 3.2 : ecospat.kd, ecospat.grid.clim.dyn and ecospat.niche.overlap functions (and sp 1.4-5, adehabitatMA 0.3.14, -adehabitatHR 0.4.19 packages — ecospat.niche.overlap • RFate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - -
    -
    - -
    - -
    +
    @@ -214,126 +148,89 @@

    From ecospat package 3.2 : ecospat.kd, ecospat.grid.clim.dyn and adehabitatHR 0.4.19 packages

    -
    ecospat.kd(x, ext, R = 100, th = 0, env.mask = c(), method = "adehabitat")
    -ecospat.grid.clim.dyn(glob, glob1, sp, R = 100, th.sp = 0, th.env = 0, 
    -geomask = NULL, kernel.method = "adehabitat", extend.extent = c(0, 0, 0, 0))
    -ecospat.niche.overlap(z1, z2, cor)
    +
    +
    ecospat.kd(x, ext, R = 100, th = 0, env.mask = c(), method = "adehabitat")
    +ecospat.grid.clim.dyn(glob, glob1, sp, R = 100, th.sp = 0, th.env = 0, 
    +geomask = NULL, kernel.method = "adehabitat", extend.extent = c(0, 0, 0, 0))
    +ecospat.niche.overlap(z1, z2, cor)
    +
    -

    Arguments

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    x

    two-column dataframe (or a vector)

    ext

    c(xmin, xmax)

    R

    The resolution of the grid.

    th

    quantile

    env.mask

    mask

    method

    kernel.method

    glob

    A two-column dataframe (or a vector) of the environmental -values (in column) for background pixels of the whole study area (in row).

    glob1

    A two-column dataframe (or a vector) of the environmental -values (in column) for the background pixels of the species (in row).

    sp

    A two-column dataframe (or a vector) of the environmental -values (in column) for the occurrences of the species (in row).

    th.sp

    The quantile used to delimit a threshold to exclude low -species density values.

    th.env

    The quantile used to delimit a threshold to exclude low -environmental density values of the study area.

    geomask

    A geographical mask to delimit the background extent if the +

    +

    Arguments

    +
    x
    +

    two-column dataframe (or a vector)

    +
    ext
    +

    c(xmin, xmax)

    +
    R
    +

    The resolution of the grid.

    +
    th
    +

    quantile

    +
    env.mask
    +

    mask

    +
    method
    +

    kernel.method

    +
    glob
    +

    A two-column dataframe (or a vector) of the environmental +values (in column) for background pixels of the whole study area (in row).

    +
    glob1
    +

    A two-column dataframe (or a vector) of the environmental +values (in column) for the background pixels of the species (in row).

    +
    sp
    +

    A two-column dataframe (or a vector) of the environmental +values (in column) for the occurrences of the species (in row).

    +
    th.sp
    +

    The quantile used to delimit a threshold to exclude low +species density values.

    +
    th.env
    +

    The quantile used to delimit a threshold to exclude low +environmental density values of the study area.

    +
    geomask
    +

    A geographical mask to delimit the background extent if the analysis takes place in the geographical space.It can be a SpatialPolygon or a raster object. Note that the CRS should be the same as the one used -for the points.

    kernel.method

    Method used to estimate the the kernel density. +for the points.

    +
    kernel.method
    +

    Method used to estimate the the kernel density. Currently, there are two methods: by default, it is the methode from -'adehabitat'. Method from the library 'ks' is also available.

    extend.extent

    Vector with extention values of the window size -(see details).

    z1

    Species 1 occurrence density grid created by ecospat.grid.clim.

    z2

    Species 2 occurrence density grid created by ecospat.grid.clim.

    cor

    Correct the occurrence densities of each species by the -prevalence of the environments in their range (TRUE = yes, FALSE = no).

    - -

    See also

    - - +'adehabitat'. Method from the library 'ks' is also available.

    +
    extend.extent
    +

    Vector with extention values of the window size +(see details).

    +
    z1
    +

    Species 1 occurrence density grid created by ecospat.grid.clim.

    +
    z2
    +

    Species 2 occurrence density grid created by ecospat.grid.clim.

    +
    cor
    +

    Correct the occurrence densities of each species by the +prevalence of the environments in their range (TRUE = yes, FALSE = no).

    +
    +
    +
    - - - + + diff --git a/docs/reference/index.html b/docs/reference/index.html index 950c5c1..ad261b8 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -17,7 +17,7 @@ RFate - 1.0.3 + 1.1.0 @@ -29,7 +29,7 @@