2 Part 1. Prepare Training Data

In ‘gibbonR’ there are two ways that you can format your training data. The first can be a set of labelled .wav clips with the class indicated in the name of the file (e.g., ‘gibbon_01.wav’ and ‘noise_01.wav’). The second is to have a folder of selection tables created in Raven Pro (K. Lisa Yang Center for Conservation Bioacoustics) and a folder with the associated ‘.wav’ files. For the second approach there must be an annotation column indicating the call type and it is assumed that all signals of interest are annotated, and the rest of the files contain only background noise.

2.1 Load the library

library(gibbonR)

# Set location of downloaded data
FolderPath <- '/Users/denaclink/Library/CloudStorage/Box-Box/gibbonRSampleFiles/'

2.2 Download the data from Github

# You need to tell R where to store the zip files on your computer.
destination.file.path.zip <-
  "data/BorneoExampleData.zip"

# You also need to tell R where to save the unzipped files
destination.file.path <- "data/"

# This function will download the data from github

utils::download.file("https://github.com/DenaJGibbon/BorneoExampleData/archive/master.zip",
                     destfile = destination.file.path.zip)

# This function will unzip the file
utils::unzip(zipfile = destination.file.path.zip,
             exdir = destination.file.path)

# Examine the contents
list.of.sound.files <- list.files(paste(destination.file.path,
                                        "BorneoExampleData-master", "data", sep =
                                          "/"),
                                  full.names = T)
list.of.sound.files

Use this function to read in the .RDA file and save it as an R object from https://stackoverflow.com/questions/5577221/how-can-i-load-an-object-into-a-variable-name-that-i-specify-from-an-r-data-file

loadRData <- function(fileName) {
  #loads an RData file, and returns it
  load(fileName)
  get(ls()[ls() != "fileName"])
}

This function will load the entire list of r data files

library(gibbonR)

# You also need to tell R where to save the unzipped files
destination.file.path <- "data/"

# Examine the contents
list.of.sound.files <- list.files(paste(destination.file.path,
                                        "BorneoExampleData-master", "data", sep =
                                          "/"),
                                  full.names = T)
list.of.sound.files
## [1] "data//BorneoExampleData-master/data/multi.class.training.rda"            
## [2] "data//BorneoExampleData-master/data/S11_20180219_060002_1800sto3600s.rda"
list.rda.files <- list()
for(x in 1:length(list.of.sound.files)){
  list.rda.files[[x]] <-  loadRData(list.of.sound.files[[x]])
}

Assign each rda an informative name

multi.class.list <- list.rda.files[[1]]
S11_20180219_060002_1800sto3600s <- list.rda.files[[2]]

Now we create a directory with the training .wav files

TrainingDataDirectory <- "data/BorneoMultiClass/"

# Create if doesn't already exist
dir.create(TrainingDataDirectory,recursive = T)

for(a in 1:length(multi.class.list)){
  Temp.element <- multi.class.list[[a]]
  writeWave(Temp.element[[2]], paste(TrainingDataDirectory,Temp.element[[1]],sep='/'))
}

2.3 Part 1A. Training Data with Labeled .wav clips

2.3.1 Read in clips and calculate MFCCs

TrainingDataDirectory <- "data/BorneoMultiClass/"

trainingdata <- gibbonR::MFCCFunction(input.dir=TrainingDataDirectory, min.freq = 400, max.freq = 1600,win.avg="standard")

# Convert to a factor
trainingdata$class <- as.factor(trainingdata$class)

# Output a table to see sample size
table(trainingdata$class)

2.3.2 Compare Random Forest and Support Vector Machine for Supervised Classification

Note that this approach uses k-fold cross-validation by splitting the training data into two splits over multiple iterations.

# Set seed for reproducilibility
set.seed(3)

# Create an index to randomly sample 25 observations for test data
testindex <- sample(1:75,25,replace = FALSE)

# Isolate test data samples
testdata <- trainingdata[testindex,]

# Remove from training data
trainingdata_sub <- trainingdata[-testindex,]

# Ensure class is a factor
trainingdata_sub$class <- as.factor(trainingdata_sub$class)

# Run SVM
ml.model.svm <- e1071::svm(trainingdata_sub[, 2:ncol(trainingdata_sub)], trainingdata_sub$class, kernel = "radial", 
                           cross = 25,
                           probability = TRUE)

# Predict SVM on test data
svm.predict.labels <- predict(ml.model.svm,testdata[,-c(1)])

