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

Added the ability for XGI to load data collections #540

Merged
merged 14 commits into from
Jun 24, 2024
3 changes: 2 additions & 1 deletion docs/source/api/convert/xgi.convert.hypergraph_dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ xgi.convert.hypergraph_dict

.. rubric:: Functions

.. autofunction:: dict_to_hypergraph
.. autofunction:: to_hypergraph_dict
.. autofunction:: from_hypergraph_dict
148 changes: 148 additions & 0 deletions tests/convert/test_hypergraph_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import pytest

import xgi
from xgi.exception import XGIError


def test_to_hypergraph_dict():
H = xgi.Hypergraph()
H.add_edges_from(
[
(["1", "2"], "edge1", {"weight": 2}),
(["2", "3", "4"], "edge2", {"weight": 4}),
(["1", "4"], "edge3", {"weight": -1}),
]
)
H.set_node_attributes(
{
"1": {"color": "blue"},
"2": {"color": "yellow"},
"3": {"color": "cyan"},
"4": {"color": "green"},
}
)
H["name"] = "test"
H["author"] = "Nicholas Landry"

d = xgi.to_hypergraph_dict(H)

hd = {
"type": "hypergraph",
"hypergraph-data": {"name": "test", "author": "Nicholas Landry"},
"node-data": {
"1": {"color": "blue"},
"2": {"color": "yellow"},
"3": {"color": "cyan"},
"4": {"color": "green"},
},
"edge-data": {
"edge1": {"weight": 2},
"edge2": {"weight": 4},
"edge3": {"weight": -1},
},
"edge-dict": {
"edge1": ["1", "2"],
"edge2": ["2", "3", "4"],
"edge3": ["1", "4"],
},
}

assert hd == d

# test that nodes that get cast to the same id
# raises an error.
H = xgi.Hypergraph()
H.add_nodes_from(["2", 2])
with pytest.raises(XGIError):
xgi.to_hypergraph_dict(H)

# test that edges that get cast to the same id
# raises an error.
H = xgi.Hypergraph()
H.add_edges_from({"2": [1, 2, 3], 2: [3, 4]})
with pytest.raises(XGIError):
xgi.to_hypergraph_dict(H)


def test_from_hypergraph_dict(edgelist1):
hd = {
"type": "hypergraph",
"hypergraph-data": {"name": "test", "author": "Nicholas Landry"},
"node-data": {
"1": {"color": "blue"},
"2": {"color": "yellow"},
"3": {"color": "cyan"},
"4": {"color": "green"},
},
"edge-data": {
"edge1": {"weight": 2},
"edge2": {"weight": 4},
"edge3": {"weight": -1},
},
"edge-dict": {
"edge1": ["1", "2"],
"edge2": ["2", "3", "4"],
"edge3": ["1", "4"],
},
}
Hd = xgi.from_hypergraph_dict(hd)

H = xgi.Hypergraph()
H.add_edges_from(
[
(["1", "2"], "edge1", {"weight": 2}),
(["2", "3", "4"], "edge2", {"weight": 4}),
(["1", "4"], "edge3", {"weight": -1}),
]
)
H.set_node_attributes(
{
"1": {"color": "blue"},
"2": {"color": "yellow"},
"3": {"color": "cyan"},
"4": {"color": "green"},
}
)
H["name"] = "test"
H["author"] = "Nicholas Landry"

assert H.nodes == Hd.nodes
assert H.edges == Hd.edges
for e in H.edges:
assert H.edges.members(e) == Hd.edges.members(e)
assert H.edges[e] == Hd.edges[e]

for n in H.nodes:
assert H.nodes[n] == Hd.nodes[n]

assert H._hypergraph == Hd._hypergraph

# test bad dicts
hd = {
"type": "hypergraph",
"node-data": {
"1": {"color": "blue"},
},
"edge-data": {
"edge1": {"weight": 2},
},
"edge-dict": {
"edge1": ["1", "5"],
},
}
with pytest.raises(XGIError):
xgi.from_hypergraph_dict(hd)

hd = {
"type": "hypergraph",
"node-data": {
"1": {"color": "blue"},
"5": {},
},
"edge-data": {},
"edge-dict": {
"edge1": ["1", "5"],
},
}
with pytest.raises(XGIError):
xgi.from_hypergraph_dict(hd)
13 changes: 12 additions & 1 deletion tests/readwrite/test_json.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import tempfile

import pytest
from os.path import join

