Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 55 additions & 12 deletions R/centrality.R
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ graph.diversity <- function(graph, weights = NULL, vids = V(graph)) {
#' `evcent()` was renamed to [eigen_centrality()] to create a more
#' consistent API.
#' @inheritParams eigen_centrality
#' @param directed Logical scalar, whether to consider direction of the edges
#' in directed graphs. It is ignored for undirected graphs.
#' @keywords internal
#' @export
evcent <- function(
Expand Down Expand Up @@ -1312,7 +1314,7 @@ eigen_defaults <- function() {
#' computation, see [arpack()] for more about ARPACK in igraph.
#'
#' @param graph Graph to be analyzed.
#' @param directed Logical scalar, whether to consider direction of the edges
#' @param directed `r lifecycle::badge("deprecated")` Logical scalar, whether to consider direction of the edges
#' in directed graphs. It is ignored for undirected graphs.
#' @param scale `r lifecycle::badge("deprecated")` Normalization will always take
#' place.
Expand All @@ -1329,6 +1331,17 @@ eigen_defaults <- function() {
#' weights spread the centrality better.
#' @param options A named list, to override some ARPACK options. See
#' [arpack()] for details.
#' @param mode How to consider edge directions in directed graphs.
#' It is ignored for undirected graphs.
#' Possible values:
#' - `"out"` the left eigenvector of the adjacency matrix is calculated,
#' i.e. the centrality of a vertex is proportional to the sum of centralities
#' of vertices pointing to it. This is the standard eigenvector centrality.
#' - `"in"` the right eigenvector of the adjacency matrix is calculated,
#' i.e. the centrality of a vertex is proportional to the sum of centralities
#' of vertices it points to.
#' - `"all"` edge directions are ignored,
#' and the unweighted eigenvector centrality is calculated.
#' @return A named list with components:
#' \describe{
#' \item{vector}{
Expand Down Expand Up @@ -1358,10 +1371,11 @@ eigen_defaults <- function() {
#' @cdocs igraph_eigenvector_centrality
eigen_centrality <- function(
graph,
directed = FALSE,
directed = deprecated(),
scale = deprecated(),
weights = NULL,
options = arpack_defaults()
options = arpack_defaults(),
mode = c("out", "in", "all")
) {
if (is.function(options)) {
lifecycle::deprecate_soft(
Expand Down Expand Up @@ -1390,10 +1404,33 @@ eigen_centrality <- function(
}
}

mode <- igraph.match.arg(mode)

if (lifecycle::is_present(directed)) {
if (directed) {
lifecycle::deprecate_soft(
"2.2.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and everywhere:

Suggested change
"2.2.0",
"3.0.0",

"eigen_centrality(directed)",
details = "Use the mode argument."
)
if (!lifecycle::is_present(mode)) {
mode <- "out"
}
} else {
lifecycle::deprecate_soft(
"2.2.0",
"eigen_centrality(directed)",
details = "Use the mode argument."
)
if (!lifecycle::is_present(mode)) {
mode <- "all"
}
}
}

eigenvector_centrality_impl(
graph = graph,
directed = directed,
scale = TRUE,
mode = mode,
weights = weights,
options = options
)
Expand Down Expand Up @@ -1517,9 +1554,7 @@ diversity <- function(graph, weights = NULL, vids = V(graph)) {
#' scores are the same as authority scores.
#'
#' @param graph The input graph.
#' @param scale Logical scalar, whether to scale the result to have a maximum
#' score of one. If no scaling is used then the result vector has unit length
#' in the Euclidean norm.
#' @param scale `r lifecycle::badge("deprecated")` Ignored, always scaled.
#' @param weights Optional positive weight vector for calculating weighted
#' scores. If the graph has a `weight` edge attribute, then this is used
#' by default. Pass `NA` to ignore the weight attribute. This function
Expand Down Expand Up @@ -1565,15 +1600,22 @@ diversity <- function(graph, weights = NULL, vids = V(graph)) {
hits_scores <- function(
graph,
...,
scale = TRUE,
scale = deprecated(),
weights = NULL,
options = arpack_defaults()
) {
rlang::check_dots_empty()

if (lifecycle::is_present(scale)) {
lifecycle::deprecate_soft(
"2.1.5",
"hits_scores(scale = )",
details = "The function always behaves as if `scale = TRUE`.
The argument will be removed in the future."
)
}
hub_and_authority_scores_impl(
graph = graph,
scale = scale,
weights = weights,
options = options
)
Expand Down Expand Up @@ -1609,7 +1651,8 @@ authority_score <- function(
weights = weights,
options = options
)
scores$hub <- NULL
scores[["hub_vector"]] <- NULL

rlang::set_names(scores, c("vector", "value", "options"))
}

Expand Down Expand Up @@ -1654,7 +1697,7 @@ hub_score <- function(
weights = weights,
options = options
)
scores$authority <- NULL
scores[["authority_vector"]] <- NULL
rlang::set_names(scores, c("vector", "value", "options"))
}

Expand Down
100 changes: 81 additions & 19 deletions R/centralization.R
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ centralization.evcent.tmax <- function(
#' `centralization.evcent()` was renamed to [centr_eigen()] to create a more
#' consistent API.
#' @inheritParams centr_eigen
#' @param directed logical scalar, whether to use directed shortest paths for
#' calculating eigenvector centrality.
#' @keywords internal
#' @export
centralization.evcent <- function(
Expand Down Expand Up @@ -306,7 +308,7 @@ NULL
#'
#' # Calculate centralization from pre-computed scores
#' deg <- degree(g)
#' tmax <- centr_degree_tmax(g, loops = FALSE)
#' tmax <- centr_degree_tmax(g, loops = "none")
#' centralize(deg, tmax)
#'
#' # The most centralized graph according to eigenvector centrality
Expand Down Expand Up @@ -385,8 +387,11 @@ centr_degree <- function(
#' @param nodes The number of vertices. This is ignored if the graph is given.
#' @param mode This is the same as the `mode` argument of `degree()`. Ignored
#' if `graph` is given and the graph is undirected.
#' @param loops Logical scalar, whether to consider loops edges when
#' calculating the degree.
#' @param loops Character string,
#' `"none"` (the default) ignores loop edges;
#' `"once"` counts each loop edge only once;
#' `"twice"` counts each loop edge twice in undirected graphs.
#' and once in directed graphs.
Comment on lines +393 to +394
Copy link

Copilot AI Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 394 appears orphaned from the previous line's structure. It should either be joined to line 393 or reformatted as a continuation of the bullet point structure.

Suggested change
#' `"twice"` counts each loop edge twice in undirected graphs.
#' and once in directed graphs.
#' `"twice"` counts each loop edge twice in undirected graphs and once in directed graphs.

Copilot uses AI. Check for mistakes.
#' @return Real scalar, the theoretical maximum (unnormalized) graph degree
#' centrality score for graphs with given order and other parameters.
#'
Expand All @@ -397,8 +402,8 @@ centr_degree <- function(
#' @examples
#' # A BA graph is quite centralized
#' g <- sample_pa(1000, m = 4)
#' centr_degree(g, normalized = FALSE)$centralization %>%
#' `/`(centr_degree_tmax(g, loops = FALSE))
#' centr_degree(g, normalized = FALSE)$centralization /
#' centr_degree_tmax(g, loops = "twice")
Comment on lines +405 to +406
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember I wasn't sure about this. For centr_degree(), the default is "twice" IIRC. Is then the same loops value needed in the denominator?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is correct.

#' centr_degree(g, normalized = TRUE)$centralization
centr_degree_tmax <- function(
graph = NULL,
Expand All @@ -412,16 +417,14 @@ centr_degree_tmax <- function(
what = "centr_degree_tmax(loops = 'must be explicit')",
details = "The default value (currently `FALSE`) will be dropped in the next release. Add an explicit value for the `loops` argument."
)
loops <- FALSE
loops <- "none"
}

# Argument checks
ensure_igraph(graph, optional = TRUE)

nodes <- as.numeric(nodes)

loops <- as.logical(loops)

# Function call
res <- centralization_degree_tmax_impl(
graph,
Expand Down Expand Up @@ -611,12 +614,22 @@ centr_clo_tmax <- function(
#' See [centralize()] for a summary of graph centralization.
#'
#' @param graph The input graph.
#' @param directed logical scalar, whether to use directed shortest paths for
#' calculating eigenvector centrality.
#' @param directed `r lifecycle::badge("deprecated")` Use `mode` instead.
#' @param scale `r lifecycle::badge("deprecated")` Ignored. Computing
#' eigenvector centralization requires normalized eigenvector centrality scores.
#' @param options This is passed to [eigen_centrality()], the options
#' for the ARPACK eigensolver.
#' @param mode How to consider edge directions in directed graphs.
#' It is ignored for undirected graphs.
#' Possible values:
#' - `"out"` the left eigenvector of the adjacency matrix is calculated,
#' i.e. the centrality of a vertex is proportional to the sum of centralities
#' of vertices pointing to it. This is the standard eigenvector centrality.
#' - `"in"` the right eigenvector of the adjacency matrix is calculated,
#' i.e. the centrality of a vertex is proportional to the sum of centralities
#' of vertices it points to.
#' - `"all"` edge directions are ignored,
#' and the unweighted eigenvector centrality is calculated.
#' @param normalized Logical scalar. Whether to normalize the graph level
#' centrality score by dividing by the theoretical maximum.
#' @return A named list with the following components:
Expand Down Expand Up @@ -659,10 +672,11 @@ centr_clo_tmax <- function(
#' @cdocs igraph_centralization_eigenvector_centrality
centr_eigen <- function(
graph,
directed = FALSE,
directed = deprecated(),
scale = deprecated(),
options = arpack_defaults(),
normalized = TRUE
normalized = TRUE,
mode = c("out", "in", "all")
) {
if (lifecycle::is_present(scale)) {
lifecycle::deprecate_soft(
Expand All @@ -673,12 +687,35 @@ centr_eigen <- function(
)
}

mode <- igraph.match.arg(mode)

if (lifecycle::is_present(directed)) {
if (directed) {
lifecycle::deprecate_soft(
"2.2.0",
"eigen_centrality(directed)",
details = "Use the mode argument."
)
if (!lifecycle::is_present(mode)) {
mode <- "out"
}
} else {
lifecycle::deprecate_soft(
"2.2.0",
"eigen_centrality(directed)",
details = "Use the mode argument."
)
if (!lifecycle::is_present(mode)) {
mode <- "all"
}
}
}

centralization_eigenvector_centrality_impl(
graph = graph,
directed = directed,
options = options,
normalized = normalized,
scale = TRUE
mode = mode
)
}

Expand All @@ -693,7 +730,9 @@ centr_eigen <- function(
#' @param directed logical scalar, whether to consider edge directions
#' during the calculation. Ignored in undirected graphs.
#' @param scale `r lifecycle::badge("deprecated")` Ignored. Computing
#' eigenvector centralization requires normalized eigenvector centrality scores.
#' eigenvector centralization requires normalized eigenvector centrality scores.
#' @param mode This is the same as the `mode` argument of
#' `degree()`.
#' @return Real scalar, the theoretical maximum (unnormalized) graph
#' eigenvector centrality score for graphs with given vertex count and
#' other parameters.
Expand All @@ -712,8 +751,9 @@ centr_eigen <- function(
centr_eigen_tmax <- function(
graph = NULL,
nodes = 0,
directed = FALSE,
scale = deprecated()
directed = deprecated(),
scale = deprecated(),
mode = c("out", "in", "all")
) {
if (lifecycle::is_present(scale)) {
lifecycle::deprecate_soft(
Expand All @@ -723,11 +763,33 @@ centr_eigen_tmax <- function(
The argument will be removed in the future."
)
}
mode <- igraph.match.arg(mode)

if (lifecycle::is_present(directed)) {
if (directed) {
lifecycle::deprecate_soft(
"2.2.0",
"eigen_centrality(directed)",
details = "Use the mode argument."
)
if (!lifecycle::is_present(mode)) {
mode <- "out"
}
} else {
lifecycle::deprecate_soft(
"2.2.0",
"eigen_centrality(directed)",
details = "Use the mode argument."
)
if (!lifecycle::is_present(mode)) {
mode <- "all"
}
}
}

centralization_eigenvector_centrality_tmax_impl(
graph = graph,
nodes = nodes,
directed = directed,
scale = TRUE
mode = mode
)
}
17 changes: 13 additions & 4 deletions R/community.R
Original file line number Diff line number Diff line change
Expand Up @@ -2345,6 +2345,10 @@ cluster_leading_eigen <- function(
#' @param fixed Logical vector denoting which labels are fixed. Of course this
#' makes sense only if you provided an initial state, otherwise this element
#' will be ignored. Also note that vertices without labels cannot be fixed.
#' @param lpa_variant Which variant of the label propagation algorithm to run.
#' - `"dominance"` (default) check for dominance of all nodes after each iteration.
#' - `"retention"` keep current label if among dominant labels, only check if labels changed.
#' - `"fast"` sample from dominant labels, only check neighbors.
#' @return `cluster_label_prop()` returns a
#' [communities()] object, please see the [communities()]
#' manual page for details.
Expand Down Expand Up @@ -2373,7 +2377,8 @@ cluster_label_prop <- function(
...,
mode = c("out", "in", "all"),
initial = NULL,
fixed = NULL
fixed = NULL,
lpa_variant = c("dominance", "retention", "fast")
) {
if (...length() > 0) {
lifecycle::deprecate_soft(
Expand All @@ -2395,30 +2400,34 @@ cluster_label_prop <- function(
return(inject(cluster_label_prop0(!!!dots)))
}

cluster_label_prop0(graph, weights, mode, initial, fixed)
cluster_label_prop0(graph, weights, mode, initial, fixed, lpa_variant)
}

cluster_label_prop0 <- function(
graph,
weights = NULL,
mode = c("out", "in", "all"),
initial = NULL,
fixed = NULL
fixed = NULL,
lpa_variant = c("dominance", "retention", "fast")
) {
# Argument checks
ensure_igraph(graph)

# Necessary because evaluated later
mode <- igraph.match.arg(mode)
lpa_variant <- igraph.match.arg(lpa_variant)

# Function call
membership <- community_label_propagation_impl(
graph,
mode = mode,
weights = weights,
initial = initial,
fixed = fixed
fixed = fixed,
lpa.variant = lpa_variant
)

res <- list()
if (igraph_opt("add.vertex.names") && is_named(graph)) {
res$names <- V(graph)$name
Expand Down
Loading
Loading