Skip to content

Commit

Permalink
Add clustering coefficient definitions (#316)
Browse files Browse the repository at this point in the history
* moved modules

* add clustering code

* added clustering functions

* add tests

* fixed tests

* Update xgi/algorithms/clustering.py

Co-authored-by: Maxime Lucas <[email protected]>

* updated centrality names

* added more info on clustering

* updated all names

* fixed name

* update docstrings

* added tests

* Update clustering.py

* changed NaN to nan

https://stackoverflow.com/questions/53436339/difference-between-np-nan-and-np-nan

* response to review

* change to isnan

* fixed more tests

---------

Co-authored-by: Maxime Lucas <[email protected]>
Co-authored-by: Leo Torres <[email protected]>
  • Loading branch information
3 people authored Apr 5, 2023
1 parent ca4fb51 commit 7ee8aa2
Show file tree
Hide file tree
Showing 19 changed files with 671 additions and 114 deletions.
1 change: 1 addition & 0 deletions docs/source/api/algorithms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ algorithms package

~xgi.algorithms.assortativity
~xgi.algorithms.centrality
~xgi.algorithms.clustering
~xgi.algorithms.connected
4 changes: 2 additions & 2 deletions docs/source/api/algorithms/xgi.algorithms.centrality.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ xgi.algorithms.centrality

.. rubric:: Functions

.. autofunction:: CEC_centrality
.. autofunction:: HEC_centrality
.. autofunction:: clique_eigenvector_centrality
.. autofunction:: h_eigenvector_centrality
.. autofunction:: node_edge_centrality
.. autofunction:: line_vector_centrality
12 changes: 12 additions & 0 deletions docs/source/api/algorithms/xgi.algorithms.clustering.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
xgi.algorithms.clustering
=========================

.. currentmodule:: xgi.algorithms.clustering

.. automodule:: xgi.algorithms.clustering

.. rubric:: Functions

.. autofunction:: clustering_coefficient
.. autofunction:: local_clustering_coefficient
.. autofunction:: two_node_clustering_coefficient
2 changes: 1 addition & 1 deletion docs/source/api/linalg.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ linalg package
.. rubric:: Modules

.. autosummary::
:toctree: classes
:toctree: linalg

~xgi.linalg.matrix
10 changes: 6 additions & 4 deletions docs/source/api/stats/xgi.stats.nodestats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

.. autofunction:: attrs
.. autofunction:: average_neighbor_degree
.. autofunction:: cec_centrality
.. autofunction:: clustering
.. autofunction:: clique_eigenvector_centrality
.. autofunction:: clustering_coefficient
.. autofunction:: degree
.. autofunction:: hec_centrality
.. autofunction:: node_edge_centrality
.. autofunction:: h_eigenvector_centrality
.. autofunction:: local_clustering_coefficient
.. autofunction:: node_edge_centrality
.. autofunction:: two_node_clustering_coefficient
2 changes: 1 addition & 1 deletion docs/source/api/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ utils package
.. rubric:: Modules

.. autosummary::
:toctree: classes
:toctree: utils

~xgi.utils.utilities
62 changes: 42 additions & 20 deletions tests/algorithms/test_centrality.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,71 +6,93 @@
from xgi.exception import XGIError


def test_cec_centrality():
def test_clique_eigenvector_centrality():
# test empty hypergraph
H = xgi.Hypergraph()
assert xgi.CEC_centrality(H) == dict()
assert xgi.clique_eigenvector_centrality(H) == dict()

# Test no edges
H.add_nodes_from([0, 1, 2])
assert xgi.CEC_centrality(H) == {0: np.NaN, 1: np.NaN, 2: np.NaN}
cec = xgi.clique_eigenvector_centrality(H)
assert set(cec) == {0, 1, 2}
for i in cec:
assert np.isnan(cec[i])

# test disconnected
H.add_edge([0, 1])
assert xgi.CEC_centrality(H) == {0: np.NaN, 1: np.NaN, 2: np.NaN}
cec = xgi.clique_eigenvector_centrality(H)
assert set(cec) == {0, 1, 2}
for i in cec:
assert np.isnan(cec[i])

H = xgi.sunflower(3, 1, 3)
c = H.nodes.cec_centrality.asnumpy()
c = H.nodes.clique_eigenvector_centrality.asnumpy()
assert norm(c[1:] - c[1]) < 1e-4
assert abs(c[0] / c[1] - ratio(3, 3, kind="CEC")) < 1e-4

H = xgi.sunflower(5, 1, 7)
c = H.nodes.cec_centrality.asnumpy()
c = H.nodes.clique_eigenvector_centrality.asnumpy()
assert norm(c[1:] - c[1]) < 1e-4
assert abs(c[0] / c[1] - ratio(5, 7, kind="CEC")) < 1e-4


@pytest.mark.slow
def test_hec_centrality():
def test_h_eigenvector_centrality():
# test empty hypergraph
H = xgi.Hypergraph()
c = xgi.HEC_centrality(H)
c = xgi.h_eigenvector_centrality(H)
assert c == dict()

# Test no edges
H.add_nodes_from([0, 1, 2])
c = xgi.HEC_centrality(H)
assert c == {0: np.NaN, 1: np.NaN, 2: np.NaN}
hec = xgi.h_eigenvector_centrality(H)
for i in hec:
assert np.isnan(hec[i])

# test disconnected
H.add_edge([0, 1])
c = xgi.HEC_centrality(H)
assert c == {0: np.NaN, 1: np.NaN, 2: np.NaN}
hec = xgi.h_eigenvector_centrality(H)
assert set(hec) == {0, 1, 2}
for i in hec:
assert np.isnan(hec[i])

H = xgi.sunflower(3, 1, 5)
c = H.nodes.hec_centrality(max_iter=1000).asnumpy()
c = H.nodes.h_eigenvector_centrality(max_iter=1000).asnumpy()
assert norm(c[1:] - c[1]) < 1e-4
assert abs(c[0] / c[1] - ratio(3, 5, kind="HEC")) < 1e-4

H = xgi.sunflower(5, 1, 7)
c = H.nodes.hec_centrality(max_iter=1000).asnumpy()
c = H.nodes.h_eigenvector_centrality(max_iter=1000).asnumpy()
assert norm(c[1:] - c[1]) < 1e-4
assert abs(c[0] / c[1] - ratio(5, 7, kind="HEC")) < 1e-4

with pytest.raises(XGIError):
H = xgi.Hypergraph([[1, 2], [2, 3, 4]])
H.nodes.hec_centrality.asnumpy()
H.nodes.h_eigenvector_centrality.asnumpy()


def test_node_edge_centrality():
# test empty hypergraph
H = xgi.Hypergraph()
assert xgi.node_edge_centrality(H) == (dict(), dict())

# Test no edges
H.add_nodes_from([0, 1, 2])
assert xgi.node_edge_centrality(H) == ({0: np.NaN, 1: np.NaN, 2: np.NaN}, dict())
nc, ec = xgi.node_edge_centrality(H)
assert set(nc) == {0, 1, 2}
for i in nc:
assert np.isnan(nc[i])
assert ec == dict()

# test disconnected
H.add_edge([0, 1])
assert xgi.node_edge_centrality(H) == (
{0: np.NaN, 1: np.NaN, 2: np.NaN},
{0: np.NaN},
)
nc, ec = xgi.node_edge_centrality(H)
assert set(nc) == {0, 1, 2}
for i in nc:
assert np.isnan(nc[i])
assert set(ec) == {0}
for i in ec:
assert np.isnan(ec[i])

H = xgi.Hypergraph([[0, 1, 2, 3, 4]])
c = H.nodes.node_edge_centrality.asnumpy()
Expand Down
125 changes: 125 additions & 0 deletions tests/algorithms/test_clustering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import numpy as np
import pytest

import xgi
from xgi.exception import XGIError


def test_local_clustering_coefficient(edgelist8):
H = xgi.random_hypergraph(3, [1])

cc = xgi.local_clustering_coefficient(H)
true_cc = {0: 1.0, 1: 1.0, 2: 1.0}
assert cc == true_cc

H = xgi.random_hypergraph(3, [1, 1])
cc = xgi.local_clustering_coefficient(H)
true_cc = {0: 1.0, 1: 1.0, 2: 1.0}
assert cc == true_cc

H = xgi.random_hypergraph(3, [0, 1])
cc = xgi.local_clustering_coefficient(H)
true_cc = {0: 0.0, 1: 0.0, 2: 0.0}
assert cc == true_cc

H = xgi.Hypergraph()
cc = xgi.local_clustering_coefficient(H)
assert cc == {}

H = xgi.Hypergraph()
H.add_nodes_from(range(3))
cc = xgi.local_clustering_coefficient(H)
assert cc == {0: 0, 1: 0, 2: 0}

H = xgi.Hypergraph(edgelist8)
cc = xgi.local_clustering_coefficient(H)
true_cc = {
0: 0.6777777777777778,
1: 0.575,
2: 0.3333333333333333,
3: 0.3333333333333333,
4: 0.6666666666666666,
5: 0.0,
6: 0.0,
}
assert cc == true_cc


def test_clustering_coefficient(edgelist1):
H = xgi.random_hypergraph(3, [1])

cc = xgi.clustering_coefficient(H)
true_cc = {0: 1.0, 1: 1.0, 2: 1.0}
assert cc == true_cc

H = xgi.random_hypergraph(3, [1, 1])
cc = xgi.clustering_coefficient(H)
true_cc = {0: 1.0, 1: 1.0, 2: 1.0}
assert cc == true_cc

H = xgi.random_hypergraph(3, [0, 1])
cc = xgi.clustering_coefficient(H)
true_cc = {0: 1.0, 1: 1.0, 2: 1.0}
assert cc == true_cc

H = xgi.Hypergraph()
cc = xgi.clustering_coefficient(H)
assert cc == {}

H = xgi.Hypergraph()
H.add_nodes_from(range(3))
cc = xgi.clustering_coefficient(H)
assert {0: 0, 1: 0, 2: 0}

H = xgi.Hypergraph(edgelist1)
cc = xgi.clustering_coefficient(H)
true_cc = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0, 5: 0, 6: 1 / 3, 8: 1.0, 7: 1.0}
assert cc == true_cc


