Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename egonet and move the neighbors method to the IDView class #129

Merged
merged 6 commits into from
Jun 29, 2022
Merged
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
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",
Comment on lines +41 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

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

At this point we might want to consider (in the future) just passing the entire network...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

haha, good point. In the future perhaps the whole network and an indicator whether it's for nodes or edges?

)

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