import xgi
from xgi.exception import XGIError
Expand Down Expand Up @@ -216,7 +217,7 @@ def test_read_json():
xgi.read_json(filename, edgetype=int)


def test_write_json(edgelist1):
def test_write_json(edgelist1, edgelist2):
_, filename = tempfile.mkstemp()
H1 = xgi.Hypergraph(edgelist1)

Expand Down Expand Up @@ -263,3 +264,13 @@ def test_write_json(edgelist1):
badH.add_edges_from({"2": [1, 2, 3], 2: [4, 5, 6]})
with pytest.raises(XGIError):
xgi.write_json(badH, "test.json")

# test list collection
H2 = xgi.Hypergraph(edgelist2)
collection = [H1, H2]
dir = tempfile.mkdtemp()

xgi.write_json(collection, dir, collection_name="test")
collection = xgi.read_json(join(dir, "test_collection_information.json"))
assert len(collection) == 2
assert isinstance(collection, dict)
19 changes: 19 additions & 0 deletions tests/readwrite/test_xgi_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,29 @@ def test_load_xgi_data(capfd):
assert "email-enron" in out
assert "congress-bills" in out

# test collection
collection = load_xgi_data("hyperbard")
assert len(collection) == 37
nwlandry marked this conversation as resolved.
Show resolved Hide resolved
assert isinstance(collection, dict)
assert collection["as-you-like-it"].num_nodes == 30
assert collection["as-you-like-it"].num_edges == 80


@pytest.mark.webtest
@pytest.mark.slow
def test_download_xgi_data():
dir = tempfile.mkdtemp()
download_xgi_data("email-enron", dir)
H = read_json(join(dir, "email-enron.json"))
H_online = load_xgi_data("email-enron")
assert H.edges.members() == H_online.edges.members()

dir = tempfile.mkdtemp()
download_xgi_data("hyperbard", dir)
collection = read_json(join(dir, "collection_information.json"))

print(collection)
assert len(collection) == 37
assert isinstance(collection, dict)
assert collection["as-you-like-it"].num_nodes == 30
assert collection["as-you-like-it"].num_edges == 80
65 changes: 63 additions & 2 deletions xgi/convert/hypergraph_dict.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,73 @@
"""Method for converting from a standardized dictionary."""

from collections import Counter

from ..exception import XGIError
from ..generators import empty_hypergraph
from ..utils import get_network_type

__all__ = ["to_hypergraph_dict", "from_hypergraph_dict"]

__all__ = ["dict_to_hypergraph"]

def to_hypergraph_dict(H):
nwlandry marked this conversation as resolved.
Show resolved Hide resolved
"""A method to convert a hypergraph into a standard dictionary format.

def dict_to_hypergraph(data, nodetype=None, edgetype=None, max_order=None):
Parameters
----------
H : Hypergraph
The hypergraph to convert

Returns
-------
dict
A dictionary of the form described in https://github.com/xgi-org/xgi-data.

Raises
------
XGIError
If node IDs will be collapsed when casting to a string.
XGIError
If edge Ids will be collapsed when casting to a string.
"""
data = {}
data["type"] = get_network_type(H)
nwlandry marked this conversation as resolved.
Show resolved Hide resolved
# name always gets written (default is an empty string)
data["hypergraph-data"] = {}
data["hypergraph-data"].update(H._hypergraph)

# get node data
data["node-data"] = {str(idx): H.nodes[idx] for idx in H.nodes}

if len(data["node-data"]) != H.num_nodes:
dups = [
item
for item, count in Counter([str(n) for n in H.nodes]).items()
if count > 1
]
raise XGIError(
f"When casting node IDs to strings, ID(s) {', '.join(dups)} have conflicting IDs!"
)

data["edge-data"] = {str(idx): H.edges[idx] for idx in H.edges}

if len(data["edge-data"]) != H.num_edges:
dups = [
item
for item, count in Counter([str(n) for n in H.edges]).items()
if count > 1
]
raise XGIError(
f"When casting edge IDs to strings, ID(s) {', '.join(dups)} have conflicting IDs!"
)

# hyperedge dict
data["edge-dict"] = {
str(idx): [str(n) for n in sorted(H.edges.members(idx))] for idx in H.edges
}
return data


def from_hypergraph_dict(data, nodetype=None, edgetype=None, max_order=None):
"""
A function to read a file in a standardized JSON format.

Expand Down
Loading
Loading