Skip to content

Commit

Permalink
Rename egonet and move the neighbors method to the IDView class (#129)
Browse files Browse the repository at this point in the history
Changed name from `egonet` to `edge_neighborhood` and moved `neighbors()` to the `IDView` class
  • Loading branch information
nwlandry authored Jun 29, 2022
1 parent 9535a8c commit 997f40e
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 67 deletions.
2 changes: 1 addition & 1 deletion docs/source/api/classes/xgi.classes.function.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
.. autofunction:: create_empty_copy
.. autofunction:: degree_counts
.. autofunction:: degree_histogram
.. autofunction:: egonet
.. autofunction:: edge_neighborhood
.. autofunction:: freeze
.. autofunction:: frozen
.. autofunction:: get_edge_attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,4 @@
~Hypergraph.isolates
~Hypergraph.duplicate_edges
~Hypergraph.has_edge
~Hypergraph.neighbors
~Hypergraph.singleton_edges
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ xgi.classes.reportviews.EdgeView
:nosignatures:

~EdgeView.members
~IDView.neighbors
~IDView.filterby
~IDView.filterby_attr
1 change: 1 addition & 0 deletions docs/source/api/classes/xgi.classes.reportviews.IDView.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ xgi.classes.reportviews.IDView
:nosignatures:

~IDView.from_view
~IDView.neighbors
~IDView.filterby
~IDView.filterby_attr
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ xgi.classes.reportviews.NodeView
:nosignatures:

~NodeView.memberships
~IDView.neighbors
~IDView.filterby
~IDView.filterby_attr
10 changes: 5 additions & 5 deletions tests/classes/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ def test_is_uniform(edgelist1, edgelist6, edgelist7):
assert xgi.is_uniform(H3) == False


def test_egonet(edgelist3):
def test_edge_neighborhood(edgelist3):
H = xgi.Hypergraph(edgelist3)
assert H.neighbors(3) == {1, 2, 4}
assert xgi.egonet(H, 3) == [[1, 2], [4]]
assert xgi.egonet(H, 3, include_self=True) == [[1, 2, 3], [3, 4]]
assert H.nodes.neighbors(3) == {1, 2, 4}
assert xgi.edge_neighborhood(H, 3) == [[1, 2], [4]]
assert xgi.edge_neighborhood(H, 3, include_self=True) == [[1, 2, 3], [3, 4]]
with pytest.raises(IDNotFound):
xgi.egonet(H, 7)
xgi.edge_neighborhood(H, 7)


def test_degree_counts(edgelist1, edgelist2, edgelist3):
Expand Down
12 changes: 0 additions & 12 deletions tests/classes/test_hypergraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,6 @@ def test_len(edgelist1, edgelist2):
assert len(H2) == 6


def test_neighbors(edgelist1, edgelist2):
el1 = edgelist1
el2 = edgelist2
H1 = xgi.Hypergraph(el1)
H2 = xgi.Hypergraph(el2)
assert H1.neighbors(1) == {2, 3}
assert H1.neighbors(4) == set()
assert H1.neighbors(6) == {5, 7, 8}
assert H2.neighbors(4) == {3, 5, 6}
assert H2.neighbors(1) == {2}


def test_dual(edgelist1, edgelist2, edgelist4):
el1 = edgelist1
el2 = edgelist2
Expand Down
2 changes: 1 addition & 1 deletion tests/classes/test_iddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_iddict(edgelist1):
def test_neighbors():
H = xgi.Hypergraph()
with pytest.raises(IDNotFound):
H.neighbors(0)
H.nodes.neighbors(0)
with pytest.raises(IDNotFound):
H.remove_node(0)
with pytest.raises(IDNotFound):
Expand Down
17 changes: 17 additions & 0 deletions tests/classes/test_reportviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@
from xgi.exception import IDNotFound, XGIError


def test_neighbors(edgelist1, edgelist2):
el1 = edgelist1
el2 = edgelist2
H1 = xgi.Hypergraph(el1)
H2 = xgi.Hypergraph(el2)
assert H1.nodes.neighbors(1) == {2, 3}
assert H1.nodes.neighbors(4) == set()
assert H1.nodes.neighbors(6) == {5, 7, 8}
assert H2.nodes.neighbors(4) == {3, 5, 6}
assert H2.nodes.neighbors(1) == {2}

assert H1.edges.neighbors(0) == set()
assert H1.edges.neighbors(1) == set()
assert H1.edges.neighbors(2) == {3}
assert H1.edges.neighbors(3) == {2}


def test_edge_order(edgelist3):
H = xgi.Hypergraph(edgelist3)

Expand Down
2 changes: 1 addition & 1 deletion xgi/algorithms/connected.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def _plain_bfs(H, source):
for v in thislevel:
if v not in seen:
seen.add(v)
nextlevel.update(H.neighbors(v))
nextlevel.update(H.nodes.neighbors(v))
return seen


Expand Down
24 changes: 12 additions & 12 deletions xgi/classes/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"max_edge_order",
"is_possible_order",
"is_uniform",
"egonet",
"edge_neighborhood",
"degree_counts",
"degree_histogram",
"unique_edge_sizes",
Expand Down Expand Up @@ -101,27 +101,27 @@ def is_uniform(H):
return edge_sizes.pop() - 1 # order of all edges


def egonet(H, n, include_self=False):
"""The egonet of the specified node.
def edge_neighborhood(H, n, include_self=False):
"""The edge neighborhood of the specified node.
The egonet of a node `n` in a hypergraph `H` is another hypergraph whose nodes
are the neighbors of `n` and its edges are all the edges in `H` that contain
`n`. Usually, the egonet do not include `n` itself. This can be controlled
The edge neighborhood of a node `n` in a hypergraph `H` is an edgelist of all the edges
containing `n` and its edges are all the edges in `H` that contain
`n`. Usually, the edge neighborhood does not include `n` itself. This can be controlled
with `include_self`.
Parameters
----------
H : Hypergraph
THe hypergraph of interest
n : node
Node whose egonet is needed.
Node whose edge_neighborhood is needed.
include_self : bool (default False)
Whether the egonet contains `n`.
Whether the edge_neighborhood contains `n`.
Returns
-------
list
An edgelist of the egonet of `n`.
An edgelist of the edge_neighborhood of `n`.
See Also
--------
Expand All @@ -131,11 +131,11 @@ def egonet(H, n, include_self=False):
--------
>>> import xgi
>>> H = xgi.Hypergraph([[1, 2, 3], [3, 4], [4, 5, 6]])
>>> H.neighbors(3)
>>> H.nodes.neighbors(3)
{1, 2, 4}
>>> xgi.egonet(H, 3)
>>> xgi.edge_neighborhood(H, 3)
[[1, 2], [4]]
>>> xgi.egonet(H, 3, include_self=True)
>>> xgi.edge_neighborhood(H, 3, include_self=True)
[[1, 2, 3], [3, 4]]
"""
Expand Down
27 changes: 0 additions & 27 deletions xgi/classes/hypergraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,33 +265,6 @@ def num_edges(self):
"""
return len(self._edge)

def neighbors(self, n):
"""Find the neighbors of a node.
The neighbors of a node are those nodes that appear in at least one edge with
said node.
Parameters
----------
n : node
Node to find neighbors of.
Returns
-------
set
A set of the neighboring nodes
See Also
--------
egonet
Examples
--------
>>> import xgi
>>> hyperedge_list = [[1, 2], [2, 3, 4]]
>>> H = xgi.Hypergraph(hyperedge_list)
>>> H.neighbors(1)
{2}
>>> H.neighbors(2)
{1, 3, 4}
"""
return {i for e in self._node[n] for i in self._edge[e]}.difference({n})

def add_node(self, node, **attr):
"""Add one node with optional attributes.
Expand Down
63 changes: 57 additions & 6 deletions xgi/classes/reportviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ class IDView(Mapping, Set):
"""

__slots__ = ("_dispatcher", "_id_dict", "_id_attr", "_ids")
__slots__ = (
"_dispatcher",
"_id_dict",
"_id_attr",
"_bi_id_dict",
"_bi_id_attr",
"_ids",
)

def __getstate__(self):
"""Function that allows pickling.
Expand All @@ -53,6 +60,8 @@ def __getstate__(self):
return {
"_id_dict": self._id_dict,
"_id_attr": self._id_attr,
"_bi_id_dict": self._bi_id_dict,
"_bi_id_attr": self._bi_id_attr,
"_ids": self._ids,
}

Expand All @@ -68,12 +77,16 @@ def __setstate__(self, state):
"""
self._id_dict = state["_id_dict"]
self._id_attr = state["_id_attr"]
self._bi_id_dict = state["_bi_id_dict"]
self._bi_id_attr = state["_bi_id_attr"]
self._ids = state["_ids"]

def __init__(self, H, id_dict, id_attr, dispatcher, ids=None):
def __init__(self, id_dict, id_attr, bi_id_dict, bi_id_attr, dispatcher, ids=None):
self._dispatcher = dispatcher
self._id_dict = id_dict
self._id_attr = id_attr
self._bi_id_dict = bi_id_dict
self._bi_id_attr = bi_id_attr

if ids is None:
self._ids = self._id_dict
Expand Down Expand Up @@ -297,6 +310,38 @@ def filterby_attr(self, attr, val, mode="eq", missing=None):
)
return type(self).from_view(self, bunch)

