a b/R/combine_layers.R
1
#' Combine layers
2
#'
3
#' Return a merged graph from two graph layers.
4
#'
5
#' @param graph1 an igraph object or list of igraph (\code{list.igraph}).
6
#' @param graph2 an igraph object or list of igraph (\code{list.igraph}) with 
7
#'                the same length as \code{graph1}.
8
#' @param interaction.df (optional) a 2 colomns data.frame (from, to) 
9
#' describing the edges between vertices from both graphs.
10
#' 
11
#' @details
12
#' If \code{graph2} is a single graph, it will be merged to each element of 
13
#' \code{graph1} (\code{igraph} or \code{list.igraph}).
14
#' 
15
#' If \code{graph2} is a list of graph (\code{list.igraph}), each element of 
16
#' \code{graph1} and each element of \code{graph2} are merged in pairs.
17
#' 
18
#' Optionally, \code{interaction.df} should be provide if any vertex are shared 
19
#' between graphs. It can also be used to extend the first graph.
20
#' 
21
#' In both scenarios, vertex attributes are kept. If a vertex attribute is 
22
#' missing from graph1 or graph2, NULL value is added.
23
#' Otherwise, if there is an overlap between attribute values for the same 
24
#' vertex, attribute from graph2 is dropped.
25
#' 
26
#' @return 
27
#' a merged graph with both vertex attributes from graph1 and graph2.
28
#'
29
#' @examples
30
#' # with single graphs
31
#' graph1 <- igraph::graph_from_data_frame(list(from = c('A', 'B'),
32
#'                                              to = c('B', 'C')),
33
#'                                         directed = FALSE)
34
#' graph2 <- igraph::graph_from_data_frame(list(from = c(1), 
35
#'                                              to = c(2)),
36
#'                                         directed = FALSE)
37
#' res <- combine_layers(graph1 = graph1,
38
#'                       graph2 = graph2)
39
#' 
40
#' # with list of graphs
41
#' graph1.list <- list(graph1, graph1)
42
#' graph2.list <- list(graph2, graph2)
43
#' class(graph1.list) <- class(graph2.list) <- 'list.igraph'
44
#' 
45
#' res <- combine_layers(graph1 = graph1.list, 
46
#'                       graph2 = graph2)
47
#' res <- combine_layers(graph1 = graph1.list, 
48
#'                       graph2 = graph2.list)
49
#' 
50
#' # with interaction dataframe
51
#' interaction.df1 <- as.data.frame(list(from = c('C', 'B'), to = c(1, 2)))
52
#' res <- combine_layers(graph1 = graph1.list, 
53
#'                       graph2 = graph2, 
54
#'                       interaction.df = interaction.df1)
55
#' 
56
#' 
57
#' @importFrom purrr is_empty map reduce map2
58
#' @importFrom igraph induced_subgraph
59
#' @importFrom igraph set_vertex_attr
60
#' @importFrom igraph adjacent_vertices
61
#' @importFrom igraph graph_from_data_frame
62
#' @importFrom igraph vcount
63
#' @importFrom igraph V
64
#' @importFrom igraph as.undirected
65
66
#' @export
67
combine_layers <- function(graph1, 
68
                           graph2 = NULL, 
69
                           interaction.df = NULL) {
70
    
71
    # check graph1
72
    if (!is(graph1, "igraph") & !is(graph1, "list.igraph")) {
73
        stop("graph1 must be an igraph or list.igraph object")
74
    }
75
    if (is(graph1, "list.igraph")) {
76
        if (is.null(names(graph1))) {
77
            names(graph1) <- seq_along(graph1)
78
        }
79
    }
80
    
81
    if (!is(graph2, "igraph") & !is(graph2, "list.igraph") & !is.null(graph2)) {
82
        stop("graph2 must be an igraph or list.igraph object or NULL")
83
    }
84
    if (!is.null(interaction.df)) {
85
        interaction.df <- check_db(interaction.df)
86
        
87
        if (!is(interaction.df, "igraph")) {
88
            interaction.df <- interaction.df %>%
89
                dplyr::select(c("from", "to"))
90
            interaction.graph <- igraph::graph_from_data_frame(interaction.df, 
91
                                                               directed = FALSE)
92
        } else {
93
            interaction.graph <- igraph::as.undirected(interaction.df)
94
        }
95
    }
96
    
97
    # case1: graph2 = NULL, interaction.df = NULL
98
    if (is.null(graph2) & is.null(interaction.df)) {
99
        merged.res <- graph1
100
    }
101
    
102
    # case2: graph1 and graph2 are single graph (+ interaction.df)
103
    if (is(graph1, "igraph") & is(graph2, "igraph")) {
104
        merged.res <- merge_graphs(graph1, graph2)
105
        if (!is.null(interaction.df)) {
106
            # interaction.graph can be not found, df can be NULL
107
            interaction.graph.induced <- igraph::induced_subgraph(
108
                graph = interaction.graph, 
109
                vids = intersect(igraph::V(interaction.graph)$name, 
110
                                 igraph::V(merged.res)$name))
111
            merged.res <- merge_graphs(merged.res, 
112
                                       interaction.graph.induced)
113
        }
114
        
115
        # case3: graph1 is a list and graph2 is a single graph 
116
        # (+ interaction.df)
117
    } else if (is(graph1, "list.igraph") & is(graph2, "igraph")) {
118
        merged.res <- purrr::map(graph1, ~{
119
            merge_graphs(.x, graph2)
120
        })
121
        names(merged.res) <- names(graph1)
122
        if (!is.null(interaction.df)) {
123
            # interaction.graph can be not found, df can be NULL 
124
            # merged.res <- list() # already defined
125
            for (i in names(merged.res)) {
126
                interaction.graph.induced <- igraph::induced_subgraph(
127
                    graph = interaction.graph, 
128
                    vids = intersect(igraph::V(interaction.graph)$name,
129
                                     igraph::V(merged.res[[i]])$name))
130
                merged.res[[i]] <- merge_graphs(merged.res[[i]], 
131
                                                interaction.graph.induced)
132
            }
133
        }
134
        
135
        # case4: graph1 and graph2 are list of graph (+ interaction.df)
136
    } else if (is(graph1, "list.igraph") & is(graph2, "list.igraph")) {
137
        if (length(graph1) != length(graph2)) {
138
            stop("graph1 and graph2 must have the same length")
139
        }
140
        if (!is.null(names(graph1)) & !is.null(names(graph2))) {
141
            # graph1 and graph2 have names same length 
142
            # so reciprocal is TRUE they don't have the same names
143
            if (!all(names(graph1) %in% names(graph2))) {
144
                stop("graph1 and graph2 must have the same names")
145
            } else {
146
                merged.res <- purrr::map2(graph1, graph2[names(graph1)], ~{
147
                    merge_graphs(.x, .y)
148
                })
149
            }
150
        } else {
151
            # no names, don't care about the order
152
            merged.res <- purrr::map2(graph1, graph2, ~{
153
                merge_graphs(.x, .y)
154
            })
155
            names(merged.res) <- names(graph1)
156
        }
157
        if (!is.null(interaction.df)) {
158
            # interaction.graph can be not found, df can be NULL
159
            for (i in names(merged.res)) {
160
                interaction.graph.induced <- igraph::induced_subgraph(
161
                    graph = interaction.graph, 
162
                    vids = intersect(igraph::V(interaction.graph)$name,
163
                                     igraph::V(merged.res[[i]])$name))
164
                merged.res[[i]] <- merge_graphs(merged.res[[i]], 
165
                                                interaction.graph.induced)
166
            }
167
        }
168
        
169
        # case5: inverse of case3 -> error
170
    } else if (is(graph1, "igraph") & is(graph2, "list.igraph")) {
171
        stop("graph1 and graph2 must have the same length")
172
        
173
        # case6: graph1 and interaction.df
174
    } else if (is(graph1, "igraph") & 
175
               is.null(graph2) & 
176
               !is.null(interaction.df)) {
177
        interaction.df.sub <- interaction.df %>%
178
            dplyr::filter(.$from %in% igraph::V(graph1)$name | 
179
                              .$to %in% igraph::V(graph1)$name)
180
        interaction.graph <- igraph::graph_from_data_frame(interaction.df.sub, 
181
                                                           directed = FALSE)
182
        merged.res <- merge_graphs(graph1, interaction.graph)
183
        
184
        # case7: graph1 list and interaction.df
185
    } else if (is(graph1, "list.igraph") & 
186
               is.null(graph2) & 
187
               !is.null(interaction.df)) {
188
        merged.res <- list()
189
        for (i in names(graph1)) {
190
            interaction.df.sub <- interaction.df %>%
191
                dplyr::filter(.$from %in% igraph::V(graph1[[i]])$name | 
192
                                  .$to %in% igraph::V(graph1[[i]])$name)
193
            interaction.graph <- igraph::graph_from_data_frame(
194
                interaction.df.sub, 
195
                directed = FALSE)
196
            merged.res[[i]] <- merge_graphs(graph1[[i]], interaction.graph)
197
        }
198
    }
199
    
200
    if (is(merged.res, "list")) {
201
        class(merged.res) <- c("list.igraph", "list.merged.igraph")
202
    }
203
    return(merged.res)
204
}
205
206
207
#' @importFrom igraph vertex_attr 
208
#' @importFrom igraph union 
209
#' @importFrom igraph delete_vertex_attr 
210
#' @importFrom igraph set_vertex_attr 
211
#' @importFrom igraph vcount
212
merge_graphs <- function(graph1, 
213
                         graph2) {
214
    # shared attr except 'name'
215
    shared_attr <- intersect(names(igraph::vertex_attr(graph1)), 
216
                             names(igraph::vertex_attr(graph2)))
217
    shared_attr <- shared_attr[!(shared_attr == "name")]
218
    
219
    merged_graphs <- igraph::union(graph1, graph2)
220
    # vertex_attr(merged_graphs) %>% as.data.frame()
221
    merged_attr <- igraph::vertex_attr(merged_graphs)
222
    for (sa in shared_attr) {
223
        merged_attr[[sa]] <- vector(length = igraph::vcount(merged_graphs))
224
        for (i in seq_along(merged_attr[[sa]])) {
225
            # if !is.na _1, return _1 else return _2
226
            merged_attr[[sa]][i] <- 
227
                ifelse(!is.na(merged_attr[[paste0(sa, "_1")]][i]), 
228
                       merged_attr[[paste0(sa, "_1")]][i], 
229
                       merged_attr[[paste0(sa, "_2")]][i])
230
        }
231
        merged_graphs <- delete_vertex_attr(graph = merged_graphs, 
232
                                            name = paste0(sa, "_1"))
233
        merged_graphs <- delete_vertex_attr(graph = merged_graphs, 
234
                                            name = paste0(sa, "_2"))
235
        merged_graphs <- set_vertex_attr(graph = merged_graphs, 
236
                                         name = sa, value = merged_attr[[sa]])
237
    }
238
    class(merged_graphs) <- c("merged.igraph", "igraph")
239
    return(merged_graphs)
240
}