File: layout_backbone.R

package info (click to toggle)
r-cran-graphlayouts 1.2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,660 kB
  • sloc: cpp: 696; sh: 13; makefile: 2
file content (156 lines) | stat: -rw-r--r-- 5,588 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#' backbone graph layout
#' @description emphasizes a hidden group structure if it exists in the graph. Calculates a layout for a sparsified network only including the most embedded edges. Deleted edges are added back after the layout is calculated.
#' @name layout_backbone
#' @param g igraph object
#' @param keep fraction of edges to keep during backbone calculation
#' @param backbone logical. Return edge ids of the backbone (Default: TRUE)
#' @details
#' The layout_igraph_* function should not be used directly. It is only used as an argument for plotting with 'igraph'.
#' 'ggraph' natively supports the layout.
#' @return list of xy coordinates and vector of edge ids included in the backbone
#' @examples
#' library(igraph)
#' \dontrun{
#' g <- sample_islands(9, 20, 0.4, 9)
#' g <- simplify(g)
#' V(g)$grp <- as.character(rep(1:9, each = 20))
#' bb <- layout_as_backbone(g, keep = 0.4)
#'
#' # add backbone links as edge attribute
#' E(g)$col <- FALSE
#' E(g)$col[bb$backbone] <- TRUE
#' }
#' @references Nocaj, A., Ortmann, M., & Brandes, U. (2015). Untangling the hairballs of multi-centered, small-world online social media networks. Journal of Graph Algorithms and Applications: JGAA, 19(2), 595-618.
#' @export
#'

layout_as_backbone <- function(g, keep = 0.2, backbone = TRUE) {
    ensure_igraph(g)
    if (!requireNamespace("oaqc", quietly = TRUE)) {
        stop("oaqc is needed for this function to work. Please install it.", call. = FALSE)
    }
    if (igraph::ecount(g) == 0) {
        stop("graph is empty")
    }
    if (igraph::any_multiple(g)) {
        stop("backbone layout does not work with multiple edges.")
    }
    if (igraph::is_directed(g)) {
        stop("backbone layout does not work with directed edges.")
    }
    if (igraph::any_loop(g)) {
        stop("backbone layout does not work with loops.")
    }

    if (any(igraph::components(g)$no > 1)) {
        warning("input graph is disconnected. The algorithm works best on connected graphs and may lead to misleading results for graphs with disconnected components. Run the algorithm on each component separately and delete isolated nodes to mitigate this issue.")
    }

    # weighting ----
    orbs <- oaqc::oaqc(igraph::as_edgelist(g, names = FALSE) - 1, non_ind_freq = TRUE)
    e11 <- orbs$e_orbits_non_ind[, 11]

    qu <- rep(0, igraph::vcount(g))
    el <- igraph::as_edgelist(g, names = FALSE)
    el <- cbind(el, e11)
    for (e in seq_len(nrow(el))) {
        qu[el[e, 1]] <- qu[el[e, 1]] + el[e, 3]
        qu[el[e, 2]] <- qu[el[e, 2]] + el[e, 3]
    }
    w <- apply(el, 1, function(x) x[3] / sqrt(qu[x[1]] * qu[x[2]]))

    w[is.na(w)] <- 0
    w[is.infinite(w)] <- 0
    igraph::E(g)$weight <- w

    # reweighting -----
    w <- max_prexif_jaccard(g)
    igraph::E(g)$weight <- w

    # umst ----
    g_umst <- umst(g)

    # filtering ----
    igraph::E(g)$bone <- w >= sort(w, decreasing = TRUE)[ceiling(igraph::ecount(g) * keep)]
    g_bone <- igraph::graph_from_edgelist(el[igraph::E(g)$bone, 1:2], directed = F)
    g_lay <- igraph::simplify(igraph::union(g_umst, g_bone))
    # if there is an issue with isolates (see #44)
    if (igraph::vcount(g_lay) != igraph::vcount(g)) {
        n_iso <- igraph::vcount(g) - igraph::vcount(g_lay)
        g_lay <- igraph::add_vertices(g_lay, n_iso)
    }
    if (backbone) {
        bb <- backbone_edges(g, g_lay)
    } else {
        bb <- NULL
    }
    xy <- layout_with_stress(g_lay)
    list(xy = xy, backbone = bb)
}

#-------------------------------------------------------------------------------
# helper functions
#-------------------------------------------------------------------------------

umst <- function(g) {
    el <- igraph::as_edgelist(g, names = FALSE)
    el <- cbind(el, igraph::E(g)$weight)
    el <- el[order(el[, 3], decreasing = TRUE), ]
    el <- cbind(el, rank(-el[, 3]))
    vfind <- 1:igraph::vcount(g)
    el_un <- matrix(0, 0, 2)
    for (i in unique(el[, 4])) {
        el_tmp <- matrix(0, 0, 2)
        Bi <- which(el[, 4] == i)
        for (e in Bi) {
            u <- el[e, 1]
            v <- el[e, 2]
            if (vfind[u] != vfind[v]) {
                el_tmp <- rbind(el_tmp, c(u, v))
            }
        }
        if (nrow(el_tmp) == 0) {
            next()
        }
        for (eb in seq_len(nrow(el_tmp))) {
            u <- el_tmp[eb, 1]
            v <- el_tmp[eb, 2]
            partu <- vfind[u]
            partv <- vfind[v]
            vfind[v] <- partu
            if (any(vfind == partv)) {
                vfind[vfind == partv] <- partu
            }
        }
        el_un <- rbind(el_un, el_tmp)
    }
    return(igraph::simplify(igraph::graph_from_edgelist(el_un, directed = FALSE)))
}


backbone_edges <- function(g, g_lay) {
    tmp <- rbind(igraph::as_edgelist(g_lay), igraph::as_edgelist(g, names = FALSE))
    which(duplicated(tmp)) - igraph::ecount(g_lay)
}

max_prexif_jaccard <- function(g) {
    if ("name" %in% igraph::vertex_attr_names(g)) {
        g <- igraph::delete_vertex_attr(g, "name")
    }
    el_tbl <- igraph::as_data_frame(g, "edges")

    N_ranks <- lapply(1:igraph::vcount(g), get_rank, el_tbl = el_tbl)
    el <- igraph::as_edgelist(g, names = FALSE)
    new_w <- reweighting(el - 1, N_ranks)
    new_w
}

get_rank <- function(el_tbl, u) {
    Nu_idx <- el_tbl[["from"]] == u | el_tbl[["to"]] == u
    omega <- el_tbl[Nu_idx, "weight"]
    Nu <- setdiff(c(el_tbl[Nu_idx, "from"], el_tbl[Nu_idx, "to"]), u)
    r <- rank(-omega)
    r <- match(r, sort(unique(r))) - 1
    Nru <- cbind(Nu - 1, r)
    Nru[order(Nru[, 2]), , drop = FALSE]
}