def test_two_node_clustering_coefficient(edgelist1, edgelist8):
H = xgi.random_hypergraph(3, [1])

cc = xgi.two_node_clustering_coefficient(H)
true_cc = {0: 1 / 3, 1: 1 / 3, 2: 1 / 3}
assert cc == true_cc

# check default keyword
cc1 = xgi.two_node_clustering_coefficient(H, kind="union")
assert cc == cc1

H = xgi.random_hypergraph(3, [1, 1])
cc = xgi.two_node_clustering_coefficient(H)
true_cc = {0: 0.5, 1: 0.5, 2: 0.5}
assert cc == true_cc

H = xgi.Hypergraph(edgelist1)
cc1 = xgi.two_node_clustering_coefficient(H, kind="union")
cc2 = xgi.two_node_clustering_coefficient(H, kind="min")
cc3 = xgi.two_node_clustering_coefficient(H, kind="max")

true_cc1 = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0, 5: 0.5, 6: 0.5, 8: 0.75, 7: 0.75}
true_cc2 = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0, 5: 1.0, 6: 1.0, 8: 1.0, 7: 1.0}
true_cc3 = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0, 5: 0.5, 6: 0.5, 8: 0.75, 7: 0.75}

assert cc1 == true_cc1
assert cc2 == true_cc2
assert cc3 == true_cc3

