From 85faf02a9eac6142e6c876f64cb5361a5289e3c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:05:15 +0000 Subject: [PATCH 1/4] Initial plan From 296e204a0fc44fc8098d6d0422107e866546103c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:22:53 +0000 Subject: [PATCH 2/4] Add `knnk()` function to expose igraph_degree_correlation_vector() Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- NAMESPACE | 1 + R/structural-properties.R | 96 ++++++++++++++ man/bfs.Rd | 1 + man/components.Rd | 1 + man/constraint.Rd | 1 + man/coreness.Rd | 1 + man/degree.Rd | 1 + man/dfs.Rd | 1 + man/distances.Rd | 1 + man/edge_density.Rd | 1 + man/ego.Rd | 1 + man/feedback_arc_set.Rd | 1 + man/feedback_vertex_set.Rd | 1 + man/girth.Rd | 1 + man/is_acyclic.Rd | 1 + man/is_dag.Rd | 1 + man/k_shortest_paths.Rd | 1 + man/knn.Rd | 1 + man/knnk.Rd | 135 ++++++++++++++++++++ man/matching.Rd | 1 + man/reciprocity.Rd | 1 + man/subcomponent.Rd | 1 + man/subgraph.Rd | 1 + man/topo_sort.Rd | 1 + man/transitivity.Rd | 1 + man/unfold_tree.Rd | 1 + man/which_multiple.Rd | 1 + man/which_mutual.Rd | 1 + tests/testthat/test-structural-properties.R | 65 ++++++++++ tests/testthat/testthat-problems.rds | Bin 46380 -> 0 bytes 30 files changed, 322 insertions(+) create mode 100644 man/knnk.Rd delete mode 100644 tests/testthat/testthat-problems.rds diff --git a/NAMESPACE b/NAMESPACE index 95be07fb203..3df25d0b02e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -584,6 +584,7 @@ export(k_shortest_paths) export(kautz_graph) export(keeping_degseq) export(knn) +export(knnk) export(label.propagation.community) export(laplacian_matrix) export(largest.cliques) diff --git a/R/structural-properties.R b/R/structural-properties.R index b99ba9e37f5..5bf25fc458f 100644 --- a/R/structural-properties.R +++ b/R/structural-properties.R @@ -3569,3 +3569,99 @@ knn <- function( weights = weights ) } + +#' Degree correlation function +#' +#' Computes the k_nn(k) degree correlation function, which gives the mean degree +#' of neighbors of vertices with degree k. +#' +#' The k_nn(k) function characterizes degree correlations in networks. +#' It provides the average degree of neighbors as a function of vertex degree. +#' This is one of the primary ways to measure degree assortativity in networks. +#' +#' For directed graphs, this function provides fine-grained control over how +#' in/out degrees are used through the `from.mode` and `to.mode` parameters. +#' +#' Note that for degrees that do not appear in the network, the result is `NaN` +#' (zero divided by zero). +#' +#' The weighted version computes a weighted average as: +#' +#' \deqn{k_{nn}(k) = \frac{\sum_{i: k_i=k} \sum_j w_{ij} k_j}{\sum_{i: k_i=k} \sum_j w_{ij}}}{k_nn(k) = sum_(i: k_i=k) sum_j w_ij k_j / sum_(i: k_i=k) sum_j w_ij} +#' +#' where the first sum runs over vertices of degree k, the second sum runs +#' over their neighbors j, w_ij is the edge weight, and k_j is the neighbor's degree. +#' +#' @param graph The input graph. It may be directed. +#' @param weights Optional edge weights. If the graph has a `weight` edge +#' attribute, then this is used by default. If this argument is given, +#' then edge weights are used in the calculation. Set to `NA` to ignore +#' the `weight` edge attribute even if present. +#' @param from.mode How to compute the degree of source vertices in directed graphs? +#' `out` uses out-degree, `in` uses in-degree, and `all` or `total` uses +#' total degree. Ignored for undirected graphs. +#' @param to.mode How to compute the degree of target vertices (neighbors) in +#' directed graphs? `out` uses out-degree, `in` uses in-degree, and `all` +#' or `total` uses total degree. Ignored for undirected graphs. +#' @param directed.neighbors Logical scalar. Whether to consider edges as directed +#' when computing neighbor relationships in directed graphs. If `FALSE`, +#' edges are treated as undirected (i.e., reciprocal). Ignored for undirected graphs. +#' @return A numeric vector. Element i contains the mean degree of neighbors +#' of vertices with degree i-1. Note that degree 0 is included at index 1. +#' The length of the vector is one more than the maximum degree in the graph. +#' @author Gabor Csardi \email{csardi.gabor@@gmail.com} +#' @references +#' R. Pastor-Satorras, A. Vazquez, A. Vespignani: +#' Dynamical and Correlation Properties of the Internet, +#' Phys. Rev. Lett., vol. 87, pp. 258701 (2001). +#' \doi{10.1103/PhysRevLett.87.258701} +#' +#' A. Vazquez, R. Pastor-Satorras, A. Vespignani: +#' Large-scale topological and dynamical properties of the Internet, +#' Phys. Rev. E, vol. 65, pp. 066130 (2002). +#' \doi{10.1103/PhysRevE.65.066130} +#' +#' A. Barrat, M. Barthelemy, R. Pastor-Satorras, and A. Vespignani: +#' The architecture of complex weighted networks, +#' Proc. Natl. Acad. Sci. USA 101, 3747 (2004). +#' \doi{10.1073/pnas.0400087101} +#' +#' A.-L. Barabási, Network Science (2016). Chapter 7, Degree Correlations. +#' \url{https://networksciencebook.com/chapter/7#measuring-degree} +#' @seealso [knn()] for computing average nearest neighbor degree for specific vertices +#' @keywords graphs +#' @examples +#' # Ring graph - all vertices have degree 2, so k_nn(2) = 2 +#' g <- make_ring(10) +#' knnk(g) +#' +#' # Star graph +#' g2 <- make_star(10) +#' knnk(g2) +#' +#' # Scale-free graph - typically shows degree anti-correlation +#' g3 <- sample_pa(1000, m = 5) +#' result <- knnk(g3) +#' plot(result, xlab = "k", ylab = "k_nn(k)", type = "l") +#' +#' # Directed graph with different degree modes +#' g4 <- sample_pa(100, directed = TRUE) +#' knnk(g4, from.mode = "out", to.mode = "in") +#' @family structural.properties +#' @export +#' @cdocs igraph_degree_correlation_vector +knnk <- function( + graph, + weights = NULL, + from.mode = c("out", "in", "all", "total"), + to.mode = c("in", "out", "all", "total"), + directed.neighbors = TRUE +) { + degree_correlation_vector_impl( + graph = graph, + weights = weights, + from.mode = from.mode, + to.mode = to.mode, + directed.neighbors = directed.neighbors + ) +} diff --git a/man/bfs.Rd b/man/bfs.Rd index d72f3f76036..139f3816dbc 100644 --- a/man/bfs.Rd +++ b/man/bfs.Rd @@ -185,6 +185,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/components.Rd b/man/components.Rd index ac7bffb001b..de0dd254f35 100644 --- a/man/components.Rd +++ b/man/components.Rd @@ -117,6 +117,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/constraint.Rd b/man/constraint.Rd index ae337644aa5..d1fa8782b47 100644 --- a/man/constraint.Rd +++ b/man/constraint.Rd @@ -69,6 +69,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/coreness.Rd b/man/coreness.Rd index c77603f3369..65cfde94bc4 100644 --- a/man/coreness.Rd +++ b/man/coreness.Rd @@ -64,6 +64,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/degree.Rd b/man/degree.Rd index 098cf73fea2..1b559f0d12e 100644 --- a/man/degree.Rd +++ b/man/degree.Rd @@ -87,6 +87,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/dfs.Rd b/man/dfs.Rd index 643c7031f03..010190b0719 100644 --- a/man/dfs.Rd +++ b/man/dfs.Rd @@ -176,6 +176,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/distances.Rd b/man/distances.Rd index 96ffa1c2860..f6aa4e3345d 100644 --- a/man/distances.Rd +++ b/man/distances.Rd @@ -306,6 +306,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/edge_density.Rd b/man/edge_density.Rd index 99b4db0eb71..86afee30e47 100644 --- a/man/edge_density.Rd +++ b/man/edge_density.Rd @@ -66,6 +66,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/ego.Rd b/man/ego.Rd index 69c018240fe..381949204a4 100644 --- a/man/ego.Rd +++ b/man/ego.Rd @@ -192,6 +192,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/feedback_arc_set.Rd b/man/feedback_arc_set.Rd index 59a41641f6a..0e87998831e 100644 --- a/man/feedback_arc_set.Rd +++ b/man/feedback_arc_set.Rd @@ -67,6 +67,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/feedback_vertex_set.Rd b/man/feedback_vertex_set.Rd index 06f635f7ee5..3571c0836fc 100644 --- a/man/feedback_vertex_set.Rd +++ b/man/feedback_vertex_set.Rd @@ -53,6 +53,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/girth.Rd b/man/girth.Rd index c95e85f018b..7df5ad73ae9 100644 --- a/man/girth.Rd +++ b/man/girth.Rd @@ -74,6 +74,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/is_acyclic.Rd b/man/is_acyclic.Rd index 21a8b7dc86d..47978332496 100644 --- a/man/is_acyclic.Rd +++ b/man/is_acyclic.Rd @@ -55,6 +55,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/is_dag.Rd b/man/is_dag.Rd index 0c651de7b3e..5bccf741832 100644 --- a/man/is_dag.Rd +++ b/man/is_dag.Rd @@ -55,6 +55,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/k_shortest_paths.Rd b/man/k_shortest_paths.Rd index c136dc00977..c8a5c478372 100644 --- a/man/k_shortest_paths.Rd +++ b/man/k_shortest_paths.Rd @@ -82,6 +82,7 @@ Other structural.properties: \code{\link{is_dag}()}, \code{\link{is_matching}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/knn.Rd b/man/knn.Rd index 13c35422f0c..ed2449f8a30 100644 --- a/man/knn.Rd +++ b/man/knn.Rd @@ -111,6 +111,7 @@ Other structural.properties: \code{\link{is_dag}()}, \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/knnk.Rd b/man/knnk.Rd new file mode 100644 index 00000000000..5e69bdc39ed --- /dev/null +++ b/man/knnk.Rd @@ -0,0 +1,135 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/structural-properties.R +\name{knnk} +\alias{knnk} +\title{Degree correlation function} +\usage{ +knnk( + graph, + weights = NULL, + from.mode = c("out", "in", "all", "total"), + to.mode = c("in", "out", "all", "total"), + directed.neighbors = TRUE +) +} +\arguments{ +\item{graph}{The input graph. It may be directed.} + +\item{weights}{Optional edge weights. If the graph has a \code{weight} edge +attribute, then this is used by default. If this argument is given, +then edge weights are used in the calculation. Set to \code{NA} to ignore +the \code{weight} edge attribute even if present.} + +\item{from.mode}{How to compute the degree of source vertices in directed graphs? +\code{out} uses out-degree, \verb{in} uses in-degree, and \code{all} or \code{total} uses +total degree. Ignored for undirected graphs.} + +\item{to.mode}{How to compute the degree of target vertices (neighbors) in +directed graphs? \code{out} uses out-degree, \verb{in} uses in-degree, and \code{all} +or \code{total} uses total degree. Ignored for undirected graphs.} + +\item{directed.neighbors}{Logical scalar. Whether to consider edges as directed +when computing neighbor relationships in directed graphs. If \code{FALSE}, +edges are treated as undirected (i.e., reciprocal). Ignored for undirected graphs.} +} +\value{ +A numeric vector. Element i contains the mean degree of neighbors +of vertices with degree i-1. Note that degree 0 is included at index 1. +The length of the vector is one more than the maximum degree in the graph. +} +\description{ +Computes the k_nn(k) degree correlation function, which gives the mean degree +of neighbors of vertices with degree k. +} +\details{ +The k_nn(k) function characterizes degree correlations in networks. +It provides the average degree of neighbors as a function of vertex degree. +This is one of the primary ways to measure degree assortativity in networks. + +For directed graphs, this function provides fine-grained control over how +in/out degrees are used through the \code{from.mode} and \code{to.mode} parameters. + +Note that for degrees that do not appear in the network, the result is \code{NaN} +(zero divided by zero). + +The weighted version computes a weighted average as: + +\deqn{k_{nn}(k) = \frac{\sum_{i: k_i=k} \sum_j w_{ij} k_j}{\sum_{i: k_i=k} \sum_j w_{ij}}}{k_nn(k) = sum_(i: k_i=k) sum_j w_ij k_j / sum_(i: k_i=k) sum_j w_ij} + +where the first sum runs over vertices of degree k, the second sum runs +over their neighbors j, w_ij is the edge weight, and k_j is the neighbor's degree. +} +\examples{ +# Ring graph - all vertices have degree 2, so k_nn(2) = 2 +g <- make_ring(10) +knnk(g) + +# Star graph +g2 <- make_star(10) +knnk(g2) + +# Scale-free graph - typically shows degree anti-correlation +g3 <- sample_pa(1000, m = 5) +result <- knnk(g3) +plot(result, xlab = "k", ylab = "k_nn(k)", type = "l") + +# Directed graph with different degree modes +g4 <- sample_pa(100, directed = TRUE) +knnk(g4, from.mode = "out", to.mode = "in") +} +\references{ +R. Pastor-Satorras, A. Vazquez, A. Vespignani: +Dynamical and Correlation Properties of the Internet, +Phys. Rev. Lett., vol. 87, pp. 258701 (2001). +\doi{10.1103/PhysRevLett.87.258701} + +A. Vazquez, R. Pastor-Satorras, A. Vespignani: +Large-scale topological and dynamical properties of the Internet, +Phys. Rev. E, vol. 65, pp. 066130 (2002). +\doi{10.1103/PhysRevE.65.066130} + +A. Barrat, M. Barthelemy, R. Pastor-Satorras, and A. Vespignani: +The architecture of complex weighted networks, +Proc. Natl. Acad. Sci. USA 101, 3747 (2004). +\doi{10.1073/pnas.0400087101} + +A.-L. Barabási, Network Science (2016). Chapter 7, Degree Correlations. +\url{https://networksciencebook.com/chapter/7#measuring-degree} +} +\seealso{ +\code{\link[=knn]{knn()}} for computing average nearest neighbor degree for specific vertices + +Other structural.properties: +\code{\link{bfs}()}, +\code{\link{component_distribution}()}, +\code{\link{connect}()}, +\code{\link{constraint}()}, +\code{\link{coreness}()}, +\code{\link{degree}()}, +\code{\link{dfs}()}, +\code{\link{distance_table}()}, +\code{\link{edge_density}()}, +\code{\link{feedback_arc_set}()}, +\code{\link{feedback_vertex_set}()}, +\code{\link{girth}()}, +\code{\link{is_acyclic}()}, +\code{\link{is_dag}()}, +\code{\link{is_matching}()}, +\code{\link{k_shortest_paths}()}, +\code{\link{knn}()}, +\code{\link{reciprocity}()}, +\code{\link{subcomponent}()}, +\code{\link{subgraph}()}, +\code{\link{topo_sort}()}, +\code{\link{transitivity}()}, +\code{\link{unfold_tree}()}, +\code{\link{which_multiple}()}, +\code{\link{which_mutual}()} +} +\author{ +Gabor Csardi \email{csardi.gabor@gmail.com} +} +\concept{structural.properties} +\keyword{graphs} +\section{Related documentation in the C library}{\href{https://igraph.org/c/html/latest/igraph-Structural.html#igraph_degree_correlation_vector}{\code{degree_correlation_vector()}}.} + diff --git a/man/matching.Rd b/man/matching.Rd index aef4e2ac57a..c22cab1bfb8 100644 --- a/man/matching.Rd +++ b/man/matching.Rd @@ -134,6 +134,7 @@ Other structural.properties: \code{\link{is_dag}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/reciprocity.Rd b/man/reciprocity.Rd index 72b71313216..e8db6a56191 100644 --- a/man/reciprocity.Rd +++ b/man/reciprocity.Rd @@ -60,6 +60,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, \code{\link{topo_sort}()}, diff --git a/man/subcomponent.Rd b/man/subcomponent.Rd index 128a85e74b6..6a67a11d6f7 100644 --- a/man/subcomponent.Rd +++ b/man/subcomponent.Rd @@ -56,6 +56,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subgraph}()}, \code{\link{topo_sort}()}, diff --git a/man/subgraph.Rd b/man/subgraph.Rd index 36705561d56..335207c708c 100644 --- a/man/subgraph.Rd +++ b/man/subgraph.Rd @@ -83,6 +83,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{topo_sort}()}, diff --git a/man/topo_sort.Rd b/man/topo_sort.Rd index 4d6cfb66c5a..296f76cbe52 100644 --- a/man/topo_sort.Rd +++ b/man/topo_sort.Rd @@ -55,6 +55,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/transitivity.Rd b/man/transitivity.Rd index 2c6800eda4b..4257fe53e9f 100644 --- a/man/transitivity.Rd +++ b/man/transitivity.Rd @@ -152,6 +152,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/unfold_tree.Rd b/man/unfold_tree.Rd index 88fef56131b..80c25790521 100644 --- a/man/unfold_tree.Rd +++ b/man/unfold_tree.Rd @@ -67,6 +67,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/which_multiple.Rd b/man/which_multiple.Rd index c7ae8a79065..c98aed93477 100644 --- a/man/which_multiple.Rd +++ b/man/which_multiple.Rd @@ -103,6 +103,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/man/which_mutual.Rd b/man/which_mutual.Rd index 9acbed38d8d..ce3bedaa6f9 100644 --- a/man/which_mutual.Rd +++ b/man/which_mutual.Rd @@ -60,6 +60,7 @@ Other structural.properties: \code{\link{is_matching}()}, \code{\link{k_shortest_paths}()}, \code{\link{knn}()}, +\code{\link{knnk}()}, \code{\link{reciprocity}()}, \code{\link{subcomponent}()}, \code{\link{subgraph}()}, diff --git a/tests/testthat/test-structural-properties.R b/tests/testthat/test-structural-properties.R index f938d495dd0..21c7af98c6f 100644 --- a/tests/testthat/test-structural-properties.R +++ b/tests/testthat/test-structural-properties.R @@ -949,6 +949,71 @@ test_that("knn works", { ) }) +test_that("knnk works", { + withr::local_seed(42) + + ## Ring graph - all vertices have degree 2 + g <- make_ring(10) + result <- knnk(g) + # Index 1 is degree 0 (NaN), index 2 is degree 1 (NaN), index 3 is degree 2 (value 2) + expect_equal(result, c(NaN, NaN, 2)) + + # Compare with knn result (knn's knnk starts at degree 1) + knn_result <- knn(g) + expect_equal(result[2:3], knn_result$knnk) + + ## Star graph + g2 <- make_star(10) + result2 <- knnk(g2) + # Degree 0: NaN, Degree 1: avg neighbor degree is 9 + expect_equal(result2, c(NaN, 9)) + + # Compare with knn + knn_result2 <- knn(g2) + expect_equal(result2[2], knn_result2$knnk[1]) + + ## Directed graph with different modes + g3 <- make_graph(c(1, 2, 1, 3, 2, 3, 3, 4), directed = TRUE) + # Vertex 1 has out-degree 2, points to vertices 2 and 3 with in-degrees 1 and 2 + # Vertex 2 has out-degree 1, points to vertex 3 with in-degree 2 + # Vertex 3 has out-degree 1, points to vertex 4 with in-degree 1 + # Vertex 4 has out-degree 0 + + # from.mode = "out", to.mode = "in" + result3 <- knnk(g3, from.mode = "out", to.mode = "in") + expect_equal(result3[1], NaN) # degree 0 + expect_equal(result3[2], 1.5) # degree 1: vertices 2 and 3 point to vertices with in-degree 2 and 1, avg = 1.5 + expect_equal(result3[3], 1.5) # degree 2: vertex 1 points to vertices with in-degree 1 and 2, avg = 1.5 + + ## Weighted graph - star with 5 vertices + g4 <- make_star(5) + E(g4)$weight <- c(1, 2, 3, 4) + result4 <- knnk(g4) + # Only degree 1 and degree 4 exist in this graph + expect_equal(result4, c(NaN, 4)) + + ## Test directed.neighbors parameter + g5 <- make_graph(c(1, 2, 1, 3), directed = TRUE) + # With directed.neighbors = TRUE (default): edges are directed + result5a <- knnk( + g5, + from.mode = "out", + to.mode = "in", + directed.neighbors = TRUE + ) + # With directed.neighbors = FALSE: edges are treated as reciprocal + result5b <- knnk( + g5, + from.mode = "out", + to.mode = "in", + directed.neighbors = FALSE + ) + # Results should differ when directed.neighbors changes + expect_false(identical(result5a, result5b)) + expect_equal(result5a, c(NaN, NaN, 1)) # With directed: only out-degree 2 vertex exists + expect_equal(result5b, c(NaN, 2, 1)) # With reciprocal: out-degree 0 vertices now also have out-degree 1 +}) + test_that("reciprocity works", { g <- make_graph(c(1, 2, 2, 1, 2, 3, 3, 4, 4, 4), directed = TRUE) expect_equal(reciprocity(g), 0.5) diff --git a/tests/testthat/testthat-problems.rds b/tests/testthat/testthat-problems.rds deleted file mode 100644 index 95cf1759297186292377247df7cb3d1a15c7620e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46380 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 Date: Sun, 26 Oct 2025 14:27:32 +0000 Subject: [PATCH 3/4] Add parameter validation to `knnk()` function Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- R/structural-properties.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/structural-properties.R b/R/structural-properties.R index 5bf25fc458f..aab3165723b 100644 --- a/R/structural-properties.R +++ b/R/structural-properties.R @@ -3657,6 +3657,8 @@ knnk <- function( to.mode = c("in", "out", "all", "total"), directed.neighbors = TRUE ) { + from.mode <- match.arg(from.mode) + to.mode <- match.arg(to.mode) degree_correlation_vector_impl( graph = graph, weights = weights, From 0d3b77a076128d5eb59a07083f1859d8f392268d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 17:30:25 +0000 Subject: [PATCH 4/4] Changes before error encountered Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- R/structural-properties.R | 21 ++++++++++++--------- man/knnk.Rd | 21 ++++++++++++--------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/R/structural-properties.R b/R/structural-properties.R index 569906fbd5a..b89a116afc2 100644 --- a/R/structural-properties.R +++ b/R/structural-properties.R @@ -3608,10 +3608,12 @@ knn <- function( #' Degree correlation function #' -#' Computes the k_nn(k) degree correlation function, which gives the mean degree -#' of neighbors of vertices with degree k. +#' `r lifecycle::badge("experimental")` +#' +#' Computes the \eqn{k_{nn}(k)} degree correlation function, which gives the mean degree +#' of neighbors of vertices with degree \eqn{k}. #' -#' The k_nn(k) function characterizes degree correlations in networks. +#' The \eqn{k_{nn}(k)} function characterizes degree correlations in networks. #' It provides the average degree of neighbors as a function of vertex degree. #' This is one of the primary ways to measure degree assortativity in networks. #' @@ -3625,8 +3627,8 @@ knn <- function( #' #' \deqn{k_{nn}(k) = \frac{\sum_{i: k_i=k} \sum_j w_{ij} k_j}{\sum_{i: k_i=k} \sum_j w_{ij}}}{k_nn(k) = sum_(i: k_i=k) sum_j w_ij k_j / sum_(i: k_i=k) sum_j w_ij} #' -#' where the first sum runs over vertices of degree k, the second sum runs -#' over their neighbors j, w_ij is the edge weight, and k_j is the neighbor's degree. +#' where the first sum runs over vertices of degree \eqn{k}, the second sum runs +#' over their neighbors \eqn{j}, \eqn{w_{ij}} is the edge weight, and \eqn{k_j} is the neighbor's degree. #' #' @param graph The input graph. It may be directed. #' @param weights Optional edge weights. If the graph has a `weight` edge @@ -3642,8 +3644,9 @@ knn <- function( #' @param directed.neighbors Logical scalar. Whether to consider edges as directed #' when computing neighbor relationships in directed graphs. If `FALSE`, #' edges are treated as undirected (i.e., reciprocal). Ignored for undirected graphs. -#' @return A numeric vector. Element i contains the mean degree of neighbors -#' of vertices with degree i-1. Note that degree 0 is included at index 1. +#' @return A numeric vector. +#' Element \eqn{i} contains the mean degree of neighbors of vertices with degree \eqn{i-1}. +#' Note that degree 0 is included at index 1. #' The length of the vector is one more than the maximum degree in the graph. #' @author Gabor Csardi \email{csardi.gabor@@gmail.com} #' @references @@ -3667,7 +3670,7 @@ knn <- function( #' @seealso [knn()] for computing average nearest neighbor degree for specific vertices #' @keywords graphs #' @examples -#' # Ring graph - all vertices have degree 2, so k_nn(2) = 2 +#' # Ring graph - all vertices have degree 2 #' g <- make_ring(10) #' knnk(g) #' @@ -3678,7 +3681,7 @@ knn <- function( #' # Scale-free graph - typically shows degree anti-correlation #' g3 <- sample_pa(1000, m = 5) #' result <- knnk(g3) -#' plot(result, xlab = "k", ylab = "k_nn(k)", type = "l") +#' plot(result, xlab = "k", ylab = expression(k[nn](k)), type = "l") #' #' # Directed graph with different degree modes #' g4 <- sample_pa(100, directed = TRUE) diff --git a/man/knnk.Rd b/man/knnk.Rd index 5e69bdc39ed..7d24e05dac7 100644 --- a/man/knnk.Rd +++ b/man/knnk.Rd @@ -33,16 +33,19 @@ when computing neighbor relationships in directed graphs. If \code{FALSE}, edges are treated as undirected (i.e., reciprocal). Ignored for undirected graphs.} } \value{ -A numeric vector. Element i contains the mean degree of neighbors -of vertices with degree i-1. Note that degree 0 is included at index 1. +A numeric vector. +Element \eqn{i} contains the mean degree of neighbors of vertices with degree \eqn{i-1}. +Note that degree 0 is included at index 1. The length of the vector is one more than the maximum degree in the graph. } \description{ -Computes the k_nn(k) degree correlation function, which gives the mean degree -of neighbors of vertices with degree k. +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} } \details{ -The k_nn(k) function characterizes degree correlations in networks. +Computes the \eqn{k_{nn}(k)} degree correlation function, which gives the mean degree +of neighbors of vertices with degree \eqn{k}. + +The \eqn{k_{nn}(k)} function characterizes degree correlations in networks. It provides the average degree of neighbors as a function of vertex degree. This is one of the primary ways to measure degree assortativity in networks. @@ -56,11 +59,11 @@ The weighted version computes a weighted average as: \deqn{k_{nn}(k) = \frac{\sum_{i: k_i=k} \sum_j w_{ij} k_j}{\sum_{i: k_i=k} \sum_j w_{ij}}}{k_nn(k) = sum_(i: k_i=k) sum_j w_ij k_j / sum_(i: k_i=k) sum_j w_ij} -where the first sum runs over vertices of degree k, the second sum runs -over their neighbors j, w_ij is the edge weight, and k_j is the neighbor's degree. +where the first sum runs over vertices of degree \eqn{k}, the second sum runs +over their neighbors \eqn{j}, \eqn{w_{ij}} is the edge weight, and \eqn{k_j} is the neighbor's degree. } \examples{ -# Ring graph - all vertices have degree 2, so k_nn(2) = 2 +# Ring graph - all vertices have degree 2 g <- make_ring(10) knnk(g) @@ -71,7 +74,7 @@ knnk(g2) # Scale-free graph - typically shows degree anti-correlation g3 <- sample_pa(1000, m = 5) result <- knnk(g3) -plot(result, xlab = "k", ylab = "k_nn(k)", type = "l") +plot(result, xlab = "k", ylab = expression(k[nn](k)), type = "l") # Directed graph with different degree modes g4 <- sample_pa(100, directed = TRUE)