Diff of /R/5.topological.R [000000] .. [13df9a]

Switch to unified view

a b/R/5.topological.R
1
# ========5.topological=======
2
3
#' Extract each sample network from the whole network
4
#'
5
#' @param whole_net the whole network
6
#' @param otutab otutab, columns are samples, these columns will be extract
7
#' @param threads threads, default: 1
8
#' @param save_net should save these sub_nets? FALSE or a filename
9
#' @param fast less indexes for faster calculate ?
10
#' @param verbose verbose
11
#' @param remove_negative remove negative edge or not? default: FALSE
12
#'
13
#' @return a dataframe contains all sub_net parameters
14
#' @export
15
#' @family topological
16
#' @examples
17
#' data(otutab, package = "pcutils")
18
#' extract_sample_net(co_net, otutab) -> sub_net_pars
19
extract_sample_net <- function(whole_net, otutab, threads = 1, save_net = FALSE, fast = TRUE, remove_negative = FALSE, verbose = TRUE) {
20
  i <- NULL
21
  V(whole_net)$name -> v_name
22
  reps <- ncol(otutab)
23
24
  if (verbose) message("extracting")
25
  sub_nets <- lapply(1:reps, \(i){
26
    rownames(otutab)[otutab[, i] > 0] -> exist_sp
27
    subgraph(whole_net, which(v_name %in% exist_sp)) -> spe_sub
28
    class(spe_sub) <- c("metanet", "igraph")
29
    return(spe_sub)
30
  })
31
  names(sub_nets) <- colnames(otutab)
32
  if (verbose) message("calculating topological indexes")
33
34
  # parallel
35
  # main function
36
  loop <- function(i) {
37
    spe_sub <- sub_nets[[i]]
38
    indexs <- net_par(spe_sub, mode = "n", fast = fast, remove_negative = remove_negative)[["n_index"]]
39
    wc <- igraph::cluster_fast_greedy(spe_sub, weights = abs(igraph::E(spe_sub)$weight))
40
    indexs$modularity <- igraph::modularity(wc)
41
    indexs
42
  }
43
  {
44
    if (threads > 1) {
45
      pcutils::lib_ps("foreach", "doSNOW", "snow", library = FALSE)
46
      if (verbose) {
47
        pb <- utils::txtProgressBar(max = reps, style = 3)
48
        opts <- list(progress = function(n) utils::setTxtProgressBar(pb, n))
49
      } else {
50
        opts <- NULL
51
      }
52
      cl <- snow::makeCluster(threads)
53
      doSNOW::registerDoSNOW(cl)
54
      res <- foreach::`%dopar%`(
55
        foreach::foreach(i = 1:reps, .options.snow = opts),
56
        loop(i)
57
      )
58
      snow::stopCluster(cl)
59
      gc()
60
    } else {
61
      res <- lapply(1:reps, loop)
62
    }
63
  }
64
  # simplify method
65
  sub_net_pars <- do.call(rbind, res)
66
67
  rownames(sub_net_pars) <- colnames(otutab)
68
69
  if (is.logical(save_net)) {
70
    if (save_net) save_net <- paste0("sub_net_", date())
71
  }
72
  if (is.character(save_net)) {
73
    saveRDS(sub_nets, file = paste0(save_net, ".RDS"))
74
  }
75
  sub_net_pars
76
}
77
78
#' Calculate natural_connectivity
79
#'
80
#' @param p an igraph or metanet object
81
#' @return natural_connectivity (numeric)
82
#' @export
83
#' @references \code{`nc` in `ggClusterNet`}
84
#' @family topological
85
#' @examples
86
#' igraph::make_ring(10) %>% nc()
87
nc <- function(p) {
88
  adj_matrix <- as.matrix(igraph::as_adj(p, sparse = FALSE))
89
  adj_matrix[abs(adj_matrix) != 0] <- 1
90
91
  lambda <- eigen(adj_matrix, only.values = TRUE)$values
92
  lambda <- sort(lambda, decreasing = TRUE)
93
94
  lambda_sum <- 0
95
  N <- length(lambda)
96
  for (i in 1:N) lambda_sum <- lambda_sum + exp(lambda[i])
97
  lambda_average <- log(lambda_sum / N, base = exp(1))
98
  lambda_average
99
}
100
101
102
#' Calculate all topological indexes of a network
103
#'
104
#' @param go an igraph or metanet object
105
#' @param fast less indexes for faster calculate ?
106
#' @param mode calculate what? c("v", "e", "n", "all")
107
#' @param remove_negative remove negative edge or not? default: FALSE
108
#'
109
#' @return a 3-elements list
110
#' \item{n_index}{indexs of the whole network}
111
#' \item{v_index}{indexs of each vertex}
112
#' \item{e_index}{indexs of each edge}
113
#' @export
114
#' @family topological
115
#' @examples
116
#' igraph::make_graph("Walther") %>% net_par()
117
#' c_net_index(co_net) -> co_net_with_par
118
net_par <- function(go, mode = c("v", "e", "n", "all"), fast = TRUE, remove_negative = FALSE) {
119
  from <- to <- NULL
120
  stopifnot(is_igraph(go))
121
  if ("all" %in% mode) mode <- c("v", "e", "n")
122
123
  n_index <- NULL
124
  v_index <- NULL
125
  e_index <- NULL
126
127
  Negative_percentage <- ifelse(!is.null(E(go)$cor), sum(igraph::E(go)$cor < 0) / length(igraph::E(go)), NA)
128
  # remove negative weight
129
  if (remove_negative) {
130
    if (!is.null(E(go)$cor)) {
131
      # message("Remove negative correlation edges")
132
      c_net_filter(go, cor > 0, mode = "e") -> go
133
    }
134
  }
135
136
  # non-weighted network
137
  up <- go
138
  if (!is.null(igraph::edge_attr(up)[["weight"]])) up <- igraph::delete_edge_attr(up, "weight")
139
140
  if ("n" %in% mode) {
141
    # Calculate Network Parameters
142
    n_index <- data.frame(
143
      check.names = F,
144
      `Node_number` = length(igraph::V(go)), # number of nodes
145
      `Edge_number` = length(igraph::E(go)), # number of edges
146
      `Edge_density` = igraph::edge_density(go), # density of network, connectance
147
      `Negative_percentage` = Negative_percentage, # negative edges percentage
148
      `Average_path_length` = igraph::average.path.length(up), # Average path length
149
150
      `Global_efficiency` = igraph::global_efficiency(up),
151
      `Average_degree` = mean(igraph::degree(go)), # Average degree
152
      `Average_weighted_degree` = ifelse(is.null(igraph::E(go)$weight), mean(igraph::degree(go)), sum(igraph::E(go)$weight) / length(igraph::V(go))), # weighted degree
153
      Diameter = igraph::diameter(up), # network diameter
154
      `Clustering_coefficient` = igraph::transitivity(go), # Clustering coefficient
155
      `Centralized_betweenness` = igraph::centralization.betweenness(go)$centralization, # Betweenness centralization
156
      `Natural_connectivity` = nc(go) # natural
157
    )
158
159
    if (!fast) {
160
      # mean_dist=mean_distance(go)#
161
      # w_mean_dist=ifelse(is.null(E(go)$weight),mean_dist,mean_distance(go))
162
      # v_conn= vertex.connectivity(go) #
163
      # e_conn= edge.connectivity(go) #
164
      # components= count_components(go) #
165
      modularity <- igraph::modularity(igraph::cluster_fast_greedy(go)) #
166
      rand.g <- igraph::erdos.renyi.game(length(V(go)), length(E(go)), type = "gnm")
167
      rand_m <- igraph::modularity(igraph::cluster_fast_greedy(rand.g))
168
      relative_modularity <- (modularity - rand_m) / rand_m #
169
170
      n_index <- data.frame(
171
        check.names = F,
172
        n_index,
173
        Modularity = modularity,
174
        `Relative_modularity` = relative_modularity,
175
        `Centralized_closeness` = igraph::centralization.closeness(go)$centralization, # Closeness centralization
176
        `Centralized_degree` = igraph::centralization.degree(go)$centralization, # Degree centralization
177
        `Centralized_eigenvector` = igraph::centralization.evcent(go)$centralization # eigenvector centralization
178
      )
179
    }
180
    n_index <- apply(n_index, 1, FUN = \(x)replace(x, is.nan(x), 0)) %>%
181
      t() %>%
182
      as.data.frame()
183
    n_index <- cbind_new(get_n(go, simple = TRUE), n_index)
184
  }
185
  if ("v" %in% mode) {
186
    # Calculate Vertices Parameters
187
    v_index <- data.frame(
188
      check.names = F,
189
      Degree = igraph::degree(go),
190
      `Clustering_coefficient` = igraph::transitivity(go, type = "local"), # local clustering coefficient
191
      Betweenness = igraph::betweenness(go), # betweenness
192
      Eccentricity = igraph::eccentricity(go),
193
      Closeness = igraph::closeness(go),
194
      `Hub_score` = igraph::hub_score(go)[["vector"]]
195
      # page_rank = page.rank(go)$vector
196
      # igraph::evcent(go)[["vector"]]
197
      # igraph::local_efficiency(go)
198
    )
199
200
    # weighted degree
201
    if (!is.null(E(go)$cor)) {
202
      get_e(go) -> edge_list
203
      edge_list %>%
204
        dplyr::select(from, cor) %>%
205
        rbind(., dplyr::select(edge_list, to, cor) %>% dplyr::rename(from = to)) %>%
206
        dplyr::group_by(from) %>%
207
        dplyr::summarise(w_degree = sum(cor)) -> w_degree
208
      v_index$`Average_weighted_degree` <- w_degree[match(rownames(v_index), w_degree$from), "w_degree"] %>% unlist()
209
    }
210
211
    v_index <- apply(v_index, 1, FUN = \(x)replace(x, is.nan(x), 0)) %>%
212
      t() %>%
213
      as.data.frame()
214
    v_index <- cbind_new(get_v(go), v_index)
215
  }
216
  if ("e" %in% mode) {
217
    # Calculate Edges Parameters
218
    e_index <- get_e(go)
219
    # if(!(edge_attr(go)%>%unlist()%>%is.null()))e_index=data.frame(edge_attr(go),e_index)
220
  }
221
  return(list(n_index = n_index, v_index = v_index, e_index = e_index))
222
}
223
224
225
#' Add topological indexes for a network
226
#' @param go igraph or metanet
227
#' @param force replace existed net_par
228
#'
229
#' @export
230
#' @rdname net_par
231
c_net_index <- function(go, force = FALSE) {
232
  if (!force) {
233
    if (!is.null(graph_attr(go)[["net_par"]])) stop("Already calculated net_pars, set `force = TRUE to replace existed net_par")
234
  }
235
  net_par(go, fast = FALSE) -> res
236
  graph_attr(go) <- as.list(res$n_index)
237
  graph_attr(go)[["net_par"]] <- TRUE
238
  vertex_attr(go) <- as.list(res$v_index)
239
  edge_attr(go) <- as.list(res$e_index)
240
  go
241
}
242
243
244
#' Fit power-law distribution for an igraph
245
#'
246
#' @param go igraph
247
#' @param p.value calculate p.value
248
#'
249
#' @return ggplot
250
#' @export
251
#' @family topological
252
#' @examples
253
#' fit_power(co_net)
254
fit_power <- function(go, p.value = FALSE) {
255
  x <- y <- formula <- NULL
256
  # igraph::degree distribution
257
  degree_dist <- table(igraph::degree(go))
258
  dat <- data.frame(degree = as.numeric(names(degree_dist)), count = as.numeric(degree_dist))
259
  # fit, set the original a & b
260
  mod <- stats::nls(count ~ a * degree^b, data = dat, start = list(a = 2, b = 1.5))
261
  summary(mod)
262
  # extract the coefficient
263
  a <- round(coef(mod)[1], 3)
264
  b <- round(coef(mod)[2], 3)
265
  fit <- fitted(mod)
266
  SSre <- sum((dat$count - fit)^2)
267
  SStot <- sum((dat$count - mean(dat$count))^2)
268
  R2 <- round(1 - SSre / SStot, 3)
269
270
  # bootstrap t get p.value
271
  if (p.value) {
272
    dat_rand <- dat
273
    p_num <- lapply(seq_len(999), \(i){
274
      dat_rand$count <- sample(dat_rand$count)
275
      SSre_rand <- sum((dat_rand$count - fit)^2)
276
      SStot_rand <- sum((dat_rand$count - mean(dat_rand$count))^2)
277
      R2_rand <- 1 - SSre_rand / SStot_rand
278
      R2_rand > R2
279
    })
280
    p_value <- (sum(unlist(p_num)) + 1) / (999 + 1)
281
  }
282
283
  p <- ggplot(dat, aes(x = degree, y = count)) +
284
    geom_point() +
285
    theme_bw() +
286
    stat_smooth(method = "nls", formula = y ~ a * x^b, method.args = list(start = list(a = 2, b = 1.5)), se = FALSE) +
287
    labs(x = "Degree", y = "Count")
288
289
  if (p.value) {
290
    label <- data.frame(
291
      x = 0.8 * max(dat$degree),
292
      y = c(0.9, 0.8, 0.7) * max(dat$count),
293
      formula = c(
294
        sprintf("italic(Y) == %.3f*italic(X)^%.3f", a, b),
295
        sprintf("italic(R^2) == %.3f", R2),
296
        sprintf("italic(P) < %.3f", p_value)
297
      )
298
    )
299
  } else {
300
    label <- data.frame(
301
      x = 0.8 * max(dat$degree),
302
      y = c(0.9, 0.8) * max(dat$count),
303
      formula = c(
304
        sprintf("italic(Y) == %.3f*italic(X)^%.3f", a, b),
305
        sprintf("italic(R^2) == %.3f", R2)
306
      )
307
    )
308
  }
309
  p + geom_text(aes(x = x, y = y, label = formula), data = label, parse = TRUE)
310
}
311
312
313
#' Degree distribution comparison with random network
314
#'
315
#' @param go igraph object
316
#' @param plot plot or not
317
#'
318
#' @return ggplot
319
#' @export
320
#' @family topological
321
#' @examples
322
#' rand_net(co_net)
323
rand_net <- function(go = go, plot = TRUE) {
324
  freq <- net <- NULL
325
  # generate a random network
326
  rand.g <- igraph::erdos.renyi.game(length(V(go)), length(E(go)), type = "gnm")
327
328
  if (!plot) {
329
    return(rand.g)
330
  }
331
332
  data1 <- data.frame(
333
    freq = igraph::degree_distribution(go), net = "Network",
334
    degree = 0:(length(degree_distribution(go)) - 1)
335
  )
336
337
  data2 <- data.frame(
338
    freq = igraph::degree_distribution(rand.g), net = "Random E-R",
339
    degree = 0:(length(degree_distribution(rand.g)) - 1)
340
  )
341
342
  # if data1[1,1]=0, it'is delete single vertex
343
  if (data1[1, 1] == 0) data1 <- data1[-1, ]
344
345
  data <- rbind(data1, data2)
346
  p1 <- ggplot(data) +
347
    geom_point(aes(x = degree, y = freq, group = net, fill = net), pch = 21, size = 2) +
348
    geom_smooth(aes(x = degree, y = freq, group = net, color = net), se = FALSE, method = "loess", formula = "y ~ x") +
349
    labs(x = "Degree", y = "Proportion") +
350
    scale_color_manual(values = c("#F58B8B", "#7AADF0")) +
351
    scale_fill_manual(values = c("#F58B8B", "#7AADF0")) +
352
    MetaNet_theme +
353
    theme(legend.position = c(0.8, 0.9), legend.title = element_blank())
354
  print(p1)
355
  return(rand.g)
356
}
357
358
#' Net_pars of many random network
359
#'
360
#' @param go igraph
361
#' @param reps simulation time
362
#' @param threads threads
363
#' @param verbose verbose
364
#'
365
#' @export
366
#' @rdname compare_rand
367
rand_net_par <- function(go, reps = 99, threads = 1, verbose = TRUE) {
368
  i <- NULL
369
  # parallel
370
  # main function
371
  loop <- function(i) {
372
    # generate a random network
373
    rand.g <- igraph::erdos.renyi.game(length(igraph::V(go)),
374
      length(igraph::E(go)),
375
      type = "gnm"
376
    )
377
    indexs <- net_par(rand.g, mode = "n")[["n_index"]]
378
    wc <- igraph::cluster_fast_greedy(rand.g)
379
    indexs$modularity <- igraph::modularity(wc)
380
    indexs
381
  }
382
  {
383
    if (threads > 1) {
384
      pcutils::lib_ps("foreach", "doSNOW", "snow", library = FALSE)
385
      if (verbose) {
386
        pb <- utils::txtProgressBar(max = reps, style = 3)
387
        opts <- list(progress = function(n) utils::setTxtProgressBar(pb, n))
388
      } else {
389
        opts <- NULL
390
      }
391
      cl <- snow::makeCluster(threads)
392
      doSNOW::registerDoSNOW(cl)
393
      res <- foreach::`%dopar%`(
394
        foreach::foreach(i = 1:reps, .options.snow = opts),
395
        loop(i)
396
      )
397
      snow::stopCluster(cl)
398
      gc()
399
    } else {
400
      res <- lapply(1:reps, loop)
401
    }
402
  }
403
  # simplify method
404
  rand_net_pars <- do.call(rbind, res)
405
406
  rand_net_pars
407
}
408
409
#' Compare some indexes between your net with random networks
410
#'
411
#' @param pars your net pars resulted by net_pars()
412
#' @param randp random networks pars resulted by rand_net_par()
413
#' @param index compared indexes: "Average_path_length","Clustering_coefficient" or else
414
#'
415
#' @return ggplot
416
#' @export
417
#' @family topological
418
#' @examples
419
#' data("c_net")
420
#' rand_net_par(co_net_rmt, reps = 30) -> randp
421
#' net_par(co_net_rmt, fast = FALSE) -> pars
422
#' compare_rand(pars, randp)
423
compare_rand <- function(pars, randp, index = c("Average_path_length", "Clustering_coefficient")) {
424
  V1 <- NULL
425
  labss <- t(pars$n_index[, index, drop = FALSE]) %>% as.data.frame()
426
  rownames(labss) -> labss$indexes
427
428
  p <- pcutils::group_box(randp[, index, drop = FALSE])
429
430
  p <- p +
431
    geom_hline(data = labss, aes(yintercept = V1), linetype = 2, color = "blue3") +
432
    geom_text(
433
      data = labss, aes(x = 1, y = V1 * 1.05, label = paste0("Network: ", round(V1, 3))),
434
      color = "blue3"
435
    ) +
436
    MetaNet_theme +
437
    theme(legend.position = "none", axis.text.x = element_blank())
438
  p
439
}
440
441
442
#' Calculate small-world coefficient
443
#'
444
#' @param go igraph or metanet
445
#' @param reps simulation time
446
#' @param threads threads
447
#' @param verbose verbose
448
#'
449
#' @return number
450
#' @export
451
#' @family topological
452
#' @examples
453
#' \donttest{
454
#' # set reps at least 99 when you run.
455
#' smallworldness(co_net, reps = 9)
456
#' }
457
smallworldness <- function(go, reps = 99, threads = 1, verbose = TRUE) {
458
  rand_net_par(go, reps = reps, threads = threads, verbose = verbose) -> rands
459
  small_world_coefficient <- (igraph::transitivity(go) / mean(rands$Clustering_coefficient)) /
460
    (igraph::average.path.length(go) / mean(rands$`Average_path_length`))
461
  small_world_coefficient
462
}