Diff of /R/7.stability.R [000000] .. [13df9a]

Switch to unified view

a b/R/7.stability.R
1
# ========7.stability===========
2
3
#' Evaluate the stability of a network
4
#'
5
#' @param go_ls an igraph object or igraph list.
6
#' @param mode "robust_test", "vulnerability", "robustness"
7
#' @param partial how much percent vertexes be removed in total (default: 0.5, only for robust_test)
8
#' @param step how many nodes be removed each time? (default: 10, only for robust_test)
9
#' @param reps simulation number (default: 9)
10
#' @param threads threads
11
#' @param verbose verbose
12
#' @param keystone remove 70%% keystones instead of remove 50%% nodes (default: False, only for robustness)
13
#'
14
#' @return a data.frame
15
#' @export
16
#'
17
#' @examples
18
#' \donttest{
19
#' data("c_net")
20
#' if (requireNamespace("ggpmisc")) {
21
#'   c_net_stability(co_net, mode = "robust_test", step = 20, reps = 9) -> robust_res
22
#'   plot(robust_res, index = "Average_degree", mode = 2)
23
#' }
24
#'
25
#' c_net_stability(co_net, mode = "vulnerability") -> vulnerability_res
26
#' plot(vulnerability_res)
27
#'
28
#' robustness(co_net) -> robustness_res
29
#' plot(robustness_res)
30
#'
31
#' module_detect(co_net) -> co_net_modu
32
#' zp_analyse(co_net_modu, mode = 2) -> co_net_modu
33
#'
34
#' c_net_stability(co_net_modu, mode = "robustness", keystone = TRUE) -> robustness_res
35
#' plot(robustness_res)
36
#' }
37
c_net_stability <- function(go_ls, mode = "robust_test", partial = 0.5, step = 10, reps = 9,
38
                            threads = 1, verbose = TRUE, keystone = FALSE) {
39
  mode <- match.arg(mode, c("robust_test", "vulnerability", "robustness"))
40
  if (mode == "robust_test") {
41
    res <- robust_test(go_ls, partial = partial, step = step, reps = reps, threads = threads, verbose = verbose)
42
  } else if (mode == "vulnerability") {
43
    res <- vulnerability(go_ls, threads = threads, verbose = verbose)
44
  } else if (mode == "robustness") {
45
    res <- robustness(go_ls, keystone = keystone, reps = reps, threads = threads, verbose = verbose)
46
  }
47
  return(res)
48
}
49
50
#' Robust_test for a network
51
#' @rdname c_net_stability
52
#'
53
#' @return data.frame (robustness class)
54
#' @export
55
#'
56
robust_test <- function(go_ls, partial = 0.5, step = 10, reps = 9, threads = 1, verbose = TRUE) {
57
  if ("igraph" %in% class(go_ls)) {
58
    robustness_res <- robust_test_in(go_ls, partial = partial, step = step, reps = reps, threads = threads, verbose = verbose)
59
    robustness_res <- data.frame(robustness_res, group = "Network")
60
  } else {
61
    if (!is.igraph(go_ls[[1]])) stop("No igraph or igraph-list.")
62
    robustness_res <- lapply(names(go_ls), \(i){
63
      tmp <- robust_test_in(go_ls[[i]], partial = partial, step = step, reps = reps, threads = threads, verbose = verbose)
64
      data.frame(tmp, group = i)
65
    })
66
    robustness_res <- do.call(rbind, robustness_res)
67
    robustness_res$group <- factor(robustness_res$group, levels = names(go_ls))
68
  }
69
  class(robustness_res) <- c("robust", "data.frame")
70
  return(robustness_res)
71
}
72
73
robust_test_in <- function(go, partial = 0.5, step = 10, reps = 9, threads = 1, verbose = TRUE) {
74
  i <- NULL
75
76
  cal_del <- \(go, partial, step, rep){
77
    nodes <- length(igraph::V(go))
78
    floor(nodes * partial) -> del_i
79
    del_i_indexs <- data.frame()
80
    sequ <- seq(0, del_i, step)
81
    if (sequ[length(sequ)] < del_i) sequ <- c(sequ, del_i)
82
    res <- lapply(sequ, \(i){
83
      # remove i nodes in the network
84
      remove_node <- sample(1:nodes, i)
85
      dp <- igraph::delete.vertices(go, remove_node)
86
      dp <- igraph::delete.vertices(dp, igraph::V(dp)[igraph::degree(dp) == 0])
87
88
      # calculate network parameters
89
      tmp_ind <- (MetaNet::net_par(dp, mode = "n")$n_index)
90
      data.frame(tmp_ind, i = i)
91
    })
92
    del_i_indexs <- do.call(rbind, res)
93
94
    # for (i in sequ) {
95
    #   # remove i nodes in the network
96
    #   remove_node <- sample(1:nodes, i)
97
    #   dp <- igraph::delete.vertices(go, remove_node)
98
    #   dp=igraph::delete.vertices(dp,igraph::V(dp)[igraph::degree(dp)==0])
99
    #
100
    #   # calculate network parameters
101
    #   tmp_ind=(MetaNet::net_par(dp,mode = "n")$n_index)
102
    #   del_i_indexs <- rbind(
103
    #     del_i_indexs,
104
    #     data.frame(tmp_ind, i = i)
105
    #   )
106
    # }
107
108
    return(data.frame(del_i_indexs, "rep" = rep))
109
  }
110
111
  # parallel
112
  # main function
113
  loop <- function(i) {
114
    cal_del(go, partial, step, i)
115
  }
116
  {
117
    if (threads > 1) {
118
      pcutils::lib_ps("foreach", "doSNOW", "snow", library = FALSE)
119
      pb <- utils::txtProgressBar(max = reps, style = 3)
120
      if (verbose) {
121
        opts <- list(progress = function(n) utils::setTxtProgressBar(pb, n))
122
        cl <- snow::makeCluster(threads)
123
      } else {
124
        opts <- NULL
125
      }
126
      doSNOW::registerDoSNOW(cl)
127
      res <- foreach::`%dopar%`(
128
        foreach::foreach(i = 1:reps, .options.snow = opts),
129
        loop(i)
130
      )
131
      snow::stopCluster(cl)
132
      gc()
133
    } else {
134
      res <- lapply(1:reps, loop)
135
    }
136
  }
137
  # simplify method
138
  del_i_indexs <- do.call(rbind, res)
139
140
  del_i_indexs$i_ratio <- del_i_indexs$i / length(V(go)) %>% round(., 3)
141
  class(del_i_indexs) <- c("robust", class(del_i_indexs))
142
  return(del_i_indexs)
143
}
144
145
#' Plot robust
146
#'
147
#' @param x `robust_test()` result (robust object)
148
#' @param mode plot mode, 1~3
149
#' @param indexes indexes selected to show
150
#' @param use_ratio use the delete nodes ratio rather than nodes number
151
#' @param ... additional arguments for \code{\link[pcutils]{group_box}}
152
#'
153
#' @return a ggplot
154
#' @method plot robust
155
#' @exportS3Method
156
#' @rdname robust_test
157
plot.robust <- function(x, indexes = c("Natural_connectivity", "Average_degree"), use_ratio = FALSE, mode = 1, ...) {
158
  i <- group <- variable <- value <- se <- eq.label <- adj.rr.label <- Natural_connectivity <- lm <- NULL
159
  robust_res <- x
160
  xlab <- "Removed_nodes"
161
  if (use_ratio) {
162
    robust_res$i <- robust_res$i_ratio
163
    xlab <- "Removed_nodes_ratio"
164
  }
165
  robust_res %>%
166
    dplyr::select(i, group, !!indexes) %>%
167
    reshape2::melt(id.var = c("i", "group")) -> pdat
168
169
  pdat %>%
170
    dplyr::group_by(i, variable, group) %>%
171
    dplyr::summarise(mean = mean(value), sd = sd(value), se = sd / sqrt(length(value))) -> sdd
172
173
  if (mode == 1) {
174
    p <- ggplot(sdd, aes(x = i, y = mean, col = group)) +
175
      geom_line() +
176
      geom_errorbar(data = sdd, aes(ymax = mean + se, ymin = mean - se)) +
177
      # geom_smooth(se = FALSE,method = "loess",formula = 'y ~ x') +
178
      facet_wrap(. ~ variable, scales = "free") +
179
      labs(x = xlab, y = NULL) +
180
      theme_bw()
181
  }
182
  if (mode == 2) {
183
    lib_ps("ggpmisc", library = FALSE)
184
    p <- ggplot(sdd, aes(x = i, y = mean, col = group)) +
185
      geom_point(size = 0.2, alpha = 0.4) +
186
      geom_smooth(se = FALSE, method = "loess", formula = "y ~ x") +
187
      ggpmisc::stat_poly_eq(
188
        aes(label = paste(after_stat(eq.label), after_stat(adj.rr.label), sep = "~~~~~")),
189
        formula = y ~ x, parse = TRUE, label.x = "right",
190
        size = 3,
191
      ) +
192
      facet_wrap(. ~ variable, scales = "free") +
193
      labs(x = xlab, y = NULL) +
194
      theme_bw()
195
  }
196
  if (mode == 3) {
197
    robust_res %>% dplyr::select(i, rep, group, `Natural_connectivity`) -> pdat
198
199
    pdat %>%
200
      # filter(i<0.4)%>%
201
      dplyr::group_by(rep, group) %>%
202
      dplyr::summarise(slope = coefficients(lm(`Natural_connectivity` ~ i))[2]) -> slope
203
204
    p <- pcutils::group_box(slope["slope"], group = "group", metadata = slope, alpha = TRUE, ...) + theme_bw()
205
  }
206
  return(p)
207
}
208
209
#' Vulnerability calculation
210
#'
211
#' @rdname c_net_stability
212
#'
213
#' @return a vector
214
#' @export
215
#' @description
216
#' \deqn{Vi=\frac{E-Ei}{E}}
217
#' E is the global efficiency and Ei is the global efficiency after the removal of the node i and its entire links.
218
#'
219
vulnerability <- function(go_ls, threads = 1, verbose = TRUE) {
220
  if ("igraph" %in% class(go_ls)) {
221
    vulnerability_res <- vul_max(go_ls, threads = threads, verbose = verbose)
222
  } else {
223
    if (!"igraph" %in% class(go_ls[[1]])) stop("No igraph or igraph-list.")
224
    vulnerability_res <- lapply(names(go_ls), \(i){
225
      vul_max(go_ls[[i]], threads = threads, verbose = verbose)
226
    })
227
    names(vulnerability_res) <- names(go_ls)
228
  }
229
  class(vulnerability_res) <- c("vulnerability", class(vulnerability_res))
230
  return(vulnerability_res)
231
}
232
233
vul_max <- function(go, threads = 1, verbose = TRUE) {
234
  i <- NULL
235
  stopifnot(is.igraph(go))
236
  if (is.null(V(go)$name)) V(go)$name <- V(go)
237
238
  # parallel
239
  # main function
240
  loop <- function(i) igraph::global_efficiency(igraph::delete_vertices(go, i))
241
  {
242
    if (threads > 1) {
243
      pcutils::lib_ps("foreach", "doSNOW", "snow", library = FALSE)
244
      if (verbose) {
245
        pb <- utils::txtProgressBar(max = length(V(go)$name), style = 3)
246
        opts <- list(progress = function(n) utils::setTxtProgressBar(pb, n))
247
      } else {
248
        opts <- NULL
249
      }
250
      cl <- snow::makeCluster(threads)
251
      doSNOW::registerDoSNOW(cl)
252
      res <- foreach::`%dopar%`(
253
        foreach::foreach(i = V(go)$name, .options.snow = opts),
254
        loop(i)
255
      )
256
      snow::stopCluster(cl)
257
      gc()
258
    } else {
259
      res <- lapply(V(go)$name, loop)
260
    }
261
  }
262
  # simplify method
263
  tmpv <- do.call(c, res)
264
265
  (igraph::global_efficiency(go) - tmpv) / igraph::global_efficiency(go)
266
}
267
268
#' Plot vulnerability
269
#'
270
#' @param x `vulnerability()` result (vulnerability object)
271
#' @param ... add
272
#'
273
#' @return a ggplot
274
#' @method plot vulnerability
275
#' @exportS3Method
276
#' @rdname vulnerability
277
plot.vulnerability <- function(x, ...) {
278
  group <- NULL
279
  vulnerability_res <- x
280
  if (!is.list(vulnerability_res)) {
281
    vulnerability_res <- list(Network = vulnerability_res)
282
  }
283
  pdat <- data.frame(
284
    group = factor(names(vulnerability_res), levels = names(vulnerability_res)),
285
    vulnerability = vapply(vulnerability_res, max, numeric(1))
286
  )
287
  ggplot(pdat, aes(group, vulnerability)) +
288
    geom_col(aes(fill = group), ...) +
289
    geom_text(aes(group, vulnerability * 1.05, label = round(vulnerability, 3))) +
290
    theme_bw()
291
}
292
293
294
#' Robustness after remove 50%% nodes or some hubs, need the metanet contains "cor" attribute.
295
#'
296
#' @rdname c_net_stability
297
#'
298
#' @export
299
robustness <- function(go_ls, keystone = FALSE, reps = 9, threads = 1, verbose = TRUE) {
300
  if ("igraph" %in% class(go_ls)) {
301
    robustness_res <- robustness_in(go_ls, keystone = keystone, reps = reps, threads = threads, verbose = verbose)
302
  } else {
303
    if (!"igraph" %in% class(go_ls[[1]])) stop("No igraph or igraph-list.")
304
    robustness_res <- lapply(names(go_ls), \(i){
305
      tmp <- robustness_in(go_ls[[i]], keystone = keystone, reps = reps, threads = threads, verbose = verbose)
306
      data.frame(tmp, group = i)
307
    })
308
    robustness_res <- do.call(rbind, robustness_res)
309
    robustness_res$group <- factor(robustness_res$group, levels = names(go_ls))
310
  }
311
  class(robustness_res) <- c("robustness", class(robustness_res))
312
  return(robustness_res)
313
}
314
315
robustness_in <- function(go, keystone = FALSE, reps = 9, threads = 1, verbose = TRUE) {
316
  roles <- name <- from <- to <- i <- NULL
317
318
  nodes <- length(V(go))
319
  floor(nodes * 0.5) -> del_i
320
  if (keystone) {
321
    get_v(go) -> tmp_v
322
    if (!"module" %in% colnames(tmp_v)) stop("no modules, please `module_detect()` first")
323
    if (!"roles" %in% colnames(tmp_v)) stop("no roles, please `zp_analyse()` first")
324
    tmp_v %>%
325
      dplyr::filter(roles == "Module hubs") %>%
326
      dplyr::pull(name) -> hubs
327
    floor(length(hubs) * 0.7) -> del_i
328
  }
329
330
  # parallel
331
  # main function
332
  loop <- \(i){
333
    if (!keystone) remove_node <- sample(1:nodes, del_i)
334
    if (keystone) remove_node <- sample(hubs, del_i)
335
336
    dp <- igraph::delete.vertices(go, remove_node)
337
    dead_s <- "init"
338
    # calculated the abundance-weighted mean interaction strength (wMIS) of nodes,<=0 will dead
339
    # repeat until all >0
340
    while (length(dead_s) > 0) {
341
      get_e(dp) -> edge_list
342
      edge_list %>%
343
        dplyr::select(from, cor) %>%
344
        rbind(., dplyr::select(edge_list, to, cor) %>% dplyr::rename(from = to)) %>%
345
        dplyr::group_by(from) %>%
346
        dplyr::summarise(w_degree = sum(cor)) -> w_degree
347
      w_degree %>%
348
        dplyr::filter(w_degree <= 0) %>%
349
        dplyr::pull(from) -> dead_s
350
      dp <- igraph::delete.vertices(dp, dead_s)
351
    }
352
    tmp_ind <- (MetaNet::net_par(dp, mode = "n")$n_index)
353
    data.frame(tmp_ind, reps = i)
354
  }
355
  {
356
    if (threads > 1) {
357
      pcutils::lib_ps("foreach", "doSNOW", "snow", library = FALSE)
358
      if (verbose) {
359
        pb <- utils::txtProgressBar(max = reps, style = 3)
360
        opts <- list(progress = function(n) utils::setTxtProgressBar(pb, n))
361
      } else {
362
        opts <- NULL
363
      }
364
      cl <- snow::makeCluster(threads)
365
      doSNOW::registerDoSNOW(cl)
366
      res <- foreach::`%dopar%`(
367
        foreach::foreach(i = 1:reps, .options.snow = opts),
368
        loop(i)
369
      )
370
      snow::stopCluster(cl)
371
      gc()
372
    } else {
373
      res <- lapply(1:reps, loop)
374
    }
375
  }
376
  # simplify method
377
  del_i_indexs <- do.call(rbind, res)
378
  return(del_i_indexs)
379
}
380
381
#' Plot robustness
382
#'
383
#' @param x `robustness()` result (robustness object)
384
#' @param indexes indexes selected to show
385
#' @param ... additional arguments for \code{\link[pcutils]{group_box}}
386
#'
387
#' @return a ggplot
388
#' @method plot robustness
389
#' @exportS3Method
390
#' @rdname robustness
391
plot.robustness <- function(x, indexes = "Node_number", ...) {
392
  robustness_res <- x
393
  if (!"group" %in% colnames(robustness_res)) robustness_res$group <- "Network"
394
  pcutils::group_box(robustness_res[, indexes, drop = FALSE],
395
    group = "group",
396
    metadata = robustness_res, ..., facet = FALSE
397
  ) + theme_bw()
398
}
399
400
401
#' Cohesion calculation
402
#'
403
#' @param otutab otutab
404
#' @param reps iteration time
405
#' @param threads threads
406
#' @param mycor a correlation matrix you want to use, skip the null model build when mycor is not NULL, default: NULL
407
#' @param verbose verbose
408
#'
409
#' @return Cohesion object: a list with two dataframe
410
#' @export
411
#' @references
412
#' Herren, C. M. & McMahon, K. (2017) Cohesion: a method for quantifying the connectivity of microbial communities. doi:10.1038/ismej.2017.91.
413
#' @examples
414
#' \donttest{
415
#' data("otutab", package = "pcutils")
416
#' # set reps at least 99 when you run.
417
#' Cohesion(otutab[1:50, ], reps = 19) -> cohesion_res
418
#' if (requireNamespace("ggpubr")) {
419
#'   plot(cohesion_res, group = "Group", metadata = metadata, mode = 1)
420
#'   plot(cohesion_res, group = "Group", metadata = metadata, mode = 2)
421
#' }
422
#' }
423
Cohesion <- function(otutab, reps = 200, threads = 1, mycor = NULL, verbose = TRUE) {
424
  i <- NULL
425
  d <- t(otutab)
426
  rel.d <- d / rowSums(d)
427
428
  if (is.null(mycor)) {
429
    # Create observed correlation matrix
430
    cor.mat.true <- cor(rel.d)
431
    nc <- ncol(rel.d)
432
433
    loop <- \(which.taxon){
434
      # create vector to hold correlations from every permutation for each single otu
435
      ## perm.cor.vec.mat stands for permuted correlations vector matrix
436
      perm.cor.vec.mat <- vector()
437
438
      for (i in 1:reps) {
439
        # Create empty matrix of same dimension as rel.d
440
        perm.rel.d <- matrix(numeric(0), dim(rel.d)[1], dim(rel.d)[2])
441
        rownames(perm.rel.d) <- rownames(rel.d)
442
        colnames(perm.rel.d) <- colnames(rel.d)
443
444
        # For each otu
445
        for (j in seq_len(dim(rel.d)[2])) {
446
          # Replace the original taxon vector with a permuted taxon vector
447
          perm.rel.d[, j] <- sample(rel.d[, j])
448
        }
449
450
        # Do not randomize focal column
451
        perm.rel.d[, which.taxon] <- rel.d[, which.taxon]
452
453
        # Calculate correlation matrix of permuted matrix
454
        cor.mat.null <- cor(perm.rel.d, perm.rel.d[, which.taxon])
455
456
        # For each iteration, save the vector of null matrix correlations between focal taxon and other taxa
457
        perm.cor.vec.mat <- cbind(perm.cor.vec.mat, cor.mat.null)
458
      }
459
460
      # Save the median correlations between the focal taxon and all other taxa
461
      apply(perm.cor.vec.mat, 1, median)
462
    }
463
    {
464
      if (threads > 1) {
465
        pcutils::lib_ps("foreach", "doSNOW", "snow", library = FALSE)
466
        pb <- utils::txtProgressBar(max = reps, style = 3)
467
        if (verbose) {
468
          opts <- list(progress = function(n) utils::setTxtProgressBar(pb, n))
469
          cl <- snow::makeCluster(threads)
470
        } else {
471
          opts <- NULL
472
        }
473
        doSNOW::registerDoSNOW(cl)
474
        res <- foreach::`%dopar%`(
475
          foreach::foreach(i = 1:nc, .options.snow = opts),
476
          loop(i)
477
        )
478
        snow::stopCluster(cl)
479
        gc()
480
      } else {
481
        res <- lapply(1:nc, loop)
482
      }
483
    }
484
485
    simplify2array(res) -> med.tax.cors
486
487
    obs.exp.cors.mat <- cor.mat.true - med.tax.cors
488
  } else {
489
    obs.exp.cors.mat <- mycor
490
  }
491
492
  diag(obs.exp.cors.mat) <- 0
493
494
  # Calculate connectedness by averaging positive and negative observed - expected correlations
495
  pn.mean <- \(vector, mode = "p"){
496
    if (mode == "p") {
497
      pos.vals <- vector[which(vector > 0)]
498
    } else if (mode == "n") pos.vals <- vector[which(vector < 0)]
499
    p.mean <- mean(pos.vals)
500
    if (length(pos.vals) == 0) p.mean <- 0
501
    return(p.mean)
502
  }
503
  connectedness.pos <- apply(obs.exp.cors.mat, 2, pn.mean, mode = "p")
504
  connectedness.neg <- apply(obs.exp.cors.mat, 2, pn.mean, mode = "n")
505
506
  # Calculate cohesion by multiplying the relative abundance dataset by associated connectedness
507
  cohesion.pos <- rel.d %*% connectedness.pos
508
  cohesion.neg <- rel.d %*% connectedness.neg
509
510
  #### Combine vectors into one list and print
511
  output <- list(
512
    data.frame(neg = connectedness.neg, pos = connectedness.pos),
513
    data.frame(neg = cohesion.neg, pos = cohesion.pos)
514
  )
515
516
  names(output) <- c("Connectedness", "Cohesion")
517
518
  class(output) <- c("cohesion", class(output))
519
  return(output)
520
}
521
522
523
#' Plot cohesion
524
#'
525
#' @param x `Cohesion()` result (cohesion object)
526
#' @param mode plot mode, 1~2
527
#' @param group group name in colnames(metadata)
528
#' @param metadata metadata
529
#' @param ... additional arguments for \code{\link[pcutils]{group_box}} (mode=1) or \code{\link[pcutils]{group_box}} (mode=2)
530
#'
531
#' @return a ggplot
532
#' @method plot cohesion
533
#' @exportS3Method
534
#' @rdname Cohesion
535
plot.cohesion <- function(x, group, metadata, mode = 1, ...) {
536
  neg <- pos <- NULL
537
  cohesion_res <- x
538
  if (mode == 1) p <- pcutils::stackplot(abs(t(cohesion_res$Cohesion)), group = group, metadata = metadata, ...)
539
  if (mode == 2) {
540
    co <- cohesion_res$Cohesion %>% dplyr::transmute(`neg:pos` = neg / pos)
541
    p <- pcutils::group_box(co, group = group, metadata = metadata, p_value2 = TRUE, ...) +
542
      ylab("neg:pos cohesion") + theme_bw()
543
  }
544
  p
545
}