Diff of /R/2-1.build.R [000000] .. [13df9a]

Switch to unified view

a b/R/2-1.build.R
1
# =========2.build======
2
3
#' Construct a metanet from a corr object
4
#'
5
#' @param corr corr object from `c_net_calculate()` or `read_corr()`.
6
#' @param r_threshold r_threshold (default: >0.6).
7
#' @param p_threshold p_threshold (default: <0.05).
8
#' @param use_p_adj use the p.adjust instead of p.value (default: TRUE), if p.adjust not in the corr object, use p.value.
9
#' @param delete_single should delete single vertexes?
10
#'
11
#' @return an metanet object
12
#' @export
13
#'
14
#' @family build
15
#' @examples
16
#' data("otutab", package = "pcutils")
17
#' t(otutab) -> totu
18
#' metadata[, 3:10] -> env
19
#' c_net_calculate(totu) -> corr
20
#' c_net_build(corr, r_threshold = 0.65) -> co_net
21
#'
22
#' c_net_calculate(totu, env) -> corr2
23
#' c_net_build(corr2) -> co_net2
24
c_net_build <- function(corr, r_threshold = 0.6, p_threshold = 0.05, use_p_adj = TRUE, delete_single = TRUE) {
25
  stopifnot(inherits(corr, "corr"))
26
  # set thresholds to construct
27
  if ("r" %in% names(corr)) {
28
    occor.r <- corr$r
29
  } else {
30
    stop("No r in the input corr object.")
31
  }
32
33
  if (("p.adjust" %in% names(corr)) && use_p_adj) {
34
    occor.p <- corr$p.adjust
35
  } else {
36
    if ("p.value" %in% names(corr)) {
37
      occor.p <- corr$p.value
38
      message("Have not do p-value adjustment! use the p.value to build network.")
39
    } else {
40
      occor.p <- occor.r
41
      occor.p[occor.p != 0] <- 0
42
      message("No p.value or p.adjust in the input corr object, use the r to build network.")
43
    }
44
  }
45
46
  if (any(is.na(occor.r))) {
47
    occor.r[is.na(occor.r)] <- 0
48
    warning("NA values in the input corr object, set to 0.")
49
  }
50
  occor.r[occor.p > p_threshold | abs(occor.r) < r_threshold] <- 0
51
52
  # make igraph
53
  if (t_flag(occor.r)) {
54
    go <- igraph::graph_from_adjacency_matrix(as.matrix(occor.r),
55
      mode = "undirected", weighted = TRUE, diag = FALSE
56
    )
57
    igraph::graph.attributes(go)$n_type <- "single"
58
  } else {
59
    inset <- intersect(rownames(occor.r), colnames(occor.r))
60
    if (length(inset) > 0) {
61
      insets <- paste0(inset, collapse = ", ")
62
      warning("some nodes (row and column) has same name, please check: ", insets)
63
    }
64
    go <- igraph::graph_from_incidence_matrix(as.matrix(occor.r),
65
      directed = FALSE, weighted = TRUE
66
    )
67
    igraph::graph.attributes(go)$n_type <- "bipartite"
68
  }
69
70
  # delete single vertexes?
71
  if (delete_single) go <- igraph::delete.vertices(go, V(go)[igraph::degree(go) == 0])
72
73
  # set vertex attributes
74
  V(go)$v_group <- ifelse(V(go)$name %in% rownames(occor.r), "v_group1", "v_group2")
75
  V(go)$v_class <- ifelse(V(go)$name %in% rownames(occor.r), "v_class1", "v_class2")
76
  V(go)$size <- ceiling(60 / sqrt(length(V(go)))) + 1
77
  V(go)$label <- V(go)$name
78
79
  # abs edges weight
80
  go.weight <- E(go)$weight
81
  E(go)$cor <- go.weight
82
  E(go)$weight <- abs(go.weight)
83
84
  # time-consuming
85
  if (TRUE) {
86
    tmp_e <- get_e(go)
87
    if ("p.value" %in% names(corr)) {
88
      # E(go)$p.value <-get_e(go)%>%dplyr::select(from,to)%>%apply(., 1, \(x)occor.r$p.value[x[1],x[2]])
89
      tmp <- reshape2::melt(corr$p.value, varnames = c("from", "to"), value.name = "p.value", as.is = TRUE)
90
      tmp_e <- dplyr::left_join(tmp_e, tmp, by = c("from", "to"))
91
    }
92
    if ("p.adjust" %in% names(corr)) {
93
      # E(go)$p.adjust <-get_e(go)%>%dplyr::select(from,to)%>%apply(., 1, \(x)occor.r$p.adjust[x[1],x[2]])
94
      tmp <- reshape2::melt(corr$p.adjust, varnames = c("from", "to"), value.name = "p.adjust", as.is = TRUE)
95
      tmp_e <- dplyr::left_join(tmp_e, tmp, by = c("from", "to"))
96
    }
97
    # 应该直接expand,再left_join快很多
98
    igraph::edge.attributes(go) <- as.list(tmp_e)
99
  }
100
101
  # set edges type
102
  E(go)$e_type <- ifelse(go.weight > 0, "positive", "negative")
103
  # set edges width
104
  E(go)$width <- E(go)$weight
105
106
  if (!t_flag(occor.r)) {
107
    # set edges from_to
108
    anno_edge(go, get_v(go)[, c("name", "v_group")], verbose = FALSE) -> go
109
    # set edge intra-inter
110
    tmp_e <- igraph::edge.attributes(go)
111
    E(go)$e_class <- ifelse(tmp_e$v_group_from == tmp_e$v_group_to, "intra", "inter")
112
  }
113
114
  c_net_update(go, initialize = TRUE) -> go
115
116
  return(go)
117
}
118
119
120
#' Multi-omics network build
121
#'
122
#' @param ... some omics abundance tables
123
#' @param mode "full"
124
#' @param method "spearman" or "pearson"
125
#' @param filename the prefix of saved .corr file or FALSE
126
#' @param p.adjust.method see \code{\link[stats]{p.adjust}}
127
#' @param r_threshold r_threshold (default: >0.6)
128
#' @param p_threshold p_threshold (default: <0.05)
129
#' @param use_p_adj use the p.adjust instead of p-value (default: TRUE)
130
#' @param delete_single should delete single vertexes?
131
#'
132
#' @return metanet
133
#' @export
134
#' @family build
135
#' @examples
136
#' data("multi_test")
137
#' multi1 <- multi_net_build(list(Microbiome = micro, Metabolome = metab, Transcriptome = transc))
138
#' multi1 <- c_net_set(multi1, micro_g, metab_g, transc_g,
139
#'   vertex_class = c("Phylum", "kingdom", "type")
140
#' )
141
#' multi1 <- c_net_set(multi1, data.frame("Abundance1" = colSums(micro)),
142
#'   data.frame("Abundance2" = colSums(metab)), data.frame("Abundance3" = colSums(transc)),
143
#'   vertex_size = paste0("Abundance", 1:3)
144
#' )
145
#' c_net_plot(multi1)
146
multi_net_build <- function(..., mode = "full", method = "spearman",
147
                            filename = FALSE, p.adjust.method = NULL,
148
                            r_threshold = 0.6, p_threshold = 0.05, use_p_adj = TRUE, delete_single = TRUE) {
149
  tables <- list(...)
150
  if (all(class(tables[[1]]) == "list")) tables <- tables[[1]]
151
  tabs_name <- names(tables)
152
153
  tables <- check_tabs(tables)
154
  if (mode == "full") {
155
    all_totu <- do.call(cbind, tables)
156
    message("Calculating ", nrow(all_totu), " samples and ", ncol(all_totu), " features of ", length(tables), " groups.\n")
157
    all_corr <- c_net_calculate(all_totu, method = method, filename = filename, p.adjust.method = p.adjust.method)
158
159
    c_net_build(all_corr,
160
      r_threshold = r_threshold, p_threshold = p_threshold,
161
      use_p_adj = use_p_adj, delete_single = delete_single
162
    ) -> multi_net
163
164
    igraph::graph.attributes(multi_net)$n_type <- "multi_full"
165
166
    get_v(multi_net) -> tmp_v
167
    if (is.null(tabs_name)) tabs_name <- paste0("omic", seq_len(length(tables)))
168
    position <- rep(tabs_name, vapply(tables, ncol, numeric(1)))
169
    names(position) <- lapply(tables, colnames) %>% do.call(c, .)
170
171
    tmp_v$v_class <- tmp_v$v_group <- vapply(tmp_v$name, \(x)position[x], character(1))
172
    tmp_v %>% as.list() -> igraph::vertex_attr(multi_net)
173
174
    # set edges from_to
175
    suppressMessages(anno_edge(multi_net, tmp_v[, c("name", "v_group")], verbose = FALSE) -> multi_net)
176
    # set edge intra-inter
177
    tmp_e <- igraph::edge.attributes(multi_net)
178
    E(multi_net)$e_class <- ifelse(tmp_e$v_group_from == tmp_e$v_group_to, "intra", "inter")
179
    c_net_update(multi_net, initialize = TRUE, verbose = FALSE) -> multi_net
180
  }
181
  multi_net
182
}
183
184
185
default_v_color <- c(
186
  "#a6bce3", "#fb9a99", "#fdbf6f", "#1f78b4", "#b2df8a",
187
  "#cab2d6", "#33a02c", "#e31a1c", "#ff7f00", "#6a3d9a",
188
  "#F8CC00", "#b15928"
189
)
190
default_v_color_num <- c(
191
  "#FEE0D2", "#FCBBA1", "#FC9272", "#FB6A4A", "#EF3B2C", "#CB181D",
192
  "#A50F15", "#67000D"
193
)
194
default_e_color <- c(
195
  "#a6cee3", "#78c679", "#c2a5cf", "#ff7f00", "#1f78b4",
196
  "#810f7c", "#F8CC00", "#006d2c", "#4d4d4d", "#8c510a",
197
  "#d73027", "#7f0000", "#41b6c4", "#e7298a", "#54278f"
198
)
199
default_e_color_num <- c(
200
  "#E5F5E0", "#C7E9C0", "#A1D99B", "#74C476", "#41AB5D", "#238B45",
201
  "#006D2C", "#00441B"
202
)
203
204
default_v_shape <- c("circle" = 21, "square" = 22, "diamond" = 23, "triangle1" = 24, "triangle2" = 25) # "triangle", "diamond", "hexagon", "star"
205
206
color_generate <- function(v_class, n_break = 5, mode = "v",
207
                           v_color = default_v_color, v_color_num = default_v_color_num,
208
                           e_color = default_e_color, e_color_num = default_e_color_num) {
209
  # !!!现在的legend数字不好看,考虑pretty改进
210
  if (is.numeric(v_class)) {
211
    # n_color=n_break
212
    # n_label=seq(min(v_class, na.rm = TRUE), max(v_class, na.rm = TRUE), length.out = n_break)
213
214
    n_break <- pretty(v_class, n_break)
215
    n_color <- length(n_break) - 1
216
    n_label <- n_break[seq_len(n_color)] %>% format(., trim = TRUE)
217
218
    if (mode == "v") {
219
      return(as.character(cut(v_class,
220
        breaks = n_break,
221
        labels = pcutils::get_cols(n_color, v_color_num)
222
      )))
223
    } else if (mode == "e") {
224
      return(as.character(cut(v_class,
225
        breaks = n_break,
226
        labels = pcutils::get_cols(n_color, e_color_num)
227
      )))
228
    } else if (mode == "label") {
229
      return(as.character(cut(v_class,
230
        breaks = n_break,
231
        labels = n_label
232
      )))
233
    }
234
  } else {
235
    if (mode == "v") {
236
      return(pcutils::tidai(v_class, pcutils::get_cols(nlevels(factor(v_class)), v_color), fac = TRUE))
237
    } else if (mode == "e") {
238
      v_class <- droplevels(as.factor(v_class))
239
      if (all(levels(v_class) %in% c("negative", "positive"))) {
240
        ncols <- c(negative = "#E85D5D", positive = "#48A4F0")[levels(v_class)]
241
      } else if (all(levels(v_class) %in% c("inter-module", "intra-module"))) {
242
        ncols <- c("inter-module" = "#FA789A", "intra-module" = "#A6CEE3")[levels(v_class)]
243
      } else {
244
        ncols <- e_color
245
      }
246
      return(pcutils::tidai(v_class, pcutils::get_cols(nlevels(factor(v_class)), ncols), fac = TRUE))
247
    } else if (mode == "label") {
248
      return(v_class)
249
    }
250
  }
251
}
252
253
#' Update a metanet object or transform igraph object to metanet object
254
#'
255
#' @param go a metanet object or igraph object
256
#' @param node_break node_break if v_class is numeric, default: 5
257
#' @param initialize initialize?
258
#' @param edge_break edge_break if e_type is numeric, default: 5
259
#' @param verbose verbose?
260
#' @param uniq_v_class if TRUE, add prefix to v_class if multiple v_class belong to same v_group.
261
#'
262
#' @aliases as.metanet
263
#' @export
264
#' @family build
265
#' @return metanet
266
c_net_update <- function(go, node_break = 5, edge_break = 5, initialize = FALSE, verbose = TRUE, uniq_v_class = FALSE) {
267
  stopifnot(inherits(go, "igraph"))
268
  if (length(go) == 0) {
269
    message("Empty graph, return empty metanet object.")
270
    return(go)
271
  }
272
  # name
273
  if (!"name" %in% igraph::vertex_attr_names(go)) V(go)$name <- as.character(seq_len(length(go)))
274
  if (!"label" %in% igraph::vertex_attr_names(go)) V(go)$label <- V(go)$name
275
  get_v(go) -> tmp_v
276
  # v_size
277
  if (!"size" %in% colnames(tmp_v)) tmp_v$size <- 1
278
279
  # !!!从这里开始仔细修改,考虑对应关系
280
  # v_shape
281
  if (!"v_group" %in% colnames(tmp_v)) {
282
    tmp_v$v_group <- "v_group1"
283
  } else {
284
    tmp_v$v_group <- as.character(tmp_v$v_group)
285
    if (length(unique(tmp_v$v_group)) > 10) warning("Too many groups, please check the 'v_group'.")
286
  }
287
  flag <- TRUE
288
  if ("shape" %in% colnames(tmp_v)) {
289
    if (initialize) {
290
      if (e_match(tmp_v[, c("v_group", "shape")])) {
291
        flag <- FALSE
292
      } else if (verbose) message("'v_group' and 'shape' not match one by one, update 'shape'.")
293
    } else {
294
      if (e_match(tmp_v[, c("v_group", "shape")], test = 1)) {
295
        flag <- FALSE
296
      } else if (verbose) message("a 'v_group' should not match multiple shapes, update 'shape'.")
297
    }
298
  }
299
  if (flag) tmp_v$shape <- pcutils::tidai(tmp_v$v_group, names(default_v_shape), fac = TRUE)
300
301
  # v_color
302
  if (!"v_class" %in% colnames(tmp_v)) {
303
    tmp_v$v_class <- "v_class1"
304
  }
305
306
  if (uniq_v_class) {
307
    # 如果有同样的v_class对应不同v_group,则加上前缀
308
    dplyr::distinct_all(tmp_v[, c("v_class", "v_group")]) %>% dplyr::count(v_class) -> group_class
309
    group_class <- setNames(group_class$n, group_class$v_class)
310
    if (any(group_class > 1)) {
311
      la <- which(group_class > 1)
312
      if (verbose) message("some 'v_class' belong to multiple 'v_group': \n", paste0(names(la), collapse = ", "))
313
      tmp_v$v_class %in% names(la) -> r_index
314
      tmp_v$v_class[r_index] <- paste0(tmp_v$v_group[r_index], "-", tmp_v$v_class[r_index])
315
    }
316
  }
317
318
  flag <- TRUE
319
  if ("color" %in% colnames(tmp_v)) {
320
    if (initialize) {
321
      if (e_match(tmp_v[, c("v_class", "color")])) {
322
        flag <- FALSE
323
      } else if (verbose) message("'v_class' and 'color' not match one by one, update 'color'.")
324
    } else {
325
      if (e_match(tmp_v[, c("v_class", "color")], test = 1)) {
326
        flag <- FALSE
327
      } else if (verbose) message("a 'v_class' should not match multiple colors, update 'color'.")
328
    }
329
  }
330
331
  if (flag) {
332
    tmp_v$color <- color_generate(tmp_v$v_class, node_break, mode = "v")
333
    tmp_v$v_class <- color_generate(tmp_v$v_class, node_break, mode = "label")
334
  }
335
336
  as.list(tmp_v) -> igraph::vertex_attr(go)
337
338
  # e_color
339
  if (!"e_type" %in% igraph::edge_attr_names(go)) {
340
    E(go)$e_type <- "e_type1"
341
  }
342
  flag <- TRUE
343
  if ("color" %in% igraph::edge_attr_names(go)) {
344
    if (initialize) {
345
      if (e_match(data.frame(a = E(go)$e_type, b = E(go)$color))) {
346
        flag <- FALSE
347
      } else if (verbose) message("'e_type' and 'color' not match one by one, update 'color'.")
348
    } else {
349
      if (e_match(data.frame(a = E(go)$e_type, b = E(go)$color), test = 1)) {
350
        flag <- FALSE
351
      } else if (verbose) message("a 'e_type' should not match multiple colors, update 'color'.")
352
    }
353
  }
354
  if (flag) {
355
    E(go)$color <- color_generate(E(go)$e_type, edge_break, mode = "e")
356
    E(go)$e_type <- color_generate(E(go)$e_type, edge_break, mode = "label")
357
  }
358
359
  # e_lty
360
  if (!"e_class" %in% igraph::edge_attr_names(go)) {
361
    E(go)$e_class <- "e_class1"
362
  }
363
  flag <- TRUE
364
  if ("lty" %in% igraph::edge_attr_names(go)) {
365
    if (initialize) {
366
      if (e_match(data.frame(a = E(go)$e_class, b = E(go)$lty))) {
367
        flag <- FALSE
368
      } else if (verbose) message("'e_class' and 'lty' not match one by one, update 'lty'.")
369
    } else {
370
      if (e_match(data.frame(a = E(go)$e_class, b = E(go)$lty), test = 1)) {
371
        flag <- FALSE
372
      } else if (verbose) message("a 'e_class' should not match multiple linetypes, update 'lty'.")
373
    }
374
  }
375
  if (flag) E(go)$lty <- pcutils::tidai(E(go)$e_class, 1:4, fac = TRUE)
376
377
  # e_width
378
  if (!"width" %in% igraph::edge_attr_names(go)) {
379
    E(go)$width <- 1
380
  }
381
382
  class(go) <- c("metanet", "igraph")
383
  return(go)
384
}
385
386
387
#' Clean a igraph object
388
#'
389
#' @param go igraph, metanet objects
390
#' @param direct direct?
391
#'
392
#' @return a igraph object
393
#' @export
394
clean_igraph <- function(go, direct = TRUE) {
395
  stopifnot(inherits(go, "igraph"))
396
  go <- igraph::graph_from_data_frame(
397
    d = get_e(go)[, c("from", "to")],
398
    directed = direct,
399
    vertices = get_v(go)["name"]
400
  )
401
  go
402
}
403
404
405
#' Construct a network from edge_list dataframe
406
#'
407
#' @param edgelist first is source, second is target, others are annotation
408
#' @param vertex_df vertex metadata data.frame
409
#' @param direct logical
410
#' @param e_type set e_type
411
#' @param e_class set e_class
412
#' @return metanet
413
#' @export
414
#' @family build
415
#' @examples
416
#' data(edgelist)
417
#' edge_net <- c_net_from_edgelist(arc_count, vertex_df = arc_taxonomy)
418
#' edge_net <- c_net_set(edge_net, vertex_class = "Phylum", edge_width = "n")
419
#' c_net_plot(edge_net)
420
c_net_from_edgelist <- function(edgelist, vertex_df = NULL, direct = FALSE, e_type = NULL, e_class = NULL) {
421
  if (!is.null(vertex_df)) {
422
    if (!"name" %in% colnames(vertex_df)) {
423
      vertex_df$name <- rownames(vertex_df)
424
      message("No 'name' in the colnames(vertex_df), use rownames(vertex_df) as the 'name'.")
425
    }
426
    vertex_df <- data.frame(vertex_df[, "name", drop = FALSE], vertex_df[, setdiff(colnames(vertex_df), "name"), drop = FALSE])
427
  }
428
429
  if (!all(c("from", "to") %in% colnames(edgelist))) {
430
    message("No 'from' and 'to' in the colnames(edgelist), use the first two columns as the 'from' and 'to'.")
431
    colnames(edgelist)[1:2] <- c("from", "to")
432
  }
433
  edgelist <- data.frame(edgelist[, c("from", "to"), drop = FALSE], edgelist[, setdiff(colnames(edgelist), c("from", "to")), drop = FALSE])
434
  go <- igraph::graph_from_data_frame(edgelist, directed = direct, vertices = vertex_df)
435
  if (!is.null(e_type)) E(go)$e_type <- edgelist[, e_type]
436
  if (!is.null(e_class)) E(go)$e_class <- edgelist[, e_class]
437
  go <- c_net_update(go, initialize = TRUE)
438
  go
439
}
440
441
update_c_net_rda <- function() {
442
  data("otutab", package = "pcutils", envir = environment())
443
  t(otutab) -> totu
444
  metadata[, 3:10] -> env
445
  c_net_calculate(totu) -> corr
446
  c_net_build(corr, r_threshold = 0.65) -> co_net
447
  c_net_build(corr, r_threshold = 0.69) -> co_net_rmt
448
449
  c_net_calculate(totu, env) -> corr2
450
  c_net_build(corr2) -> co_net2
451
452
  co_net <- c_net_set(co_net, taxonomy, data.frame("Abundance" = colSums(totu)),
453
    vertex_class = "Phylum", vertex_size = "Abundance"
454
  )
455
  co_net2 <- c_net_set(co_net2, taxonomy, data.frame(name = colnames(env), env = colnames(env)),
456
    vertex_class = c("Phylum", "env")
457
  )
458
  co_net2 <- c_net_set(co_net2, data.frame("Abundance" = colSums(totu)), vertex_size = "Abundance")
459
  save(co_net, co_net2, co_net_rmt, file = "data/c_net.rda")
460
}