# Create a confusion matrix
caret::confusionMatrix(svm.predict.labels,testdata$class)
## Confusion Matrix and Statistics
## 
##                Reference
## Prediction      female.gibbon leaf.monkey noise solo.gibbon
##   female.gibbon             7           0     0           0
##   leaf.monkey               0           1     0           0
##   noise                     1           1     8           0
##   solo.gibbon               0           0     0           7
## 
## Overall Statistics
##                                           
##                Accuracy : 0.92            
##                  95% CI : (0.7397, 0.9902)
##     No Information Rate : 0.32            
##     P-Value [Acc > NIR] : 5.992e-10       
##                                           
##                   Kappa : 0.8858          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: female.gibbon Class: leaf.monkey Class: noise
## Sensitivity                        0.8750             0.5000       1.0000
## Specificity                        1.0000             1.0000       0.8824
## Pos Pred Value                     1.0000             1.0000       0.8000
## Neg Pred Value                     0.9444             0.9583       1.0000
## Prevalence                         0.3200             0.0800       0.3200
## Detection Rate                     0.2800             0.0400       0.3200
## Detection Prevalence               0.2800             0.0400       0.4000
## Balanced Accuracy                  0.9375             0.7500       0.9412
##                      Class: solo.gibbon
## Sensitivity                        1.00
## Specificity                        1.00
## Pos Pred Value                     1.00
## Neg Pred Value                     1.00
## Prevalence                         0.28
## Detection Rate                     0.28
## Detection Prevalence               0.28
## Balanced Accuracy                  1.00
# Run Random Forest
ml.model.rf <- randomForest::randomForest(x=trainingdata_sub[, 2:ncol(trainingdata_sub)], y = trainingdata_sub$class)

# Predict RF on test data
rf.predict.labels <- predict(ml.model.rf,testdata[,-c(1)])

# Create a confusion matrix
caret::confusionMatrix(rf.predict.labels,testdata$class)
## Confusion Matrix and Statistics
## 
##                Reference
## Prediction      female.gibbon leaf.monkey noise solo.gibbon
##   female.gibbon             7           0     0           0
##   leaf.monkey               0           1     0           1
##   noise                     1           1     8           1
##   solo.gibbon               0           0     0           5
## 
## Overall Statistics
##                                           
##                Accuracy : 0.84            
##                  95% CI : (0.6392, 0.9546)
##     No Information Rate : 0.32            
##     P-Value [Acc > NIR] : 1.197e-07       
##                                           
##                   Kappa : 0.7738          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: female.gibbon Class: leaf.monkey Class: noise
## Sensitivity                        0.8750             0.5000       1.0000
## Specificity                        1.0000             0.9565       0.8235
## Pos Pred Value                     1.0000             0.5000       0.7273
## Neg Pred Value                     0.9444             0.9565       1.0000
## Prevalence                         0.3200             0.0800       0.3200
## Detection Rate                     0.2800             0.0400       0.3200
## Detection Prevalence               0.2800             0.0800       0.4400
## Balanced Accuracy                  0.9375             0.7283       0.9118
##                      Class: solo.gibbon
## Sensitivity                      0.7143
## Specificity                      1.0000
## Pos Pred Value                   1.0000
## Neg Pred Value                   0.9000
## Prevalence                       0.2800
## Detection Rate                   0.2000
## Detection Prevalence             0.2000
## Balanced Accuracy                0.8571

2.4 Part 1B. Training Data with Raven Selection Tables

2.4.1 Prepare training data from labeled annotations

The script below points to a Raven selection table and a correspond .wav file, and uses the selection table to cut out the shorter clips based on the annotation.

# Specify the folder where the training data will be saved
TrainingDataFolderLocation <- paste(FolderPath,"/TrainingDataFromRavenSelectionTables/",
                                    sep='')

# Directory with annotated selection tables
AnnotatedSelectionTables <- list.files( paste(FolderPath,"SelectionTables/GibbonTrainingSelectionTables/",sep=''),
                                       full.names = T)

# Directory with corresponding .wav files
AnnotatedWaveFiles <- list.files(paste(FolderPath,"GibbonTrainingFiles/",sep=''),
                                 full.names = T)

AnnotatedWaveFilesShort <- basename(AnnotatedWaveFiles)

AnnotatedWaveFilesShort <- str_split_fixed(AnnotatedWaveFilesShort,pattern = '.wav', n=2)[,1]

# Loop to cut out the corresponding annotations into short clips
for(i in 1: length(AnnotatedSelectionTables)){
  
  # Read in selection table
  TempSelectionTable <- read.delim2(AnnotatedSelectionTables[i])
  
  # Find the corresponding soundfile
  SoundFileIndex <- which(str_detect(AnnotatedSelectionTables[i],AnnotatedWaveFilesShort))
  
  TempAnnotateWave <- readWave(AnnotatedWaveFiles[SoundFileIndex])
  
  ShortSoundClips <- lapply(1:nrow(TempSelectionTable),
                                function(j) extractWave(TempAnnotateWave,
                                                        from= as.numeric(TempSelectionTable[j,]$Begin.Time..s.),
                                                        to=as.numeric(TempSelectionTable[j,]$ End.Time..s.),
                                                        xunit = c("time"),plot=F,output="Wave"))
  # Write wave files to folder
  for(k in 1:length(ShortSoundClips)){
    TempClip <- ShortSoundClips[[k]]
    WavFileName <- paste(TrainingDataFolderLocation,'/female.gibbon_', k, '.wav',sep="")
    writeWave(TempClip,WavFileName,extensible = F)
  }
  
  
}

2.4.2 Prepare noise training data from files without target signal

The script below uses a band-limited energy detector to identify sounds in the frequency range of interest. The file included does not have any gibbon calls, so all sounds detected will be non-gibbon or noise.