with pytest.raises(XGIError):
xgi.two_node_clustering_coefficient(H, kind="test")

H = xgi.Hypergraph(edgelist8)
H.add_node(10)
cc = xgi.two_node_clustering_coefficient(H, kind="min")
true_cc = {
0: 0.6533333333333333,
1: 0.4888888888888888,
2: 0.5833333333333333,
3: 0.5833333333333333,
4: 0.5666666666666667,
5: 0.5,
6: 0.5,
10: 0,
}
assert cc == true_cc
4 changes: 0 additions & 4 deletions tests/drawing/test_draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ def test_color_arg_to_dict(edgelist4):


def test_draw(edgelist8):

H = xgi.Hypergraph(edgelist8)

ax = xgi.draw(H)
Expand All @@ -111,7 +110,6 @@ def test_draw(edgelist8):


def test_draw_nodes(edgelist8):

H = xgi.Hypergraph(edgelist8)

ax = xgi.draw_nodes(H)
Expand All @@ -128,7 +126,6 @@ def test_draw_nodes(edgelist8):


def test_draw_hyperedges(edgelist8):

H = xgi.Hypergraph(edgelist8)

ax = xgi.draw_hyperedges(H)
Expand All @@ -148,7 +145,6 @@ def test_draw_hyperedges(edgelist8):


def test_draw_simplices(edgelist8):

with pytest.raises(XGIError):
H = xgi.Hypergraph(edgelist8)
ax = xgi.draw_simplices(H)
Expand Down
Loading

0 comments on commit 7ee8aa2

Please sign in to comment.