From 743939f1a5e1632ad604433bb9bda8ccd9926905 Mon Sep 17 00:00:00 2001 From: Leo Torres Date: Wed, 2 Nov 2022 11:47:37 +0100 Subject: [PATCH] Implement set operations for NodeView and EdgeView (#208) * Feat: now NodeViews and EdgeViews support set operations. Fixes #206. * update tutorial to include set operations --- tests/classes/test_reportviews.py | 20 +++ tutorials/Tutorial 6 - Statistics.ipynb | 159 ++++++++++++++++-------- xgi/classes/reportviews.py | 9 ++ 3 files changed, 138 insertions(+), 50 deletions(-) diff --git a/tests/classes/test_reportviews.py b/tests/classes/test_reportviews.py index 4c5f6e101..2c123c538 100644 --- a/tests/classes/test_reportviews.py +++ b/tests/classes/test_reportviews.py @@ -242,3 +242,23 @@ def test_bool(edgelist1): assert bool(H.edges) is False H = xgi.Hypergraph(edgelist1) assert bool(H.edges) is True + + +def test_set_operations(hyperwithattrs): + H = hyperwithattrs + + nodes1 = H.nodes.filterby_attr("color", "blue") + nodes2 = H.nodes.filterby("degree", 2, "geq") + assert set(nodes2 - nodes1) == {3, 4} + assert set(nodes1 - nodes2) == set() + assert set(nodes1 & nodes2) == {2, 5} + assert set(nodes1 | nodes2) == {2, 3, 4, 5} + assert set(nodes1 ^ nodes2) == {3, 4} + + edges1 = H.edges + edges2 = H.edges.filterby("size", 3, "leq") + assert set(edges2 - edges1) == set() + assert set(edges1 - edges2) == {1} + assert set(edges1 & edges2) == {0, 2} + assert set(edges1 | edges2) == {0, 1, 2} + assert set(edges1 ^ edges2) == {1} diff --git a/tutorials/Tutorial 6 - Statistics.ipynb b/tutorials/Tutorial 6 - Statistics.ipynb index 91262d0aa..4347c88bb 100644 --- a/tutorials/Tutorial 6 - Statistics.ipynb +++ b/tutorials/Tutorial 6 - Statistics.ipynb @@ -742,6 +742,78 @@ "H.nodes.filterby(\"degree\", H.nodes.degree.max())" ] }, + { + "cell_type": "markdown", + "id": "cc5df033", + "metadata": {}, + "source": [ + "## Set operations" + ] + }, + { + "cell_type": "markdown", + "id": "11548f49", + "metadata": {}, + "source": [ + "Another way of chaining multiple results of `filterby*` methods is by using set operations. Indeed, chaining two filters is the same as intersecting the results of two separate calls:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "970da532", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2, 5]\n", + "[2, 5]\n" + ] + } + ], + "source": [ + "print(H.nodes.filterby(\"degree\", 2).filterby_attr(\"color\", \"blue\"))\n", + "print(H.nodes.filterby(\"degree\", 2) & H.nodes.filterby_attr(\"color\", \"blue\"))" + ] + }, + { + "cell_type": "markdown", + "id": "dc0ff694", + "metadata": {}, + "source": [ + "Other set operations are also supported." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "65c22a67", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nodes1 - nodes2 = [4]\n", + "nodes2 - nodes1 = []\n", + "nodes1 & nodes2 = [2, 5]\n", + "nodes1 | nodes2 = [2, 4, 5]\n", + "nodes1 ^ nodes2 = [4]\n" + ] + } + ], + "source": [ + "nodes1 = H.nodes.filterby(\"degree\", 2)\n", + "nodes2 = H.nodes.filterby_attr(\"color\", \"blue\")\n", + "print(f\"nodes1 - nodes2 = {nodes1 - nodes2}\")\n", + "print(f\"nodes2 - nodes1 = {nodes2 - nodes1}\")\n", + "print(f\"nodes1 & nodes2 = {nodes1 & nodes2}\")\n", + "print(f\"nodes1 | nodes2 = {nodes1 | nodes2}\")\n", + "print(f\"nodes1 ^ nodes2 = {nodes1 ^ nodes2}\")" + ] + }, { "cell_type": "markdown", "id": "697055d8", @@ -760,7 +832,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 28, "id": "09aeadef", "metadata": {}, "outputs": [ @@ -770,7 +842,7 @@ "MultiNodeStat(degree, clustering)" ] }, - "execution_count": 26, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -789,7 +861,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 29, "id": "31619b17", "metadata": { "scrolled": true @@ -805,7 +877,7 @@ " 5: {'degree': 2, 'clustering': 3.0}}" ] }, - "execution_count": 27, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -824,7 +896,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 30, "id": "c29d8461", "metadata": {}, "outputs": [ @@ -903,7 +975,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 31, "id": "250d2808", "metadata": {}, "outputs": [ @@ -971,7 +1043,7 @@ "5 2 3.000000" ] }, - "execution_count": 29, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -991,7 +1063,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 32, "id": "3c47eac0", "metadata": {}, "outputs": [ @@ -1048,7 +1120,7 @@ "3 1.333333" ] }, - "execution_count": 30, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -1067,7 +1139,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 33, "id": "d28315b8", "metadata": {}, "outputs": [ @@ -1098,7 +1170,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 34, "id": "a6c92003", "metadata": { "scrolled": true @@ -1174,7 +1246,7 @@ "5 2 1 blue" ] }, - "execution_count": 32, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1196,12 +1268,12 @@ "id": "29d4aa9f", "metadata": {}, "source": [ - "Every feature showcased above (lazy evaluation, type conversion, filtering, and multi objects) is supported for edge-quantity or edge-attribute mappings, via `EdgeStat` objects." + "Every feature showcased above (lazy evaluation, type conversion, filtering, set operations, and multi objects) is supported for edge-quantity or edge-attribute mappings, via `EdgeStat` objects." ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 35, "id": "3a43de55", "metadata": {}, "outputs": [ @@ -1211,7 +1283,7 @@ "EdgeStat('order')" ] }, - "execution_count": 33, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1222,7 +1294,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 36, "id": "9e708cda", "metadata": {}, "outputs": [ @@ -1232,7 +1304,7 @@ "{0: 2, 1: 3, 2: 2}" ] }, - "execution_count": 34, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -1243,7 +1315,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 37, "id": "29154c7e", "metadata": {}, "outputs": [ @@ -1253,7 +1325,7 @@ "EdgeView((1,))" ] }, - "execution_count": 35, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -1264,7 +1336,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 38, "id": "6b319bfc", "metadata": {}, "outputs": [ @@ -1320,7 +1392,7 @@ "2 2 3" ] }, - "execution_count": 36, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -1349,7 +1421,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 39, "id": "fec60de5", "metadata": {}, "outputs": [], @@ -1360,9 +1432,17 @@ " return {n: 10 * net.degree(n) for n in bunch}" ] }, + { + "cell_type": "markdown", + "id": "b0be4a0d", + "metadata": {}, + "source": [ + "Now `user_degree` is a valid stat that can be computed on any hypergraph:" + ] + }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 40, "id": "7409ae74", "metadata": {}, "outputs": [ @@ -1372,7 +1452,7 @@ "{1: 10, 2: 20, 3: 30, 4: 20, 5: 20}" ] }, - "execution_count": 38, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -1386,12 +1466,12 @@ "id": "65f4b7bf", "metadata": {}, "source": [ - "Now every single feature showcased above is available for use with `user_degree`, including filtering nodes and multi stats objects." + "Every single feature showcased above is available for use with `user_degree`, including filtering nodes and multi stats objects." ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 41, "id": "314751dc", "metadata": {}, "outputs": [ @@ -1401,7 +1481,7 @@ "NodeView((2, 4, 5))" ] }, - "execution_count": 39, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -1412,7 +1492,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 42, "id": "7ae354dd", "metadata": {}, "outputs": [ @@ -1480,7 +1560,7 @@ "5 2 20" ] }, - "execution_count": 40, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -1505,27 +1585,6 @@ "User-defined edge statistics can similarly be defined using the `@xgi.edgestat` decorator." ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ee7467f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "2 + 2" - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/xgi/classes/reportviews.py b/xgi/classes/reportviews.py index d5c6e9be6..4b15e3ae5 100644 --- a/xgi/classes/reportviews.py +++ b/xgi/classes/reportviews.py @@ -497,6 +497,15 @@ def from_view(cls, view, bunch=None): newview._ids = bunch return newview + def _from_iterable(self, it): + """Construct an instance of the class from any iterable input. + + This overrides collections.abc.Set._from_iterable, which is in turn used to + implement set operations such as &, |, ^, -. + + """ + return self.from_view(self, it) + class NodeView(IDView): """An IDView that keeps track of node ids.