From 8a91ab6074e1ae7279822a0f783994fddbf05689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20L=C3=B6ffler?= Date: Tue, 3 Dec 2024 15:20:38 +0100 Subject: [PATCH] Convert edge attributes to list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since igraph version 2.1, when joining networks using 'igraph::disjoint_union', edge attributes of the joining networks require identical types. As simplifiying networks necessarily converts types of edge attributes to list when merging edges, attributes now have to be of type list by default. Edge attributes that are explicitly considered during simplification and, therefore, are not converted to lists are excluded from this rule. This works towards fixing #271. Signed-off-by: Maximilian Löffler --- util-networks.R | 60 ++++++++++++++++++++++++++++++++++++++++--------- util-split.R | 6 ++--- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/util-networks.R b/util-networks.R index 648fbac9..1edbb4ab 100644 --- a/util-networks.R +++ b/util-networks.R @@ -978,6 +978,7 @@ NetworkBuilder = R6::R6Class("NetworkBuilder", attr(net, "range") = private$proj.data$get.range() } + net = convert.edge.attributes.to.list(net) return(net) }, @@ -1026,6 +1027,7 @@ NetworkBuilder = R6::R6Class("NetworkBuilder", attr(net, "range") = private$proj.data$get.range() } + net = convert.edge.attributes.to.list(net) return(net) }, @@ -1068,6 +1070,7 @@ NetworkBuilder = R6::R6Class("NetworkBuilder", attr(net, "range") = private$proj.data$get.range() } + net = convert.edge.attributes.to.list(net) return(net) }, @@ -1174,6 +1177,7 @@ NetworkBuilder = R6::R6Class("NetworkBuilder", attr(network, "range") = private$proj.data$get.range() } + network = convert.edge.attributes.to.list(network) return(network) }, @@ -1285,15 +1289,15 @@ NetworkBuilder = R6::R6Class("NetworkBuilder", ## Note: The following temporary fix only considers the 'date' attribute. However, this problem could also ## affect several other attributes, whose classes are not adjusted in our temporary fix. ## The following code block should be redundant as soon as igraph has fixed their bug. - u.actual.edge.attribute.date = igraph::edge_attr(u, "date") - if (!is.null(u.actual.edge.attribute.date)) { - if (is.list(u.actual.edge.attribute.date)) { - u.expected.edge.attribute.date = lapply(u.actual.edge.attribute.date, get.date.from.unix.timestamp) - } else { - u.expected.edge.attribute.date = get.date.from.unix.timestamp(u.actual.edge.attribute.date) - } - u = igraph::set_edge_attr(u, "date", value = u.expected.edge.attribute.date) - } + # u.actual.edge.attribute.date = do.call(base::c, igraph::edge_attr(u, "date")) + # if (!is.null(u.actual.edge.attribute.date)) { + # if (is.list(u.actual.edge.attribute.date)) { + # u.expected.edge.attribute.date = lapply(u.actual.edge.attribute.date, get.date.from.unix.timestamp) + # } else { + # u.expected.edge.attribute.date = get.date.from.unix.timestamp(u.actual.edge.attribute.date) + # } + # u = igraph::set_edge_attr(u, "date", value = as.list(u.actual.edge.attribute.date)) + # } ## 2) add the bipartite edges u = add.edges.for.bipartite.relation(u, authors.to.artifacts, private$network.conf) @@ -1632,6 +1636,7 @@ construct.network.from.edge.list = function(vertices, edge.list, network.conf, d ## initialize edge weights net = igraph::set_edge_attr(net, "weight", value = 1) + net = convert.edge.attributes.to.list(net) logging::logdebug("construct.network.from.edge.list: finished.") @@ -1792,6 +1797,13 @@ add.edges.for.bipartite.relation = function(net, bipartite.relations, network.co extra.edge.attributes["type"] = TYPE.EDGES.INTER # add egde type extra.edge.attributes["relation"] = relation # add relation type + ## convert edge attributes to list similarely to 'convert.edge.attributes.to.list' + edge.attrs = names(extra.edge.attributes) + which.attrs = !(edge.attrs %in% names(EDGE.ATTR.HANDLING)) + for (attr in edge.attrs[which.attrs]) { + extra.edge.attributes[[attr]] = as.list(extra.edge.attributes[[attr]]) + } + ## add the vertex sequences as edges to the network net = igraph::add_edges(net, unlist(vertex.sequence.for.edges), attr = extra.edge.attributes) } @@ -1812,7 +1824,7 @@ create.empty.network = function(directed = TRUE, add.attributes = FALSE) { # set proper attributes if wanted if (add.attributes) { mandatory.edge.attributes.classes = list( - date = c("POSIXct", "POSIXt"), artifact.type = "character", weight = "numeric", + date = "list", artifact.type = "list", weight = "numeric", type = "character", relation = "character" ) mandatory.vertex.attributes.classes = list(name = "character", kind = "character", type = "character") @@ -2146,6 +2158,34 @@ get.data.sources.from.relations = function(network) { return(data.sources) } +#' Convert edge attributes to list type. +#' +#' This conversion is necessary to ensure merging networks works in all cases, +#' especially when merging simplified networks with unsimplified networks as +#' simplification may convert edge attributes to list type. Attributes that are +#' explicitly considered during simplification (through EDGE.ATTR.HANDLING) +#' generally do not need to be converted. +#' +#' @param network the network of which the edge attributes are to be converted +#' @param remain.as.is the edge attributes to remain as they are +#' [default: names(EDGE.ATTR.HANDLING)] +#' +#' @return the network with converted edge attributes +convert.edge.attributes.to.list = function(network, remain.as.is = names(EDGE.ATTR.HANDLING)) { + + ## get edge attributes + edge.attrs = igraph::edge_attr_names(network) + which.attrs = !(edge.attrs %in% remain.as.is) + + ## convert edge attributes to list type + for (attr in edge.attrs[which.attrs]) { + list.attr = as.list(igraph::edge_attr(network, attr)) + network = igraph::set_edge_attr(network, attr, value = list.attr) + } + + return(network) +} + ## / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / ## Sample network ---------------------------------------------------------- diff --git a/util-split.R b/util-split.R index 2bce7739..bb51e517 100644 --- a/util-split.R +++ b/util-split.R @@ -524,7 +524,7 @@ split.network.time.based = function(network, time.period = "3 months", bins = NU number.windows = NULL, sliding.window = FALSE, remove.isolates = TRUE) { ## extract date attributes from edges - dates = get.date.from.unix.timestamp(igraph::edge_attr(network, "date")) + dates = do.call(base::c, igraph::edge_attr(network, "date")) ## number of windows given (ignoring time period and bins) if (!is.null(number.windows)) { @@ -619,7 +619,7 @@ split.networks.time.based = function(networks, time.period = "3 months", bins = dates = igraph::E(net)$date return(dates) }) - dates = unlist(networks.dates, recursive = FALSE) + dates = unlist(networks.dates) dates = get.date.from.unix.timestamp(dates) ## 2) get bin information @@ -709,7 +709,7 @@ split.network.activity.based = function(network, number.edges = 5000, number.win ## get dates in a data.frame for splitting purposes df = data.frame( - date = get.date.from.unix.timestamp(igraph::edge_attr(network, "date")), + date = do.call(base::c, igraph::edge_attr(network, "date")), my.unique.id = seq_len(edge.count) # as a unique identifier only ) ## sort by date