# Specify the folder where the training data will be saved (same as above)
TrainingDataFolderLocation <- paste(FolderPath,"/TrainingDataFromRavenSelectionTables/",
                                    sep='')

# Directory with annotated selection tables
NoiseSelectionTables <- list.files( paste(FolderPath,"/SelectionTables/NoiseSelectionTables/",sep=''),
                                       full.names = T)

# Directory with corresponding .wav files
NoiseWaveFiles <- list.files(paste(FolderPath,"/NoiseFiles/",sep=''),full.names = TRUE)

NoiseWaveFilesShort <- basename(NoiseWaveFiles)

NoiseWaveFilesShort <- str_split_fixed(NoiseWaveFilesShort,pattern = '.wav', n=2)[,1]

for(i in 1:length(NoiseSelectionTables)){
  
  # Find the corresponding soundfile
  SoundFileIndex <- which(str_detect(NoiseSelectionTables[i],NoiseWaveFilesShort))

  DetectBLED(input=NoiseWaveFiles[SoundFileIndex],
           min.freq = 400, 
           max.freq = 1600,
           noise.quantile.val=0.3,
           spectrogram.window =512,
           pattern.split = ".wav", 
           min.signal.dur = 3,
           max.sound.event.dur = 12, 
           wav.output = "TRUE", 
           output.dir = TrainingDataFolderLocation,
           swift.time=TRUE,
           time.start=06,
           time.stop=11,
           write.table.output=TRUE,
           verbose=TRUE,
           random.sample=FALSE)
}

2.4.3 Now read in clips based on Raven Selection tables and calculate MFCCs

TrainingDataFolderLocation <- paste(FolderPath,"/TrainingDataFromRavenSelectionTables/",
                                    sep='')

trainingdata <- gibbonR::MFCCFunction(input.dir=TrainingDataFolderLocation, min.freq = 400, max.freq = 1600,win.avg="standard")


trainingdata$class <- as.factor(trainingdata$class)

table(trainingdata$class )

2.4.4 Compare Random Forest and Support Vector Machine for Supervised Classification

# Set seed for reproducibility
set.seed(3)

# Check the structure (this is binary with only two classes)
table(trainingdata$class) 
## 
## female.gibbon         noise 
##            26            27
# Create an index to randomly sample 25 observations for test data
testindex <- sample(1:53,25,replace = FALSE)

# Isolate test data samples
testdata <- trainingdata[testindex,]

# Remove from training data
trainingdata_sub <- trainingdata[-testindex,]

# Ensure class is a factor
trainingdata_sub$class <- as.factor(trainingdata_sub$class)

# Run SVM
ml.model.svm <- e1071::svm(trainingdata_sub[, 2:ncol(trainingdata_sub)], trainingdata_sub$class, kernel = "radial", 
                           cross = 25,
                           probability = TRUE)

# Predict SVM on test data
svm.predict.labels <- predict(ml.model.svm,testdata[,-c(1)])

# Create a confusion matrix
caret::confusionMatrix(svm.predict.labels,testdata$class)
## Confusion Matrix and Statistics
## 
##                Reference
## Prediction      female.gibbon noise
##   female.gibbon            11     0
##   noise                     0    14
##                                        
##                Accuracy : 1            
##                  95% CI : (0.8628, 1)  
##     No Information Rate : 0.56         
##     P-Value [Acc > NIR] : 5.066e-07    
##                                        
##                   Kappa : 1            
##                                        
##  Mcnemar's Test P-Value : NA           
##                                        
##             Sensitivity : 1.00         
##             Specificity : 1.00         
##          Pos Pred Value : 1.00         
##          Neg Pred Value : 1.00         
##              Prevalence : 0.44         
##          Detection Rate : 0.44         
##    Detection Prevalence : 0.44         
##       Balanced Accuracy : 1.00         
##                                        
##        'Positive' Class : female.gibbon
## 
# Run Random Forest
ml.model.rf <- randomForest::randomForest(x=trainingdata_sub[, 2:ncol(trainingdata_sub)], y = trainingdata_sub$class)

# Predict RF on test data
rf.predict.labels <- predict(ml.model.rf,testdata[,-c(1)])

# Create a confusion matrix
caret::confusionMatrix(rf.predict.labels,testdata$class)
## Confusion Matrix and Statistics
## 
##                Reference
## Prediction      female.gibbon noise
##   female.gibbon            11     3
##   noise                     0    11
##                                           
##                Accuracy : 0.88            
##                  95% CI : (0.6878, 0.9745)
##     No Information Rate : 0.56            
##     P-Value [Acc > NIR] : 0.0006695       
##                                           
##                   Kappa : 0.7634          
##                                           
##  Mcnemar's Test P-Value : 0.2482131       
##                                           
##             Sensitivity : 1.0000          
##             Specificity : 0.7857          
##          Pos Pred Value : 0.7857          
##          Neg Pred Value : 1.0000          
##              Prevalence : 0.4400          
##          Detection Rate : 0.4400          
##    Detection Prevalence : 0.5600          
##       Balanced Accuracy : 0.8929          
##                                           
##        'Positive' Class : female.gibbon   
##