def neighbors(self, id):
"""Find the neighbors of an ID.
The neighbors of an ID are those IDs that share at least one bipartite ID.
Parameters
----------
id : hashable
ID to find neighbors of.
Returns
-------
set
A set of the neighboring IDs
See Also
--------
edge_neighborhood
Examples
--------
>>> import xgi
>>> hyperedge_list = [[1, 2], [2, 3, 4]]
>>> H = xgi.Hypergraph(hyperedge_list)
>>> H.nodes.neighbors(1)
{2}
>>> H.nodes.neighbors(2)
{1, 3, 4}
"""
return {i for n in self._id_dict[id] for i in self._bi_id_dict[n]}.difference(
{id}
)

@classmethod
def from_view(cls, view, bunch=None):
"""Create a view from another view.
Expand All @@ -321,6 +366,8 @@ def from_view(cls, view, bunch=None):
newview._dispatcher = view._dispatcher.__class__(view._dispatcher.net, newview)
newview._id_dict = view._id_dict
newview._id_attr = view._id_attr
newview._bi_id_dict = view._bi_id_dict
newview._bi_id_attr = view._bi_id_attr
all_ids = set(view._id_dict.keys())
if bunch is None:
newview._ids = all_ids
Expand Down Expand Up @@ -355,9 +402,11 @@ class NodeView(IDView):
def __init__(self, H, bunch=None):
dispatcher = NodeStatDispatcher(H, self)
if H is None:
super().__init__(None, None, None, dispatcher, bunch)
super().__init__(None, None, None, None, dispatcher, bunch)
else:
super().__init__(H, H._node, H._node_attr, dispatcher, bunch)
super().__init__(
H._node, H._node_attr, H._edge, H._edge_attr, dispatcher, bunch
)

def memberships(self, n=None):
"""Get the edge ids of which a node is a member.
Expand Down Expand Up @@ -403,9 +452,11 @@ class EdgeView(IDView):
def __init__(self, H, bunch=None):
dispatcher = EdgeStatDispatcher(H, self)
if H is None:
super().__init__(None, None, None, dispatcher, bunch)
super().__init__(None, None, None, None, dispatcher, bunch)
else:
super().__init__(H, H._edge, H._edge_attr, dispatcher, bunch)
super().__init__(
H._edge, H._edge_attr, H._node, H._node_attr, dispatcher, bunch
)

def members(self, e=None, dtype=list):
"""Get the node ids that are members of an edge.
Expand Down
2 changes: 1 addition & 1 deletion xgi/stats/nodestats.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def average_neighbor_degree(net, bunch):
"""
result = {}
for n in bunch:
neighbors = net.neighbors(n)
neighbors = net.nodes.neighbors(n)
result[n] = sum(len(net._node[nbr]) for nbr in neighbors)
result[n] = result[n] / len(neighbors) if neighbors else 0
return result
Expand Down

0 comments on commit 997f40e

Please sign in to comment.