From 35b34bf1d5049904d75122ace847debe0d1595f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20L=C3=B6ffler?= Date: Mon, 15 Sep 2025 21:32:39 +0200 Subject: [PATCH 1/4] Fix 'get.edgelist.with.timestamps' to work with listed dates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct implementations of 'get.edgelist.with.timestamp' should retain the structure of listed edges and the POSIXct type of dates. Signed-off-by: Maximilian Löffler --- tests/test-misc.R | 28 ++++++++++++++++++++++++++-- util-misc.R | 21 ++++++++++++--------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/tests/test-misc.R b/tests/test-misc.R index 47be5454..40e40159 100644 --- a/tests/test-misc.R +++ b/tests/test-misc.R @@ -24,15 +24,18 @@ test_that("Get edgelist augmented with timestamps", { + ## + ## Artifical network + ## + ## construct network - edges = list(list("A", "A"), list("D", "C"), list("C", "A"), list("B", "C")) + edges = list(list("A", "A"), list("D", "C"), list("D", "C"), list("B", "C")) timestamps = c("2016-12-07 15:30:02", "2016-08-07 15:37:02", "2016-07-12 15:59:25", "2016-07-12 15:59:59") network = igraph::make_empty_graph(n = 0, directed = TRUE) + igraph::vertices("A", "B", "C", "D") + igraph::edges(edges, relation = "mail", date = timestamps) - ## get edgelist augmented with timestamps edgelist = get.edgelist.with.timestamps(network) @@ -45,6 +48,27 @@ test_that("Get edgelist augmented with timestamps", { expect_equal(actual[["to"]], edges[[i]][[2]]) expect_equal(actual[["date"]], timestamps[i]) }) + + ## + ## Authentic network + ## + + ## make network authentic + network = igraph::set_edge_attr(network, "date", value = get.date.from.string(timestamps)) + network = convert.edge.attributes.to.list(network) + network = simplify.network(network, remove.loops = FALSE) + + ## get edgelist augmented with timestamps + edgelist = get.edgelist.with.timestamps(network) + + ## construct expected result + expected.edges = data.frame(from = c("A", "B", "D"), to = c("A", "C", "C")) + expected.edges[["date"]] = list(as.list(get.date.from.string(timestamps[1])), + as.list(get.date.from.string(timestamps[4])), + as.list(get.date.from.string(c(timestamps[2], timestamps[3])))) + + ## check correctness + expect_equal(edgelist, expected.edges, info = "Edgelist from authentic network.") }) diff --git a/util-misc.R b/util-misc.R index 97900539..a0150414 100644 --- a/util-misc.R +++ b/util-misc.R @@ -46,15 +46,18 @@ requireNamespace("lubridate") # for date conversion #' #' @return the new edgelist get.edgelist.with.timestamps = function(net) { - ## get edge list as data.frame - edges = as.data.frame(igraph::as_edgelist(net)) - colnames(edges) = c("from", "to") - ## get timestamps - dates = igraph::edge_attr(net, "date") - ## bind everything together - edges = cbind(edges, date = dates) - - return(edges) + + ## get edge list as data.frame + edges = as.data.frame(igraph::as_edgelist(net)) + colnames(edges) = c("from", "to") + + ## get timestamps + dates = igraph::edge_attr(net, "date") + + ## bind everything together + edges[["date"]] = dates + + return(edges) } From 39bf1ddc570057fcae093252d7631e13bc0b5a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20L=C3=B6ffler?= Date: Tue, 16 Sep 2025 12:01:14 +0200 Subject: [PATCH 2/4] Add 'unlist.timestamps.if.possible' parameter to convert dates to vector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the parameter is TRUE timestamps in the edgelist returned by 'get.edgelist.with.timestamps' will be into vector. Unlisting fails if the input network contains simplified edges. Signed-off-by: Maximilian Löffler --- tests/test-misc.R | 55 ++++++++++++++++++++++++++++++++++++++++------- util-misc.R | 15 +++++++++++-- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/tests/test-misc.R b/tests/test-misc.R index 40e40159..e789abc7 100644 --- a/tests/test-misc.R +++ b/tests/test-misc.R @@ -15,7 +15,7 @@ ## Copyright 2017-2018 by Claus Hunsen ## Copyright 2017-2018 by Thomas Bock ## Copyright 2023 by Thomas Bock -## Copyright 2022-2023 by Maximilian Löffler +## Copyright 2022-2023, 2025 by Maximilian Löffler ## All Rights Reserved. @@ -25,7 +25,7 @@ test_that("Get edgelist augmented with timestamps", { ## - ## Artifical network + ## Artifical network (without unlisting timestamps) ## ## construct network @@ -36,7 +36,7 @@ test_that("Get edgelist augmented with timestamps", { igraph::vertices("A", "B", "C", "D") + igraph::edges(edges, relation = "mail", date = timestamps) - ## get edgelist augmented with timestamps + ## get edgelist with timestamps edgelist = get.edgelist.with.timestamps(network) ## check correctness @@ -50,16 +50,53 @@ test_that("Get edgelist augmented with timestamps", { }) ## - ## Authentic network + ## Authentic network (without unlisting timestamps) ## - ## make network authentic + ## make network authentic (dates as POSIXct in lists) network = igraph::set_edge_attr(network, "date", value = get.date.from.string(timestamps)) network = convert.edge.attributes.to.list(network) + + ## get edgelist with timestamps (without unlisting timestamps) + edgelist = get.edgelist.with.timestamps(network, unlist.timestamps.if.possible = FALSE) + + ## check correctness + expect_equal(names(edgelist), c("from", "to", "date")) + expect_equal(nrow(edgelist), 4) + lapply(1:4, function(i) { + actual = edgelist[i, ] + expect_equal(actual[["from"]], edges[[i]][[1]]) + expect_equal(actual[["to"]], edges[[i]][[2]]) + expect_equal(actual[["date"]], list(get.date.from.string(as.list(timestamps[i])))) + }) + + ## + ## Authentic network (with unlisting timestamps) + ## + + ## get edgelist with timestamps (with unlisting timestamps) + edgelist.unlisted.if.possible = get.edgelist.with.timestamps(network, unlist.timestamps.if.possible = TRUE) + + ## check correctness + expect_equal(names(edgelist.unlisted.if.possible), c("from", "to", "date")) + expect_equal(nrow(edgelist.unlisted.if.possible), 4) + lapply(1:4, function(i) { + actual = edgelist.unlisted.if.possible[i, ] + expect_equal(actual[["from"]], edges[[i]][[1]]) + expect_equal(actual[["to"]], edges[[i]][[2]]) + expect_equal(actual[["date"]], get.date.from.string(timestamps[i])) + }) + + ## + ## Authentic network (attempt and fail to unlist timestamps) + ## + + ## simplifying edges should make unlisting timestamps impossible network = simplify.network(network, remove.loops = FALSE) - ## get edgelist augmented with timestamps - edgelist = get.edgelist.with.timestamps(network) + ## get edgelist with timestamps + edgelist = get.edgelist.with.timestamps(network, unlist.timestamps.if.possible = FALSE) + edgelist.unlisted.if.possible = get.edgelist.with.timestamps(network, unlist.timestamps.if.possible = TRUE) ## construct expected result expected.edges = data.frame(from = c("A", "B", "D"), to = c("A", "C", "C")) @@ -68,7 +105,9 @@ test_that("Get edgelist augmented with timestamps", { as.list(get.date.from.string(c(timestamps[2], timestamps[3])))) ## check correctness - expect_equal(edgelist, expected.edges, info = "Edgelist from authentic network.") + expect_equal(edgelist, expected.edges, info = "Get edgelist with timestamps.") + expect_equal(edgelist.unlisted.if.possible, expected.edges, + info = "Get edgelist with timestamps (attempt to unlist timestamps fails).") }) diff --git a/util-misc.R b/util-misc.R index a0150414..579b2be2 100644 --- a/util-misc.R +++ b/util-misc.R @@ -20,7 +20,7 @@ ## Copyright 2018-2019 by Jakob Kronawitter ## Copyright 2021 by Niklas Schneider ## Copyright 2022 by Jonathan Baumann -## Copyright 2022-2024 by Maximilian Löffler +## Copyright 2022-2025 by Maximilian Löffler ## All Rights Reserved. @@ -43,9 +43,12 @@ requireNamespace("lubridate") # for date conversion #' in order to avoid problems accessing it. #' #' @param net the given network +#' @param unlist.timestamps.if.possible whether to unlist timestamps if they are given as lists. +#' Unlisting is not possible when \code{net} contains simplified edges. +#' [default: FALSE] #' #' @return the new edgelist -get.edgelist.with.timestamps = function(net) { +get.edgelist.with.timestamps = function(net, unlist.timestamps.if.possible = FALSE) { ## get edge list as data.frame edges = as.data.frame(igraph::as_edgelist(net)) @@ -54,6 +57,14 @@ get.edgelist.with.timestamps = function(net) { ## get timestamps dates = igraph::edge_attr(net, "date") + ## unlist timestamps + if (unlist.timestamps.if.possible && is.list(dates)) { + dates.flattened = do.call(base::c, unlist(dates, recursive = FALSE)) + if (nrow(edges) == length(dates.flattened)) { + dates = dates.flattened + } + } + ## bind everything together edges[["date"]] = dates From 08bcfbae53c22691fe17d2f7897b4e5ac9c7db93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20L=C3=B6ffler?= Date: Sun, 21 Sep 2025 14:40:28 +0200 Subject: [PATCH 3/4] Temporarily fix plot printing by replacing 'ggraph::scale_edge_linetype' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As discussed in PR#289, 'graph::scale_edge_linetype' produces a scale with 'palette' = NULL. Upon printing the resulting plot (as done in 'showcase.R') this invalid palette causes the following error: "Cannot convert `x` to discrete palette" We can fix the problem temporarily by creating the scale manually through 'ggplot2::discrete_scale' and setting the palette to the default linetype palette. Signed-off-by: Maximilian Löffler --- util-plot.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/util-plot.R b/util-plot.R index c1381d2f..05b5112c 100644 --- a/util-plot.R +++ b/util-plot.R @@ -15,7 +15,7 @@ ## Copyright 2018 by Barbara Eckl ## Copyright 2018 by Thomas Bock ## Copyright 2020-2021, 2025 by Thomas Bock -## Copyright 2024 by Maximilian Löffler +## Copyright 2024-2025 by Maximilian Löffler ## All Rights Reserved. @@ -167,7 +167,9 @@ plot.get.plot.for.network = function(network, labels = TRUE) { end = 0.8, begin = 0.05) + ## scale edges (colors and styles) - ggraph::scale_edge_linetype(name = "Relation Types") + + ggplot2::discrete_scale(name = "Relation Types", aesthetics = "edge_linetype", palette = scales::pal_linetype()) + + ## BROKEN RIGHT NOW due to bug in scale_linetype() internally invoked by scale_edge_linetype(): + # ggraph::scale_edge_linetype(name = "Relation Types") + ggplot2::discrete_scale(name = "Relations", aesthetics = "edge_colour", palette = viridis::viridis_pal(option = "viridis", end = 0.8, begin = 0.25)) + ## BROKEN RIGHT NOW due to bug in scale_edge_colour_viridis(): From 9ae2a8e85d581bd5cf02b46c833fdb7f3289b220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20L=C3=B6ffler?= Date: Mon, 29 Sep 2025 20:43:05 +0200 Subject: [PATCH 4/4] Update 'NEWS.md' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Löffler --- NEWS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index d7e41f0b..9c4a3142 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,7 +8,7 @@ - Add the possibility to split networks that contain simplified edges (PR #278, 9798d33512dcdf50d3b88a1223fc4913a2a88eeb, 0ed437c14423c1917f1ba470e7e55db4626d380b, 67a6651b94d50cb7c2ab4a74888b0556d607b102, 98ef83158204be2a67b115cb25df5ba375cccf60, 7ec4d83fdeb308a24a350acd808941807b9511f1, 637d62ab70f098f26f241e588a99cdc49d10f56a, 2c70666f128f96a3a573f29a0cbbef14d803d193, 1cbc6fa36859d6db3a7ff4493ef19763e87d2de3, 41788ff029d038969bfc6b5773e919201c5ac595, b042c0dd08e2229514ccd25dee7a119f25b1ab45, 36d23d657f412aa1953c4773076e593273f19d8e, 402c256d9a05e4ffb297d4ea1fc25d0230787bc0, 54af2b19a112070f10d191b98b055482748426a7, 894414a4a970822b9ecd59c0b6c480860707f636, 0fe32a259ef703c2de79135bfa6932a595fdc1c5) - Add functionality for commit-message content analysis, such as NLP tools including stemming, tokenization, and lemmatization, as well as a function to search for keywords in commit messages and a function to measure the length of the messages (PR #281, 5aa4e4193f0c00095fedf961c6060a5c035ef9c6, 99f0638566c0062b987617bc3fe3ace1db7729ee, e469d3a0cf2881c378469b6ccfea9c204d13f19b, 7d8fd39f164c776921e3fb36daf79256e7be7426, ef689f71f248059cc69be4792ca14ce3b95dcac8, 6e642242a3063663bcc3c7f5cca0650dfebb6bb4, f54439486115cada08dac23864b2f7605edca9ea, dd9246b2f4506d3d58f1c1f37fc198aaaafebb0d) -- Deprecate support for R version 4.0 because of breaking dependencies (PR #281, 3dc91b155b3e0e2a55378592db448606381f902e) +- Add `unlist.timestamps.if.possible` parameter to `get.edgelist.with.timestamps` which allows callers to request a conversion of the timestamps from list to vector if possible, i.e., when there are no simplified edges in the network (PR #289, 39bf1ddc570057fcae093252d7631e13bc0b5a55) ### Changed/Improved @@ -22,6 +22,7 @@ - Rename the `list.attributes` parameter in `add.vertex.attribute` and `split.and.add.vertex.attribute` to `flatten.values` with inverted semantics and introduce documentation for it to improve comprehensibility (PR #285, 7dab04a5251d89c9cb286452528ef8b6775a7347) - Enhance codeface testing data by ensuring that commit ids are unique between proximity and feature data and by adding commit data that includes (1) different commits that touch the same file / function, (2) commits that are authored at the same time by different authors (PR #286, 7481099af109e1897b9e5754beb1c7da9f39ffb9, 3e53285426010cf7bf48fa23daa484f29f80ac78) - Sort author data by `author.name` instead of `author.id` when reading it from a file (PR #286, 61b538b7cf81c4b6638951bfaf051e376d0986b3) +- Deprecate support for R version 4.0 because of breaking dependencies (PR #281, 3dc91b155b3e0e2a55378592db448606381f902e) ### Fixed @@ -30,6 +31,7 @@ - Ensure that POSIXct values are correctly handled in `add.vertex.attribute`, i.e., that they are not converted to numeric values (PR #285, 7dab04a5251d89c9cb286452528ef8b6775a7347, 4924ac23737dec6f915edaeb350a5cbaebbeec79) - Handle empty edges when constructing commit networks using commit-interaction data (PR #285, d5e1e4801230224e6272cd66873bd43bb7f04a00) - Correctly retain order of commit and mail data when merging it with PAStA, synchronicity, and commit message data (PR #286, 50b9b68effd49f853b8bcb335676357a854d1f97) +- Fix `get.edgelist.with.timestamps` to work correctly on networks with dates in default (list) format (PR #289, 35b34bf1d5049904d75122ace847debe0d1595f7) ## 5.0