02_Analysis_Dataset2.R 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. ##' Data Analysis of Electroantennogram Data from two different Stonefly Ecotypes
  2. ##' (full-winged and wing-reduced)
  3. ##'
  4. ##' R routine to reproduce the Fig S2 from the manuscript entitled
  5. ##' 'Rapid evolution of olfactory degradation in recently flightless
  6. ##' insects'.
  7. ##'
  8. ##
  9. # load R package
  10. library(dplyr)
  11. library(tidyr)
  12. library(ggplot2)
  13. library(tibble)
  14. library(scales)
  15. # clean up environment
  16. rm(list=ls())
  17. # set working directory
  18. currentpath <- dirname(rstudioapi::getSourceEditorContext()$path)
  19. setwd(currentpath)
  20. getwd()
  21. # load function to calculate various response dynamic measures
  22. source('./functions/responseDynamics.R')
  23. source('./functions/utils.R')
  24. fig_path <- "../Figures"
  25. # aspect ratio (as y/x) for trace plots
  26. ar <- 2/3
  27. ### colors for stoneflies ###
  28. fw_sm_l <- "#000000" ## black
  29. fw_sm_d <- "red"
  30. data2 <- read.csv("../Datasets/Dataset2.csv", stringsAsFactors = FALSE)
  31. # automatically read in amplification from info file
  32. multiply_voltage <- 1/data2$Amplification
  33. ## correct voltage values for the different amplification factor
  34. data2[, paste0("t", 1:5000)] <- data2[, paste0("t", 1:5000)] * multiply_voltage #in Volt
  35. data2[, paste0("t", 1:5000)] <- data2[, paste0("t", 1:5000)] * 1000 # in millivolt
  36. # convert ID to a factor
  37. data2$ID <- as.factor(data2$ID)
  38. data2$AntennaState <- as.factor(data2$AntennaState)
  39. data2 <- droplevels(data2)
  40. # reorder the factor levels of column Odor
  41. data2$Odor <- factor(data2$Odor, levels = c("Blank", "2Heptanone", "1Octanol", "PropionicAcid",
  42. "2Butanone", "Water", "2HeptanoneTest"),
  43. labels = c("Blank", "2-heptanone", "1-octanol", "Propionic acid",
  44. "2-butanone", "Water", "2-heptanone (2nd)"))
  45. # data2$antpart[data2$Winged=="base"] <- "middle"
  46. data2$antpart[data2$Winged=="tip"] <- "distal"
  47. data2$antpart <- as.factor(data2$antpart)
  48. data2$AntennaState <- factor(data2$AntennaState,
  49. levels = levels(data2$AntennaState),
  50. labels = c("live", "dead"))
  51. data2 <- droplevels(data2)
  52. data2$NewGroup <- paste(data2$antpart, data2$AntennaState)
  53. data2$NewGroup <- as.factor(data2$NewGroup)
  54. # create median filter mean traces (only for non-10Hz stimulations)
  55. med <- as.data.frame(t(apply(data2[data2$PulseDuration != 50, paste0("t", 1:5000)], 1, runmed, k=11)))
  56. data2[data2$PulseDuration != 50, paste0("t", 1:5000)] <- med
  57. # add response characteristcs
  58. y1 <- as.data.frame(t(apply(data2[, paste0("t", 201:5000)], 1, responseDynamics)))
  59. colnames(y1) <- c("Timeto10Max",
  60. "Timeto90Max",
  61. "TimetoMax",
  62. "TimeFromMaxto90",
  63. "TimeFromMaxto10",
  64. "Time10to90Max",
  65. "Time90to10Max",
  66. "Timeto10Min",
  67. "Timeto90Min",
  68. "TimetoMin",
  69. "TimeFromMinto90",
  70. "TimeFromMinto10",
  71. "Time10to90Min",
  72. "Time90to10Min",
  73. "MeanMax",
  74. "MeanMin")
  75. data2 <- cbind(as.data.frame(data2), y1)
  76. data2$max <- apply(data2[, paste0("t", 201:5000)], 1, max)
  77. data2$min <- apply(data2[, paste0("t", 201:5000)], 1, min)
  78. data2 <- data2 %>%
  79. mutate(mima = ifelse(Odor == "Propionic acid" |
  80. (Odor == "Water" & (abs(min) > max)), min, max))
  81. data2 <- data2 %>%
  82. mutate(Mean = ifelse(Odor == "Propionic acid" |
  83. (Odor == "Water" & (abs(min) > max)), MeanMin, MeanMax))
  84. #######################################
  85. ## generate plot function for difference in factor NewGroup
  86. plot_Antenna <- function(data, group) {
  87. d_mean <- data %>%
  88. filter(Group==group) %>%
  89. group_by(AntennaState, Odor, PulseDuration) %>%
  90. summarise_at(.funs = mean, .vars=paste0("t", 1:5000))
  91. # calculate the standard error over all odor-pulseduration configurations
  92. d_se <- data %>%
  93. filter(Group==group) %>%
  94. group_by(AntennaState, Odor, PulseDuration) %>%
  95. summarise_at(.funs = se, .vars=paste0("t", 1:5000))
  96. # calculate sample size (n) over all odor-pulseduration configurations
  97. d_n <- data %>%
  98. filter(Group==group) %>%
  99. group_by(AntennaState, Odor, PulseDuration) %>%
  100. summarise(N=n()) %>%
  101. group_by(AntennaState) %>%
  102. summarise(N=unique(N))
  103. d_n <- unite(d_n, col = "AntennaState", sep = " (n = ")
  104. d_n$t <- ")"
  105. d_n <- unite(d_n, col = "AntennaState", sep = "")
  106. d_all <- d_mean
  107. data_se <- gather(d_se, time, se, t1:t5000, factor_key=TRUE)
  108. d_all$PulseDuration <- factor(d_all$PulseDuration, levels = c(15, 30, 150, 300))
  109. d_all$PulseDur <- factor(d_all$PulseDuration, levels = c(15, 30, 150, 300),
  110. labels = c("15 ms", "30 ms", "150 ms", "300 ms"))
  111. data_long <- gather(d_all, time, Voltage, t1:t5000, factor_key=TRUE)
  112. data_long$time <- (as.numeric(gsub("t", "", data_long$time)) - 200)/1000
  113. data_long$SE <- data_se$se
  114. data_long$AntennaState <- droplevels(data_long$AntennaState)
  115. data_long$AntennaState <- factor(data_long$AntennaState,
  116. labels = t(d_n))
  117. data_long$high <- data_long$Voltage + data_long$SE
  118. data_long$low <- data_long$Voltage - data_long$SE
  119. ## generate dummy values for a the same scale of y, but different ranges (max and min)
  120. ranges <- data.frame("NegResp" = range(data_long[data_long$Odor=="Water" |
  121. data_long$Odor=="Propionic acid", c("high", "low")]),
  122. "PosResp" = range(data_long[data_long$Odor!="Water" &
  123. data_long$Odor!="Propionic acid", c("high", "low")]),
  124. "Range"= c("min", "max"))
  125. roundUp <- function(x, to) {
  126. to*(x%/%to + as.logical(x%%to))
  127. }
  128. max_diff <- roundUp(max(diff(ranges$NegResp), diff(ranges$PosResp)), to = 0.01)
  129. max_diff/2
  130. ranges$NegResp_new <- c((ranges$NegResp[1] + (diff(ranges$NegResp)/2)) - max_diff/2,
  131. (ranges$NegResp[1] + (diff(ranges$NegResp)/2)) + max_diff/2)
  132. ranges$PosResp_new <- c((ranges$PosResp[1] + (diff(ranges$PosResp)/2)) - max_diff/2,
  133. (ranges$PosResp[1] + (diff(ranges$PosResp)/2)) + max_diff/2)
  134. df <- as.data.frame(table(data_long$Odor, data_long$PulseDuration))
  135. df <- df[df$Freq!=0,]
  136. colnames(df) <- c("Odor", "PulseDuration", "Freq")
  137. df$range <- "min"
  138. df$time <- 1
  139. df2 <- df
  140. df2$range <- "max"
  141. df <- rbind(df, df2)
  142. df$Voltage[(df$Odor=="Water" | df$Odor=="Propionic acid") & df$range=="min"] <- ranges$NegResp_new[1]
  143. df$Voltage[(df$Odor=="Water" | df$Odor=="Propionic acid") & df$range=="max"] <- ranges$NegResp_new[2]
  144. df$Voltage[(df$Odor!="Water" & df$Odor!="Propionic acid") & df$range=="min"] <- ranges$PosResp_new[1]
  145. df$Voltage[(df$Odor!="Water" & df$Odor!="Propionic acid") & df$range=="max"] <- ranges$PosResp_new[2]
  146. diff(range(df[df$Odor=="Water", "Voltage"]))
  147. mids <- df %>%
  148. filter(PulseDuration=="300") %>%
  149. group_by(Odor) %>%
  150. summarise(mid=Voltage[range=="max"] - ((Voltage[range=="max"] - Voltage[range=="min"])/2))
  151. data_segm1 <- data.frame(y=-0.02, yend=c(0), x=3, xend=3,
  152. PulseDuration=15,
  153. Odor=c("Water"))
  154. label1 <- data.frame(x=3, y=c(-0.01),
  155. text=c("0.02 mV"),
  156. PulseDuration= 15,
  157. Odor=c("Water"))
  158. data_segm2 <- data.frame(x=3, xend=4, y=-0.02, yend=-0.02,
  159. PulseDuration= 15,
  160. Odor=c("Water"))
  161. label2 <- data.frame(x=3.5, y=-0.02,
  162. text=c("1 s"),
  163. PulseDuration= 15,
  164. Odor="Water")
  165. label3 <- data.frame(x=5, y=mids$mid,
  166. text=levels(data_long$Odor),
  167. PulseDuration=300,
  168. Odor=levels(data_long$Odor))
  169. col <- c(fw_sm_l, fw_sm_d)
  170. df_sub <- df[df$range=="min",]
  171. df_sub <- droplevels(df_sub)
  172. df_sub$PulseDuration <- factor(df_sub$PulseDuration, levels = c(15, 30, 150, 300))
  173. ggplot(data_long, aes(x=time, y=Voltage)) +
  174. scale_color_manual(aesthetics = c("colour", "fill"), values = col) +
  175. facet_grid(Odor ~ factor(PulseDuration,
  176. levels = c(15, 30, 150, 300),
  177. labels = c("15 ms", "30 ms", "150 ms", "300 ms")),
  178. scales = "free_y") +
  179. geom_rect(mapping=aes(xmin=0, xmax=as.numeric(as.character(PulseDuration))/1000,
  180. ymin=-Inf, ymax=Inf),
  181. fill="grey90", alpha=1) +
  182. geom_hline(mapping = aes(yintercept = 0), color="black", size=0.3, linetype="dotted") +
  183. geom_ribbon(aes(ymin=Voltage + SE, ymax=Voltage - SE, fill=AntennaState), alpha=0.3) +
  184. geom_line(aes(colour = AntennaState)) +
  185. theme_bw() +
  186. geom_blank(df, mapping = aes(x=time, y=Voltage)) +
  187. labs(x = "Time (s)", y="Voltage (mV)") +
  188. theme(panel.grid.major = element_blank(),
  189. panel.grid.minor = element_blank(),
  190. axis.line = element_blank(),
  191. axis.title = element_blank(),
  192. axis.ticks = element_blank(),
  193. aspect.ratio = ar,
  194. plot.margin = unit(c(5.5, 5.5, 5.5, 5.5), "points"),
  195. strip.background = element_rect(colour="NA", fill="NA"),
  196. panel.border = element_blank(),
  197. text = element_text(size=18),
  198. axis.text=element_blank(),
  199. strip.text.y = element_text(angle = 0, hjust = 0),
  200. legend.position = c(0.2, 0.08),
  201. legend.title = element_blank()) +
  202. geom_segment(data=data_segm1,
  203. aes(x=x, y=y, yend=yend, xend=xend)) +
  204. geom_text(data = label1, aes(x=x, y=y, label=text), size=5, hjust=1.1) +
  205. geom_segment(data=data_segm2,
  206. aes(x=x, y=y, yend=yend, xend=xend)) +
  207. geom_text(data = label2, aes(x=x, y=y, label=text), size=5, vjust=1.3) +
  208. coord_cartesian(expand = TRUE,
  209. clip = 'off')
  210. }
  211. ## plot real data
  212. p1 <- plot_Antenna(data = data2,
  213. group = "FenestrateZombie"); p1
  214. subdat <- data2 %>%
  215. dplyr::select(Mean, Odor, AntennaState, ID_new, PulseDuration) %>%
  216. group_by(AntennaState, ID_new, PulseDuration) %>%
  217. pivot_wider(names_from = Odor, values_from = Mean)
  218. colnames(subdat) <- c("AntennaState","ID_new","PulseDuration","Blank","HPT",
  219. "OCT","PropionicAcid","BTN","Water","HPT2")
  220. subdat2 <- subdat %>%
  221. group_by(ID_new, PulseDuration) %>%
  222. summarise("HeptAlive"=HPT[AntennaState=="live"], "PropDead"=PropionicAcid[AntennaState=="dead"])
  223. subdat2$PulseDuration <- factor(subdat2$PulseDuration,
  224. levels = c(15, 30, 150, 300))
  225. subdat2$pvals <- NA
  226. ## Create a list holding the p-values for regressions on each PulseDuration
  227. subdat2 <- subdat2 %>%
  228. group_by(PulseDuration) %>%
  229. mutate(HeptAlive_sc=scale(HeptAlive), PropDead_sc=scale(PropDead))
  230. subdat2$ID_new <- as.factor(subdat2$ID_new)
  231. NEWDAT <- as.data.frame(matrix(NA, ncol = 7, nrow = 4))
  232. colnames(NEWDAT) <- c("PulseDuration", "pval", "r2", "xmin", "xmax", "ymin", "ymax")
  233. NEWDAT$PulseDuration <- as.character(unique(subdat2$PulseDuration))
  234. par(mfrow=c(1,4))
  235. for (i in as.character(unique(subdat2$PulseDuration))) {
  236. dat <- subdat2[subdat2$PulseDuration==i, ]
  237. mod <- lm(HeptAlive ~ PropDead, data = dat)
  238. ry <- range(dat$HeptAlive)
  239. rx <- range(dat$PropDead)
  240. rxy <- max(abs(diff(ry)), abs(diff(rx)))
  241. xlim <- c((rx[1] + abs(diff(rx))/2) - rxy/2, (rx[1] + abs(diff(rx))/2) + rxy/2)
  242. ylim <- c((ry[1] + abs(diff(ry))/2) - rxy/2, (ry[1] + abs(diff(ry))/2) + rxy/2)
  243. plot(HeptAlive ~ PropDead, data = dat, pch=21, las=1,
  244. cex.lab=1.2, xlim=xlim,
  245. ylim=ylim, main=i)
  246. abline(mod)
  247. summary(mod)
  248. m <- summary(mod)
  249. print(m)
  250. NEWDAT[NEWDAT$PulseDuration==i, "pval"] <- round(m$coefficients[2, 4], digits = 2)
  251. NEWDAT[NEWDAT$PulseDuration==i, "r2"] <- round(m$r.squared, digits = 2)
  252. NEWDAT[NEWDAT$PulseDuration==i, c("xmin", "xmax")] <- xlim
  253. NEWDAT[NEWDAT$PulseDuration==i, c("ymin", "ymax")] <- ylim
  254. }
  255. subdat2$PulseDuration <- factor(subdat2$PulseDuration,
  256. c(15, 30, 150, 300),
  257. c("15 ms", "30 ms", "150 ms", "300 ms"))
  258. NEWDAT$PulseDuration <- factor(NEWDAT$PulseDuration,
  259. c(15, 30, 150, 300),
  260. c("15 ms", "30 ms", "150 ms", "300 ms"))
  261. dependencePlot <- ggplot(subdat2, aes(PropDead, HeptAlive)) +
  262. geom_point() +
  263. scale_x_continuous(breaks = pretty_breaks(n = 3)) +
  264. scale_y_continuous(breaks = pretty_breaks(n = 3)) +
  265. geom_blank(data=NEWDAT, mapping=aes(x=xmin, y=ymin)) +
  266. geom_blank(data=NEWDAT, mapping=aes(x=xmax, y=ymax)) +
  267. facet_wrap( . ~ PulseDuration, scales = "free", ncol=4) +
  268. xlab("Response strength of dead antennae to propionic acid (mV)") +
  269. ylab("Response strength \n of live antennae \n to 2-heptanone (mV)") +
  270. theme_bw() +
  271. theme(panel.grid.major = element_blank(),
  272. panel.grid.minor = element_blank(),
  273. panel.spacing = unit(2, "lines"),
  274. plot.margin = unit(c(5.5, 5.5, 5.5, 5.5), "points"),
  275. strip.background = element_rect(colour="NA", fill="NA"),
  276. text = element_text(size=18),
  277. aspect.ratio=1,
  278. axis.text=element_text(colour="black")) +
  279. geom_text(NEWDAT, mapping = aes(x=xmax, y=ymax, label=paste0("p=",pval)), size=5,
  280. color="blue", hjust=1, vjust=1) +
  281. geom_text(NEWDAT, mapping = aes(x=xmin, y=ymax, label=paste0("r²=", r2)), size=5,
  282. color="blue", hjust=0, vjust=1)
  283. FigS2 <- cowplot::plot_grid(p1, dependencePlot,
  284. labels = c("A", "B"), label_size = 16,
  285. ncol = 1, nrow = 2, rel_heights = c(3.2,1))
  286. FigS2
  287. ggsave("FigureS2.pdf", path = fig_path, width = 10, height = 14)
  288. ggsave("FigureS2.jpg", path = fig_path, width = 10, height = 14)