Content
This document provide R
script and data to reproduce the comparison between the spatial distributions of the observed and the estimated descriptors (states, fishing grounds and fishing intensity) for the variegated scallop dredging trip as illustrated in the supplementary figure S5 in Le Guyader et.al (2017):
- Le Guyader,D., C. Ray, F. Gourmelon, D. Brosset., (2017) - Defining high-resolution dredge fishing grounds with Automatic Identification System (AIS) data. Aquatic Living Resources. doi: 10.1051/alr/2017038
Data and replication
R
code are available in this Github repository: https://github.com/dleguyader/LeGuyader-etal-2016-SM.
Load requires packages
# ipak function from Steven Worthington: install and load multiple R packages.
# https://gist.github.com/stevenworthington/3178163
# check to see if packages are installed. Install them if they are not, then load them into the R session.
ipak <- function(pkg){
new.pkg <- pkg[!(pkg %in% installed.packages()[, "Package"])]
if (length(new.pkg))
install.packages(new.pkg, dependencies = TRUE)
sapply(pkg, require, character.only = TRUE)
}
packages <- c("sp", "raster", "rasterVis", "rgdal", "rgeos", "spatstat", "maptools", "SDMTools", "adehabitatLT", "trip", "mclust", "ggplot2", "plyr", "dplyr", "caret", "viridis", "grid", "gridExtra")
ipak(packages)
## sp raster rasterVis rgdal rgeos
## TRUE TRUE TRUE TRUE TRUE
## spatstat maptools SDMTools adehabitatLT trip
## TRUE TRUE TRUE TRUE TRUE
## mclust ggplot2 plyr dplyr caret
## TRUE TRUE TRUE TRUE TRUE
## viridis grid gridExtra
## TRUE TRUE TRUE
Perform the analyses
1 - Import data and fix the parameters
######## Fix the parameters ################
dtm <- 600 # maximimum duration allowed between 2 positions(in seconds)
lhsv <- 47 # grid size: here we use the grid size caculated (in metres)
# for the 2011-2012 season(see paper for details)
maille <- 50 # smoothing factor (in metres): idem (see paper for details)
# Import data #######################
# GPS data for the variegated scallop fishing trip have been anonymized and
# coordinates have been modified with a vectorial translation for confidentiality matters
td <- getwd()
pet <- read.csv2(file = paste0(td, "/data/DataTripOneAnon.csv"))
coordinates(pet) <- ~coords.x + coords.y # To SPDF
proj4string(pet) <- CRS("+init=epsg:2154")
pet$time <- as.POSIXct(pet$time, tz = "UTC") # set time to POSIXct format
## Create the clipping polygon from the AIS data extent (e: extent)
e <- extent(pet) + 2000
CP <- as(e, "SpatialPolygons")
proj4string(CP) <- CRS("+init=epsg:2154")
## Get coastline data and clip to data extent
land <- readOGR(dsn = paste0(td, "/data"), layer = "land")
## OGR data source with driver: ESRI Shapefile
## Source: "C:\Users\Damien\Documents\R\document\DLG_WEBSITE_ACADEMIC_EN-FR\Version_4\content\post\data", layer: "land"
## with 2617 features
## It has 2 fields
land <- spTransform(land, CRS("+init=epsg:2154"))
land <- gIntersection(land, CP, byid = TRUE)
## Get sea shape data and clip to data extent
sea <- readOGR(dsn = paste0(td, "/data"), layer = "sea")
## OGR data source with driver: ESRI Shapefile
## Source: "C:\Users\Damien\Documents\R\document\DLG_WEBSITE_ACADEMIC_EN-FR\Version_4\content\post\data", layer: "sea"
## with 1 features
## It has 1 fields
sea <- spTransform(sea, CRS("+init=epsg:2154"))
sea <- gIntersection(sea, CP, byid = TRUE)
2 - Estimate fishing activity
## Function for data cleaning: time filter
prep.fun <- function(x, dtmax, analyse) {
# Fonction to calculate ditance (dist) between consecutive points, time
# duration (dt), mean speed (vmoy) and deletion according to dtmax x :
# the SPDF dtmax: maximimum duration allowed between 2 positions(in
# seconds), analyse: name of the metier (character string)
df <- x
# conversion to ltraj class
d.ltraj <- as.ltraj(coordinates(df), df$time, id = paste(analyse))
ld.ltraj <- ld(d.ltraj) # ltraj to data.frame class
df$dist <- ld.ltraj$dist
df$dt <- ld.ltraj$dt
df <- subset(df, !is.na(df$dt)) # NA delete
df <- subset(df, df$dt < dtmax) ## dt>dtmax delete
df$vmoy <- df$dist/df$dt # Mean speed calculation
return(df)
}
pet.pt <- prep.fun(x=pet, dtmax=dtm, analyse="Varieg. scall")
densy <- densityMclust(pet.pt$vmoy)
densy$modelName # Optimal model
## [1] "V"
densy$G # Optimal number of cluster according to the BIC value
## [1] 4
CLUSTCOMBI <- clustCombi(densy)
# entPlot(CLUSTCOMBI$MclustOutput$z, CLUSTCOMBI$combiM, abc = 'normalized')
## No elbow in the NDE value, so we get the optimal number
## of cluster according to the BIC value
## Final classification
yclust <- Mclust(pet.pt$vmoy, modelNames = densy$modelName, G = densy$G)
## We get G = 4 as we see no elbow in the NDE value
## Get classification values
pet.pt$CLUST <- yclust$classification
## Set classification (1) for estimated fishing positions and (0) for
## estimated non fishing positions given the selected cluster(s)
fishSet.fun <- function(x, minClus, maxClus) {
# minClus: identifier for the minimum speed cluster (integer) maxClus:
# identifier for the maximum speed cluster (integer)
vmin.clust <- NULL
max.clust <- NULL
vmin.clust <- min(x$vmoy[x$CLUST == minClus])
vmax.clust <- max(x$vmoy[x$CLUST == maxClus])
x$estim <- 0 # default steaming
x[x$vmoy >= vmin.clust & x$vmoy <= vmax.clust, "estim"] <- 1 # Fishing
x$act_estim <- "NO" # Steaming (i.e No Fishing)
x[x$vmoy >= vmin.clust & x$vmoy <= vmax.clust, "act_estim"] <- "FISH" # Fishing
x$act_estim <- factor(x$act_estim)
return(x)
}
pet.pt <- fishSet.fun(pet.pt, minClus = 2, maxClus = 2)
rm(densy, CLUSTCOMBI, yclust)
confMat <- confusionMatrix(table(pet.pt$act_estim, pet.pt$act_obs)) # Confusion matrix
3 - Identify Fishing grounds
## Data preparation for observed fishing positions
val.obs <- subset(data.frame(pet.pt), select = c(coords.x, coords.y, time, id, obs))
names(val.obs) <- c("x", "y", "date", "id", "statut")
## Data preparation for estimated fishing positions
val.est <- subset(data.frame(pet.pt), select = c(coords.x, coords.y, time, id, estim))
names(val.est) <- c("x", "y", "date", "id", "statut")
## Preparation for trip format coercion
fun.group <- function(x) {
x %>% arrange(id, date) %>%
mutate(gap = c(0, (diff(statut) != 0) * 1)) %>%
mutate(group = cumsum(gap) + 1)
}
val.obs <- ddply(val.obs, .(id, as.Date(date)), .fun = fun.group)
val.est <- ddply(val.est, .(id, as.Date(date)), .fun = fun.group)
val.obs$mmsi <- val.obs$id
val.obs$id <- paste(val.obs$mmsi, as.integer(as.Date(val.obs$date)), val.obs$group, sep = "_")
val.est$mmsi <- val.est$id
val.est$id <- paste(val.est$mmsi, as.integer(as.Date(val.est$date)), val.est$group, sep = "_")
## Get only fishing positions
val.obs <- val.obs %>%
filter(statut == 1) %>%
dplyr::select(x,y,date,id)
val.est <- val.est %>%
filter(statut == 1) %>%
dplyr::select(x,y,date,id)
## Trip format calculation
lenst.obs <- tapply(val.obs$id, val.obs$id, length)
## Delete non-consecutives fishing positions
val.obs <- val.obs[val.obs$id %in% names(lenst.obs)[lenst.obs > 2], ]
lenst.est <- tapply(val.est$id, val.est$id, length)
val.est <- val.est[val.est$id %in% names(lenst.est)[lenst.est > 2], ]
## Observed fishing positions to trip format
coordinates(val.obs) <- ~x + y
proj4string(val.obs) <- CRS("+init=epsg:2154")
trip.obs <- trip(val.obs, c("date", "id"))
## Estimated fishing positions to trip format
coordinates(val.est) <- ~x + y
proj4string(val.est) <- CRS("+init=epsg:2154")
trip.est <- trip(val.est, c("date", "id"))
pet.est <- trip.est
pet.obs <- trip.obs
rm(trip.est, trip.obs, val.obs, val.est)
## Function to compute KDE line density
fishGround.fun <- function(fishTrip, sea, grid, h) {
# fishTrip : fishing positions coerced to trip format sea: sea clip to extent of
# data grid, h: grid size and smoothing factor
# 1- Data preparation
owin.sea <- as(sea, "owin") # Window owin
bif <- raster(sea, resolution = maille) # Create template raster
projection(bif) <- CRS("+init=epsg:2154")
bif <- raster::mask(x = bif, mask = sea)
# 2 - compute KDE line density
t.obs <- fishTrip
t.obs$jour <- as.Date(t.obs$date)
vv.obs <- sort(unique(as.Date(t.obs$date)))
psp.obs.tx <- as.psp(t.obs) # coerce to psp format
psp.obs.tx$window <- owin.sea # affect owin
# KDE line density
dk.lx.obs <- density(psp.obs.tx, sigma = lhsv, dimyx = c(nrow(bif), ncol(bif)))
dk.obs <- raster(dk.lx.obs) # Raster conversion
projection(dk.obs) <- CRS("+init=epsg:2154")
return(dk.obs)
}
## Computation of KDE line density
pet.dk.obs <- fishGround.fun(fishTrip = pet.obs, sea = sea, grid = maille, h = lhsv)
pet.dk.est <- fishGround.fun(fishTrip = pet.est, sea = sea, grid = maille, h = lhsv)
4 - Compute Fishing Intensity
## Function to compute the total fishing time spent per surface unit
fishInt.fun <- function(fishTrip, sea, grid) {
# fishTrip : fishing positions coerced to trip format sea: sea clip to
# extent of data grid size: see the paper
owin.sea <- as(sea, "owin") #Window owin
bif.trip <- raster(sea, resolution = grid)
bif.trip[] <- round(runif(nrow(bif.trip) * ncol(bif.trip)) * 100)
bif.trip <- raster::mask(bif.trip, sea)
bif.trip.grid <- as(bif.trip, "SpatialGridDataFrame")
bif.trip.gt <- getGridTopology(bif.trip.grid) # Create empty grid
tripgrid <- tripGrid(fishTrip, grid = bif.trip.gt, method = "pixellate") # compute time spent fishing
time.t <- raster(tripgrid)
projection(time.t) <- CRS("+init=epsg:2154")
time.est <- raster::mask(time.t, sea)
return(time.est)
}
## Calculation of the total fishing time spent per surface unit
pet.time.obs <- fishInt.fun (fishTrip = pet.obs, sea = sea, grid = maille)
pet.time.est <- fishInt.fun (fishTrip = pet.est, sea = sea, grid = maille)
5 - Perform comparison with Warren’s similarity index
## Function for Warren's similarity index
evalMeth.fun <- function(x, y) {
# x and y are rasters obs and estim
asc.est <- asc.from.raster(x)
asc.obs <- asc.from.raster(y)
# ensure all data is positive
asc.est = abs(asc.est)
asc.obs = abs(asc.obs)
# calculate the I similarity statistic for Quantifying Niche Overlap
I = Istat(asc.est, asc.obs)
print(I)
}
## Get Values
pet.ground.eval <- evalMeth.fun (pet.dk.obs, pet.dk.est)
## [1] 0.9609129
pet.intens.eval <- evalMeth.fun (pet.time.obs, pet.time.est)
## [1] 0.9232937
6 - Plot the figure
basemap <- list("sp.polygons", land, fill = "gray90", col = "gray60", lwd = 1)
north <- list("SpatialPolygonsRescale", layout.north.arrow(type = 1),
offset = c(146100, 6835650), scale = 600)
scale <- list("SpatialPolygonsRescale", layout.scale.bar(), scale = 1500,
offset = c(152600, 6831350), fill = c("transparent", "black"))
text1 <- list("sp.text", c(152600, 6831650), "0")
text2 <- list("sp.text", c(154000, 6831650), "1.5 km")
ext.x <- c(extent(pet.pt)[1], extent(pet.pt)[2])
ext.y <- c(extent(pet.pt)[3], extent(pet.pt)[4])
valKappa <- list("sp.text", c(152600, 6831650),
paste0("Kappa value = ", round(confMat$overall[2], 2)))
p1 <- spplot(pet.pt, "act_obs", col.regions= c("red", 'transparent'),edge.col='grey80',
alpha=0.5,cex = 0.5,lwd=0.8,legendEntries = c("Fishing", "No fishing"),
key.space= "bottom",
par.settings=list(fontsize=list(text=9)),
sp.layout=list(basemap, north,scale, text1,text2),
main = list(label="Observed states",
font = 1,just = "left",x = grid::unit(5, "mm")))
p2 <-spplot(pet.pt, "act_estim", col.regions= c("red", 'transparent'),edge.col='grey80',
alpha=0.5,cex = 0.5,lwd=0.8,legendEntries = c("Fishing", "No fishing"),
key.space= "bottom",
par.settings=list(fontsize=list(text=9)),
sp.layout=list(basemap, valKappa),
main = list(label="Estimated states",
font = 1,just = "left",x = grid::unit(5, "mm")))
p3 <- levelplot(pet.dk.obs, xlim=ext.x,ylim=ext.y,margin=FALSE, xlab=NULL, ylab=NULL,
scales=list(draw=FALSE),col.regions = viridis, colorkey = list(space="bottom"),
main=list(label="Observed fishing ground",
font = 1,just = "left",x = grid::unit(5, "mm")),
par.settings=list(fontsize=list(text=9))) +
latticeExtra::layer(sp.polygons(land, fill= "gray90", col="gray50"))
p4 <- levelplot(pet.dk.est,xlim=ext.x,ylim=ext.y, margin=FALSE, xlab=NULL, ylab=NULL,
scales=list(draw=FALSE),col.regions = viridis, colorkey = list(space="bottom"),
main=list(label="Estimated fishing ground",
font = 1,just = "left",x = grid::unit(5, "mm")),
par.settings=list(fontsize=list(text=9))) +
latticeExtra::layer(sp.polygons(land, fill= "gray90", col="gray50")) +
latticeExtra::layer(sp.text (c(152600,6831650),
paste0("Similarity index I = ", round(pet.ground.eval, 2))))
p5 <- levelplot(pet.time.obs,xlim=ext.x,ylim=ext.y, margin=FALSE, xlab=NULL, ylab=NULL,
scales=list(draw=FALSE),col.regions = viridis, colorkey = list(space="bottom"),
main=list(label="Observed fishing intensity",
font = 1,just = "left",x = grid::unit(5, "mm")),
par.settings=list(fontsize=list(text=9))) +
latticeExtra::layer(sp.polygons(land, fill= "gray90", col="gray50"))
p6 <- levelplot(pet.time.est,xlim=ext.x,ylim=ext.y, margin=FALSE, xlab=NULL, ylab=NULL,
scales=list(draw=FALSE),col.regions = viridis, colorkey = list(space="bottom"),
main= list(label="Estimated fishing intensity",
font = 1,just = "left",x = grid::unit(5, "mm")),
par.settings=list(fontsize=list(text=9))) +
latticeExtra::layer(sp.polygons(land, fill= "gray90", col="gray50")) +
latticeExtra::layer(sp.text (c(152600,6831650),
paste0("Similarity index I = ", round(pet.intens.eval, 2))))
grid.arrange(p1,p2,p3,p4,p5,p6, nrow = 3, ncol = 2,
top= textGrob("Variegated scallops dredging trip \n",gp=gpar(fontsize=12,font=2)))