## This is the R code for Clink et al. Brevity is not a universal in animal communication # Load required libraries library(stringr) library(lme4) library(ggplot2) library(bbmle) library(apcluster) library(viridis) library(ggpubr) library(cowplot) library(magick) library(tuneR) library(seewave) # All files are available in the permanent repository via the link below and on Dryad # https://cornell.box.com/s/0pqxw6gxvg69nb1ipcy25j4zs5r0q2ua # Set input directory with location of all files input.dir <- '/Volumes/Dena Clink Toshiba 3 TB/MaleSolosPaper/' ### Part 1. Unsupervised clustering using features extracted from the spectrogram ## Data preparation # Set input directory with selection tables with male soles input.dir.solos <- paste(input.dir, 'male_solo_tables',sep='') # List all files in directory solo.list.files <- list.files(input.dir.solos) # Isolate the recorder (or male id) from file name recorder <- str_split_fixed(solo.list.files,pattern='_',n=2)[,1] # Create vector with new names for columns table.col.names <- c("Selection","View","Channel","Extra","start.time","end.time","Low Freq (Hz)","High Freq (Hz)","BW 90% (Hz)","Dur 90% (s)","Freq 5% (Hz)","Freq 95% (Hz)") # Create new list with selection tables and recorder combined.list.df <- cbind.data.frame(solo.list.files,recorder) # Create an index of all unique recorders recorder.index <- unique(combined.list.df$recorder) # Set the minimum duration between two notes (in seconds) for phrases to be considered separate cluster.time <- 2 # Create empty dataframe to hold data male.solo.df <- data.frame() # Create list to count total number of notes in dataset note.count.list <- list() # Loop to identify phrases in male solos for(f in 1:nrow(combined.list.df)){ print(f) recorder.tables <- combined.list.df[f,] male.id <- str_split_fixed(recorder.tables$solo.list.files, pattern = '_', n=2)[,1] solo.table <- read.table(paste(input.dir.solos,'/',recorder.tables$solo.list.files,sep=""),fill = T,header=T) colnames(solo.table) <- table.col.names recorder <- str_split_fixed(recorder.tables$solo.list.files[1],pattern='_',n=3)[1] site <- substr(recorder,start = 1,stop=1) date <- str_split_fixed(recorder.tables$solo.list.files[1],pattern='_',n=3)[2] year <- substr(date,start=1,stop=4) month <- substr(date,start=5,stop=6) day <- substr(date,start=7,stop=8) time <- str_split_fixed(str_split_fixed(recorder.tables$solo.list.files[1],pattern='_',n=3)[3],pattern='[.]',n=2)[1] filename <- str_split_fixed(recorder.tables$solo.list.files[1],pattern = '[.]',n=2)[1] wav.dir <- paste(input.dir, 'Male solo wav files/', filename,'.wav',sep="") # Calculate duration between notes time.between.notes.df <- data.frame() for(a in 1:(nrow(solo.table)-1)){ note1 <-solo.table[a,] note2 <- solo.table[a+1,] note1end <-note1$end.time note2start <- note2$start.time note1$time.next.note <- note2start - note1end time.between.notes.df <- rbind.data.frame(time.between.notes.df,note1) } note.count.list[[f]] <-nrow(solo.table) time.between.notes.df <- subset(time.between.notes.df, time.next.note > 0) # Assign break locations based on specified time between notes note.break.locations <- which(time.between.notes.df$time.next.note > cluster.time) note.break.locations <- c(1,note.break.locations) for(b in 1:( (length(note.break.locations)-1) )){ temp.solo.table <- time.between.notes.df[ (note.break.locations[b]+1): ((note.break.locations[ (b+1)])),] call.type.vec <- rep(b,nrow(temp.solo.table)) temp.solo.table$call.type <- call.type.vec temp.solo.table <- cbind.data.frame(male.id[1],temp.solo.table,wav.dir) male.solo.df <- rbind.data.frame(male.solo.df,temp.solo.table) } } # Count number of notes in dataset sum(unlist(note.count.list)) # Create empty dataframe for feature extraction based on clusters male.cluster.df <- data.frame() # Add a column with site identifier male.solo.df$site <- substr( (male.solo.df$'male.id[1]'),start=1,stop=1) # Create index of unique males/recording locations male.solo.index <- as.character(unique(male.solo.df$'male.id[1]')) # Loop to do feature extraction based on phrases/clusters for(v in 1:length(male.solo.index)){ print(male.solo.index[v]) malesolo.sub <- subset(male.solo.df, `male.id[1]` == male.solo.index[v]) wav.file <- str_split_fixed(malesolo.sub$wav.dir,pattern='/',n=7)[,6] if(length(unique(wav.file))==1){ cluster.index <- unique(malesolo.sub$call.type) for(w in 1: length(cluster.index)){ print(w) cluster <- subset(malesolo.sub,call.type==cluster.index[w]) cluster.duration <- max(cluster$end.time)- min(cluster$start.time) print(cluster.duration) nnotes.cluster <- nrow(cluster) mean5 <- mean(cluster[,12]) mean95 <- mean(cluster[,13]) max5 <- max(cluster[,12]) max95 <- max(cluster[,13]) min5 <- min(cluster[,12]) min95 <- min(cluster[,13]) minbw <- min(cluster[,10]) maxbw <- max(cluster[,10]) meanbw <- mean(cluster[,10]) maxbw <- max(cluster[,10]) mindurnote <- min(cluster[,11]) maxdurnote <- max(cluster[,11]) male.id <- male.solo.index[v] start.time <- min(cluster$start.time) end.time <- max(cluster$end.time) wav.dir <- cluster$wav.dir[1] temp.df <- cbind.data.frame(male.id,nnotes.cluster,min5,min95, minbw,maxbw,mean5,mean95,max5,max95,meanbw,mindurnote,maxdurnote,cluster.duration,start.time,end.time,wav.dir) male.cluster.df <- rbind.data.frame(male.cluster.df,temp.df) } } else{ unique.id <- unique(wav.file) first.file <- malesolo.sub[which(wav.file==unique.id[1]),] cluster.index <- unique(first.file$call.type) for(w in 1: length(cluster.index)){ print(w) cluster <- subset(first.file,call.type==cluster.index[w]) cluster.duration <- max(cluster$end.time)- min(cluster$start.time) print(cluster.duration) nnotes.cluster <- nrow(cluster) mean5 <- mean(cluster[,12]) mean95 <- mean(cluster[,13]) max5 <- max(cluster[,12]) max95 <- max(cluster[,13]) min5 <- min(cluster[,12]) min95 <- min(cluster[,13]) minbw <- min(cluster[,10]) maxbw <- max(cluster[,10]) meanbw <- mean(cluster[,10]) maxbw <- max(cluster[,10]) mindurnote <- min(cluster[,11]) maxdurnote <- max(cluster[,11]) male.id <- male.solo.index[v] start.time <- min(cluster$start.time) end.time <- max(cluster$end.time) wav.dir <- cluster$wav.dir[1] temp.df <- cbind.data.frame(male.id,nnotes.cluster,min5,min95, minbw,maxbw,mean5,mean95,max5,max95,meanbw,mindurnote,maxdurnote,cluster.duration,start.time,end.time,wav.dir) male.cluster.df <- rbind.data.frame(male.cluster.df,temp.df) } second.file <- malesolo.sub[which(wav.file==unique.id[2]),] cluster.index <- unique(second.file$call.type) for(w in 1: length(cluster.index)){ print(w) cluster <- subset(second.file,call.type==cluster.index[w]) cluster.duration <- max(cluster$end.time)- min(cluster$start.time) print(cluster.duration) nnotes.cluster <- nrow(cluster) mean5 <- mean(cluster[,12]) mean95 <- mean(cluster[,13]) max5 <- max(cluster[,12]) max95 <- max(cluster[,13]) min5 <- min(cluster[,12]) min95 <- min(cluster[,13]) minbw <- min(cluster[,10]) maxbw <- max(cluster[,10]) meanbw <- mean(cluster[,10]) maxbw <- max(cluster[,10]) mindurnote <- min(cluster[,11]) maxdurnote <- max(cluster[,11]) male.id <- male.solo.index[v] start.time <- min(cluster$start.time) end.time <- max(cluster$end.time) wav.dir <- cluster$wav.dir[1] temp.df <- cbind.data.frame(male.id,nnotes.cluster,min5,min95, minbw,maxbw,mean5,mean95,max5,max95,meanbw,mindurnote,maxdurnote,cluster.duration,start.time,end.time,wav.dir) male.cluster.df <- rbind.data.frame(male.cluster.df,temp.df) } } } nrow(male.cluster.df) range(table(male.cluster.df$male.id)) library(plyr) ## Unsupervised clustering by male male.cluster.df <- transform(male.cluster.df, male.id=revalue(male.id,c('S10'="D1",'S11'="D2",'S12'='D3', 'S14'='D4','S15'='D5','S16'='D6', 'S17'='D7','S19'='D8', 'S20'='D9', "M9"="M1","M10"="M2","M11"="M3","M13"="M4"))) # Summary of number of phrases sum(table(male.cluster.df$male.id)) range(table(male.cluster.df$male.id)) mean(male.cluster.df$cluster.duration) # Create unique index of males/recording locations male.index <- unique(male.cluster.df$male.id) # Create empty dataframe for unsupervised cluster results features.zipfs.df <- data.frame() # Create list of values to modify 'q' parameter in 'apcluster' qlist <- seq(from=0.1,to=1,by=0.1) male.cluster.id.added.df <- data.frame() male.solo.df.features <- male.cluster.df # Loop to run unsupervised clustering by male sil.list.all <- list() for(r in 1:length(male.index)){ male.cluster.subset <- subset(male.solo.df.features,male.id ==male.index[r]) # print(r) # sil.list <- list() # for(i in 1:length(qlist)){tryCatch({ # print(i) # cluster.df <- apcluster::apcluster(negDistMat(r=2),q=as.numeric(qlist[i]), # male.cluster.subset[,c(2:14)],maxits=10000,convits=1000) # sil <- # cluster::silhouette(x=cluster.df@idx,dist=dist(male.cluster.subset[,c(2:14)])) # # # sil.list[[i]] <-(summary(sil)$avg.width) # # }, error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) # } # # q.val <- qlist[which.max(unlist(sil.list))] cluster.df <- apcluster::apcluster(negDistMat(r=2), #q=q.val, male.cluster.subset[,c(2:14)], maxits=100000,convits=10000) sil <- cluster::silhouette(x=cluster.df@idx,dist=dist(male.cluster.subset[,c(2:14)])) sil.list.all[[r]] <- (summary(sil)$avg.width) male.cluster.subset$cluster.id <- unlist(cluster.df@idx) male.cluster.id.added.df <-rbind.data.frame(male.cluster.id.added.df,male.cluster.subset) cluster.index<- length(cluster.df@clusters) for(s in 1:cluster.index) { print(s) clust.sub <- male.cluster.subset[c(cluster.df@clusters[[s]]),] mean.dur <- mean(clust.sub$cluster.duration) mean.nnotes <- mean(clust.sub$nnotes.cluster) sum.duration <- sum(clust.sub$cluster.duration) nclusts <- nrow(clust.sub) male.id <- clust.sub$male.id[1] temp.zipfs <- cbind.data.frame(male.id,nclusts,mean.dur,sum.duration,mean.nnotes) features.zipfs.df <- rbind.data.frame(features.zipfs.df,temp.zipfs) } } # Create table to check phrase classification #sil.list.adaptive <- cbind.data.frame(table(features.zipfs.df$male.id),(unlist(sil.list.all))) #mean(sil.list.adaptive$`(unlist(sil.list.all))`) sil.list.nonadaptive <- cbind.data.frame(table(features.zipfs.df$male.id),(unlist(sil.list.all))) range(sil.list.nonadaptive$`(unlist(sil.list.all))`) ## Part 2. Test for Zipf's law of abbreviation # Spearman's rank correlation corr <- cor.test(x=log(features.zipfs.df$nclusts), y=log(features.zipfs.df$mean.dur),alternative = c("two.sided"), method = 'spearman') corr # Model selection using AIC with mean phrase duration as outcome zipfs.null <- lmer(log(mean.dur) ~ (1|male.id), data=features.zipfs.df) zipfs.model <- lmer(log(mean.dur) ~ log(nclusts) + (1|male.id), data=features.zipfs.df) zipfs.model.slope <- lmer(log(mean.dur) ~ log(nclusts) + (1+ log(nclusts)|male.id), data=features.zipfs.df) AICctab(zipfs.null,zipfs.model,zipfs.model.slope,weights=T) # Calculate pseudo R-squared value MuMIn::r.squaredGLMM(zipfs.model.slope) # Change male ids for figure legend to reflect site where they were recorded features.zipfs.df$male.id <- str_replace_all(features.zipfs.df$male.id, 'S', 'D') # Plot for Zipf's law of abbreviation in manuscript zp1 <- ggplot(features.zipfs.df,aes(x=log(nclusts),y= log(mean.dur), colour=male.id)) + geom_abline(intercept=zipfs.model.slope@beta[1],slope=zipfs.model.slope@beta[2])+ geom_point(size=5) + theme_bw() + xlab("Frequency of use of \n phrase type") +ylab("Mean duration of phrase (s)") + guides(colour=guide_legend(title=""))+ scale_color_manual(values = viridis ::viridis(n=length(levels(as.factor(features.zipfs.df$male.id)))))+ theme(text = element_text(size=20)) # Model selection using AIC with mean phrase duration as outcome zipfs.nnotes.null <- lmer(log(mean.nnotes) ~ (1|male.id), data=features.zipfs.df) zipfs.nnotes.model <- lmer(log(mean.nnotes) ~ log(nclusts) + (1|male.id), data=features.zipfs.df) zipfs.nnotes.model.slope <- lmer(log(mean.nnotes) ~ log(nclusts) + (1+ log(nclusts)|male.id), data=features.zipfs.df) AICctab(zipfs.nnotes.null,zipfs.nnotes.model,zipfs.nnotes.model.slope,weights=T) # Calculate pseudo R-squared value MuMIn::r.squaredGLMM(zipfs.nnotes.model.slope) # Change male ids for figure legend to reflect site where they were recorded features.zipfs.df$male.id <- factor(features.zipfs.df$male.id, levels = c("D1", "D2", "D3","D4","D5","D6","D7","D8","D9", "M1","M2","M3","M4")) range(table(features.zipfs.df$male.id)) mean(table(features.zipfs.df$male.id)) # Plot for Zipf's law of abbreviation in manuscript zp2 <- ggplot(features.zipfs.df,aes(x=log(nclusts),y= log(mean.nnotes), colour=male.id)) + geom_abline(intercept=zipfs.nnotes.model.slope@beta[1],slope=zipfs.nnotes.model.slope@beta[2])+ geom_point(size=5) + theme_bw() + xlab("Frequency of use of \n phrase type") +ylab("Mean number of notes \n per phrase") + guides(colour=guide_legend(title=""))+ scale_color_manual(values = viridis ::viridis(n=length(levels(as.factor(features.zipfs.df$male.id)))))+ theme(text = element_text(size=20)) legend <- get_legend( # create some space to the left of the legend zp2 + theme(legend.box.margin = margin(0, 0, 0, 0)) ) prow <- plot_grid( zp1 + theme(legend.position="none"), zp2 + theme(legend.position="none"), align = 'vh', #labels = c("A", "B"), hjust = -1, nrow = 1 ) prow # add the legend to the row we made earlier. Give it one-third of # the width of one plot (via rel_widths). png(paste("ZipfsAbbreviation.png"), width = 8, height = 4.5, units = 'in', res = 300) plot_grid(prow, legend,rel_widths = c(3, .4)) graphics.off() # Check SVM classification of clustering results svm.male.index <- unique(male.cluster.id.added.df$male.id) total.accuracy.list <- list() for(a in 1:length(svm.male.index)){ temp.svm.subset <- subset(male.cluster.id.added.df,male.id==svm.male.index[a]) male.svm.id <- e1071::svm(temp.svm.subset[,c(2:14)],temp.svm.subset$cluster.id,kernel= "radial", type='C', cross=50) total.accuracy.list[[a]] <- (male.svm.id$tot.accuracy) } range(unlist(total.accuracy.list)) ## Part 3. Individual signatures based on features from the spectrogram male.svm.id <- e1071::svm(male.solo.df.features[,c(2:14)],male.solo.df.features$male.id,kernel= "radial", type='C', cross=50) male.svm.id$tot.accuracy ## Part 4. Test for Menzerath's law # Create empty dataframe for data male.solo.df.menzerath <- data.frame() for(f in 1:length(recorder.index)){ print(f) recorder.tables <- subset(combined.list.df, recorder==recorder.index[f]) male.id <- str_split_fixed(recorder.tables$solo.list.files, pattern = '_', n=2)[,1] if(nrow(recorder.tables)>1){ table.1 <- read.table(paste(input.dir.solos,'/',recorder.tables$solo.list.files[1],sep=""),fill = T,header=T) #print(table.1[1,]) table.2 <- read.table(paste(input.dir.solos,'/',recorder.tables$solo.list.files[2],sep=""),fill = T,header=T) table.2[,5:6] <- table.2[,5:6] + round(max(table.1[,6])) solo.table <- rbind.data.frame(table.1,table.2) colnames(solo.table) <- table.col.names } else{ solo.table <- read.table(paste(input.dir.solos,'/',recorder.tables$solo.list.files,sep=""),fill = T,header=T) colnames(solo.table) <- table.col.names } solo.table[,5:6] <- solo.table[,5:6]- solo.table[1,5] # duration between notes time.between.notes.df <- data.frame() for(a in 1:(nrow(solo.table)-1)){ note1 <-solo.table[a,] note2 <- solo.table[a+1,] note1end <-note1$end.time note2start <- note2$start.time note1$time.next.note <- note2start - note1end time.between.notes.df <- rbind.data.frame(time.between.notes.df,note1) } time.between.notes.df <- subset(time.between.notes.df, time.next.note > 0) #find breaks between note >1 sec, and combine note.break.locations <- which(time.between.notes.df$time.next.note > cluster.time) note.break.locations <- c(1,note.break.locations) call.type.df <- data.frame() for(b in 1:( (length(note.break.locations)-1) )){ temp.solo.table <- time.between.notes.df[ (note.break.locations[b]+1): ((note.break.locations[ (b+1)])),] call.type.vec <- rep(b,nrow(temp.solo.table)) temp.solo.table$call.type <- call.type.vec call.type.df <- rbind.data.frame(call.type.df,temp.solo.table) } call.clusters <- unique(call.type.df$call.type) for(c in 1:length(call.clusters)){ temp.cluster <- subset(call.type.df, call.type==call.clusters[c]) cluster.duration <- max(temp.cluster$end.time) - min(temp.cluster$start.time) n.elements <- nrow(temp.cluster) start.time <- min(temp.cluster$start.time) end.time <- max(temp.cluster$end.time) cluster.id <- c mean.duration <- mean(temp.cluster[,10]) sd.duration <- sd(temp.cluster[,10]) temp.df <- cbind.data.frame(male.id[1],cluster.duration,n.elements,cluster.id,start.time,end.time,mean.duration,sd.duration) if(cluster.duration > 0.25){ male.solo.df.menzerath <- rbind.data.frame(male.solo.df.menzerath,temp.df) } } } levels(male.solo.df.menzerath$male.id) male.solo.df.menzerath <- transform(male.solo.df.menzerath, male.id=revalue(male.id,c('S10'="D1",'S11'="D2",'S12'='D3', 'S14'='D4','S15'='D5','S16'='D6', 'S17'='D7','S19'='D8', 'S20'='D9', "M9"="M1","M10"="M2","M11"="M3","M13"="M4"))) # Remove erroneuous clusters male.solo.df.menzerath<- subset(male.solo.df.menzerath,cluster.duration < 40) male.solo.df.menzerath <- subset(male.solo.df.menzerath,mean.duration > 0) # Rename male id column and add site column male.solo.df.menzerath$male.id <- as.factor(male.solo.df.menzerath$male.id ) male.solo.df.menzerath$site <- substr(male.solo.df.menzerath$male.id,start=1,stop=1) # Spearman's rank correlation corr <- cor.test(x=log(male.solo.df.menzerath$mean.duration), y=log(male.solo.df.menzerath$n.elements),alternative = c("two.sided"), method = 'spearman') corr # Test of Menzerath’s law (according to which longer sequences are made up of shorter constituents) null.model <- lmer(log(mean.duration) ~ (1|male.id),data=male.solo.df.menzerath) menzeraths.model <- lmer(log(mean.duration) ~ log(n.elements) + (1|male.id),data=male.solo.df.menzerath) menzeraths.model.slope <- lmer(log(mean.duration) ~ log(n.elements) + (1+ log(n.elements)|male.id),data=male.solo.df.menzerath) # AIC comparison AICctab(null.model,menzeraths.model,menzeraths.model.slope,weights=T) # Calculate pseudo R-squared MuMIn::r.squaredGLMM(menzeraths.model.slope) # Fix male ids for legend unique(male.solo.df.menzerath$male.id) # Change male ids for figure legend to reflect site where they were recorded male.solo.df.menzerath$male.id <- factor(male.solo.df.menzerath$male.id, levels = c("D1", "D2", "D3","D4","D5","D6","D7","D8","D9", "M1","M2","M3","M4")) # Plot for Menzerath's law in manuscript png(paste("Menzeraths.png"), width = 8, height = 4, units = 'in', res = 300) ggplot(male.solo.df.menzerath,aes(y=log(mean.duration),x= log(n.elements), group=male.id, colour=male.id)) + #facet_grid(~time) + #geom_line(aes(y=fit), size=0.8,alpha=0.5,col='black') + geom_abline(intercept=menzeraths.model.slope@beta[1], slope=menzeraths.model.slope@beta[2])+ geom_point(alpha=0.4,size=5) + theme_bw() +xlab("Number of notes in a phrase") + ylab("Mean duration of notes (s)") + #ggtitle(" Quantile")+ guides(colour=guide_legend(title=""))+ scale_color_manual(values = viridis ::viridis(n=length(levels(as.factor(male.solo.df.menzerath$male.id)))))+ theme(text = element_text(size=20)) graphics.off() ## Part 5. Create exemplary plots of unsupervised phrase classification for each male male.index <- unique(male.cluster.df$male.id) features.zipfs.df <- data.frame() qlist <- seq(from=0.1,to=1,by=0.1) # Loop for unsupervised clustering clust.id.features.zipfs.df <- data.frame() for(r in 1:length(male.index)){ male.cluster.subset <- subset(male.cluster.df,male.id ==male.index[r]) print(r) # sil.list <- list() # for(i in 1:length(qlist)){tryCatch({ # #print(i) # cluster.df <- apcluster::apcluster(negDistMat(r=2),q=as.numeric(qlist[i]), # male.cluster.subset[,c(2:14)],maxits=10000,convits=1000) # sil <- # cluster::silhouette(x=cluster.df@idx,dist=dist(male.cluster.subset[,c(2:14)])) # # # sil.list[[i]] <-(summary(sil)$avg.width) # # }, error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) # } # # q.val <- qlist[which.max(unlist(sil.list))] # cluster.df <- apcluster::apcluster(negDistMat(r=2), #q=q.val, male.cluster.subset[,c(2:14)], maxits=10000,convits=1000) cluster.index<- length(cluster.df@clusters) for(s in 1:cluster.index) { clust.sub <- male.cluster.subset[c(cluster.df@clusters[[s]]),] cluster.id <- rep(s, nrow(clust.sub)) temp.zipfs <- cbind.data.frame(clust.sub,cluster.id) print(s) clust.id.features.zipfs.df <- rbind.data.frame(clust.id.features.zipfs.df,temp.zipfs) } } # Fix names for figure legends clust.id.features.zipfs.df$male.id <- str_replace_all(clust.id.features.zipfs.df$male.id, 'S', 'D') pdf( paste(input.dir, "exemplaryphrases.pdf", sep='') ) wav.dir.index <- unique(clust.id.features.zipfs.df$wav.dir) for(b in 1:length(wav.dir.index)){ wav.for.detection <- wav.dir.index[b] single.file.cluster <- subset(clust.id.features.zipfs.df,wav.dir== as.character(wav.for.detection)) single.file.cluster par(mfrow=c(4,1)) wav.file.read <- readWave(as.character(wav.for.detection)) range.times <- max(single.file.cluster$start.time)-min(single.file.cluster$end.time) starting.times <- c( min(single.file.cluster$start.time)+range.times*0.05, min(single.file.cluster$start.time)+range.times*0.25, min(single.file.cluster$start.time)+range.times*0.5, min(single.file.cluster$start.time)+range.times*0.75) for(y in 1:length(starting.times)){ start.time <- starting.times[y] end.time <- start.time + 60 single.file.cluster.sub <- single.file.cluster[ as.integer(single.file.cluster$start.time) >= start.time & (single.file.cluster$end.time) <= end.time, ] single.file.cluster.sub wav.file.read.cut <- cutw(wav.file.read,from =start.time, to = end.time, output = 'Wave' ) temp.spec <- signal::specgram(wav.file.read.cut@left, Fs = wav.file.read.cut@samp.rate, n = 1600, overlap = 85) # Then plot the spectrogram if(y==1){ plot( temp.spec, xlab = "Time (s)", ylab = "Frequency (Hz)", col =rev(matlab::jet.colors(50)), #viridis::viridis(12,option=spec.col), ylim=c(0,2000), useRaster = TRUE, main=single.file.cluster.sub$male.id[1] ) } else { plot( temp.spec, xlab = "Time (s)", ylab = "Frequency (Hz)", col =rev(matlab::jet.colors(50)), #viridis::viridis(12,option=spec.col), ylim=c(0,2000), useRaster = TRUE ) } single.file.cluster.sub$start.time <- single.file.cluster.sub$start.time - start.time single.file.cluster.sub$end.time <- single.file.cluster.sub$end.time - start.time # And add boxes for the identified sound events for (x in 1:nrow(single.file.cluster.sub)) { rect(single.file.cluster.sub$start.time[x], 400, single.file.cluster.sub$end.time[x], 1200, border = "blue") vec <- c(single.file.cluster.sub$start.time[x], single.file.cluster.sub$end.time[x]) x.val <- vec[-length(vec)] + diff(vec) / 2 text(x.val, 1500, labels = single.file.cluster.sub$cluster.id[x],cex=2.5) } } } dev.off()