diff --git a/doc/tutorial/centralities.ipynb b/doc/tutorial/centralities.ipynb index 9c2d969..ac9aa19 100644 --- a/doc/tutorial/centralities.ipynb +++ b/doc/tutorial/centralities.ipynb @@ -2,7 +2,6 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, "source": [ "# Calculating node centralities\n", "\n", @@ -10,11 +9,16 @@ "[Download notebook](https://github.com/pathpy/pathpy/raw/master/doc/tutorial/centralities.ipynb)\n", "\n", "In the following we implement degree- and path-based centrality measures and apply them to identify important nodes in empirical networks." - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": null, + "source": [ + "pip install git+git://github.com/pathpy/pathpy.git" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:05.807157Z", @@ -22,15 +26,18 @@ "iopub.status.idle": "2021-05-21T08:57:15.235168Z", "shell.execute_reply": "2021-05-21T08:57:15.235781Z" } - }, - "outputs": [], - "source": [ - "pip install git+git://github.com/pathpy/pathpy.git" - ] + } }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, + "source": [ + "from collections import defaultdict, Counter\n", + "\n", + "import pathpy as pp\n", + "import numpy as np" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:15.241285Z", @@ -38,52 +45,18 @@ "iopub.status.idle": "2021-05-21T08:57:16.045140Z", "shell.execute_reply": "2021-05-21T08:57:16.045516Z" } - }, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": "", - "text/html": "\n \n " - }, - "metadata": {} - } - ], - "source": [ - "from collections import defaultdict, Counter\n", - "\n", - "import pathpy as pp\n", - "import numpy as np" - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "We will test our implementation in an undirected and a directed example network." - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 2, - "metadata": { - "execution": { - "iopub.execute_input": "2021-05-21T08:57:16.050824Z", - "iopub.status.busy": "2021-05-21T08:57:16.050360Z", - "iopub.status.idle": "2021-05-21T08:57:16.056826Z", - "shell.execute_reply": "2021-05-21T08:57:16.057195Z" - } - }, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": "", - "text/html": "\n
\n
\n\n" - }, - "metadata": {} - } - ], + "execution_count": null, "source": [ "n_undirected = pp.Network(directed=False)\n", "n_undirected.add_edge('a', 'b')\n", @@ -96,29 +69,20 @@ "n_undirected.add_edge('d', 'f')\n", "n_undirected.add_edge('b', 'd')\n", "n_undirected.plot()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, + ], + "outputs": [], "metadata": { "execution": { - "iopub.execute_input": "2021-05-21T08:57:16.063394Z", - "iopub.status.busy": "2021-05-21T08:57:16.062932Z", - "iopub.status.idle": "2021-05-21T08:57:16.067423Z", - "shell.execute_reply": "2021-05-21T08:57:16.067016Z" - } - }, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": "", - "text/html": "\n
\n
\n\n" - }, - "metadata": {} + "iopub.execute_input": "2021-05-21T08:57:16.050824Z", + "iopub.status.busy": "2021-05-21T08:57:16.050360Z", + "iopub.status.idle": "2021-05-21T08:57:16.056826Z", + "shell.execute_reply": "2021-05-21T08:57:16.057195Z" } - ], + } + }, + { + "cell_type": "code", + "execution_count": null, "source": [ "n_directed = pp.Network(directed=True)\n", "n_directed.add_edge('a', 'b')\n", @@ -131,20 +95,33 @@ "n_directed.add_edge('d', 'f')\n", "n_directed.add_edge('b', 'd')\n", "n_directed.plot()" - ] + ], + "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2021-05-21T08:57:16.063394Z", + "iopub.status.busy": "2021-05-21T08:57:16.062932Z", + "iopub.status.idle": "2021-05-21T08:57:16.067423Z", + "shell.execute_reply": "2021-05-21T08:57:16.067016Z" + } + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "## Degree Centrality\n", "\n", "A simple, local notion of node importance in networks can be defined based on the degrees of nodes. In `pathpy` we can compute the (in- or out-)degrees of nodes as follows:" - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, + "source": [ + "n_undirected.degrees()" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.071088Z", @@ -152,26 +129,15 @@ "iopub.status.idle": "2021-05-21T08:57:16.074330Z", "shell.execute_reply": "2021-05-21T08:57:16.073654Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "{'a': 2, 'b': 3, 'c': 2, 'd': 4, 'e': 2, 'f': 3, 'g': 2}" - ] - }, - "metadata": {}, - "execution_count": 4 - } - ], - "source": [ - "n_undirected.degrees()" - ] + } }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, + "source": [ + "n_directed.indegrees()" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.080201Z", @@ -179,26 +145,15 @@ "iopub.status.idle": "2021-05-21T08:57:16.082069Z", "shell.execute_reply": "2021-05-21T08:57:16.082689Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "{'a': 1, 'b': 1, 'c': 1, 'd': 2, 'e': 1, 'f': 2, 'g': 1}" - ] - }, - "metadata": {}, - "execution_count": 5 - } - ], - "source": [ - "n_directed.indegrees()" - ] + } }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, + "source": [ + "n_directed.outdegrees()" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.087948Z", @@ -206,33 +161,22 @@ "iopub.status.idle": "2021-05-21T08:57:16.090618Z", "shell.execute_reply": "2021-05-21T08:57:16.090067Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "{'a': 1, 'b': 2, 'c': 1, 'd': 2, 'e': 1, 'f': 1, 'g': 1}" - ] - }, - "metadata": {}, - "execution_count": 6 - } - ], - "source": [ - "n_directed.outdegrees()" - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "In order to provide a unified API to all centrality measures, `pathpy` additionally includes a `degree_centrality` function in the module `pp.algorithms.centralities`. Using the `mode` parameter, we can switch between degre, in-, or out-degree." - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, + "source": [ + "pp.algorithms.centralities.degree_centrality(n_undirected)" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.094198Z", @@ -240,26 +184,15 @@ "iopub.status.idle": "2021-05-21T08:57:16.097035Z", "shell.execute_reply": "2021-05-21T08:57:16.096626Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "{'a': 2, 'b': 3, 'c': 2, 'd': 4, 'e': 2, 'f': 3, 'g': 2}" - ] - }, - "metadata": {}, - "execution_count": 7 - } - ], - "source": [ - "pp.algorithms.centralities.degree_centrality(n_undirected)" - ] + } }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, + "source": [ + "pp.algorithms.centralities.degree_centrality(n_directed, mode='indegree')" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.102237Z", @@ -267,26 +200,15 @@ "iopub.status.idle": "2021-05-21T08:57:16.104847Z", "shell.execute_reply": "2021-05-21T08:57:16.105208Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "{'a': 1, 'b': 1, 'c': 1, 'd': 2, 'e': 1, 'f': 2, 'g': 1}" - ] - }, - "metadata": {}, - "execution_count": 8 - } - ], - "source": [ - "pp.algorithms.centralities.degree_centrality(n_directed, mode='indegree')" - ] + } }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, + "source": [ + "pp.algorithms.centralities.degree_centrality(n_directed, mode='outdegree')" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.110721Z", @@ -294,33 +216,25 @@ "iopub.status.idle": "2021-05-21T08:57:16.113957Z", "shell.execute_reply": "2021-05-21T08:57:16.113541Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "{'a': 1, 'b': 2, 'c': 1, 'd': 2, 'e': 1, 'f': 1, 'g': 1}" - ] - }, - "metadata": {}, - "execution_count": 9 - } - ], - "source": [ - "pp.algorithms.centralities.degree_centrality(n_directed, mode='outdegree')" - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "A common task in network analysis is the ranking of nodes by centrality. Since dictionaries in `python` are not ordered, this requires a different data structure. To simplify this frequent task, `pathpy` comes with a `rank_centralities` function that takes an unordered dictionary with centrality values as parameter, and returns a list of tuples with node uids and centrality values that are arranged in descending order:" - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, + "source": [ + "ranking = pp.algorithms.centralities.rank_centralities(pp.algorithms.centralities.degree_centrality(n_undirected))\n", + "print(ranking)\n", + "\n", + "print('The most important node is', ranking[0][0])" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.119027Z", @@ -328,35 +242,24 @@ "iopub.status.idle": "2021-05-21T08:57:16.121694Z", "shell.execute_reply": "2021-05-21T08:57:16.122299Z" } - }, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "[('d', 4), ('f', 3), ('b', 3), ('g', 2), ('e', 2), ('c', 2), ('a', 2)]\nThe most important node is d\n" - ] - } - ], - "source": [ - "ranking = pp.algorithms.centralities.rank_centralities(pp.algorithms.centralities.degree_centrality(n_undirected))\n", - "print(ranking)\n", - "\n", - "print('The most important node is', ranking[0][0])" - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "## Centrality measures in `pathpy`\n", "\n", "To obtain a centrality measure that actually considers the topology of links (and not only the number of links incident to nodes) we can use the `centralities` module in `pathpy.algorithms`. " - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, + "source": [ + "pp.algorithms.centralities.closeness_centrality(n_undirected)" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.136270Z", @@ -364,33 +267,15 @@ "iopub.status.idle": "2021-05-21T08:57:16.138974Z", "shell.execute_reply": "2021-05-21T08:57:16.138263Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "defaultdict(float,\n", - " {'a': 0.07692307692307693,\n", - " 'b': 0.1111111111111111,\n", - " 'c': 0.07692307692307693,\n", - " 'd': 0.125,\n", - " 'e': 0.08333333333333333,\n", - " 'f': 0.09090909090909091,\n", - " 'g': 0.08333333333333333})" - ] - }, - "metadata": {}, - "execution_count": 11 - } - ], - "source": [ - "pp.algorithms.centralities.closeness_centrality(n_undirected)" - ] + } }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, + "source": [ + "pp.algorithms.centralities.closeness_centrality(n_undirected, normalized=True)" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.146837Z", @@ -398,40 +283,22 @@ "iopub.status.idle": "2021-05-21T08:57:16.148704Z", "shell.execute_reply": "2021-05-21T08:57:16.149325Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "defaultdict(float,\n", - " {'a': 0.46153846153846156,\n", - " 'b': 0.6666666666666666,\n", - " 'c': 0.46153846153846156,\n", - " 'd': 0.75,\n", - " 'e': 0.5,\n", - " 'f': 0.5454545454545454,\n", - " 'g': 0.5})" - ] - }, - "metadata": {}, - "execution_count": 12 - } - ], - "source": [ - "pp.algorithms.centralities.closeness_centrality(n_undirected, normalized=True)" - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "Alternatively, the same methods are also available as members of the Network class, which allows us to directly calculate them on an instance:" - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, + "source": [ + "pp.algorithms.centralities.rank_centralities(n_undirected.betweenness_centrality())" + ], + "outputs": [], "metadata": { "execution": { "iopub.execute_input": "2021-05-21T08:57:16.156375Z", @@ -439,59 +306,27 @@ "iopub.status.idle": "2021-05-21T08:57:16.159166Z", "shell.execute_reply": "2021-05-21T08:57:16.158467Z" } - }, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "[('d', 19.0),\n", - " ('b', 16.0),\n", - " ('f', 1.0),\n", - " ('a', 0.0),\n", - " ('e', 0.0),\n", - " ('c', 0.0),\n", - " ('g', 0.0)]" - ] - }, - "metadata": {}, - "execution_count": 13 - } - ], - "source": [ - "pp.algorithms.centralities.rank_centralities(n_undirected.betweenness_centrality())" - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "# todo\n", "\n", "datenbank highschoolabklären und visualiesirung hinzufügen" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "metadata": {}, "source": [ "# Path Centralities" - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": "", - "text/html": "\n
\n
\n\n" - }, - "metadata": {} - } - ], + "execution_count": null, "source": [ "n = pp.Network(directed=True)\n", "n.add_edge('a', 'x')\n", @@ -499,175 +334,88 @@ "n.add_edge('x', 'c')\n", "n.add_edge('x', 'd')\n", "n.plot()" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "PathPyCounter({'0x250e243a278': 2, '0x250e243a358': 2})\n" - ] - } - ], + "execution_count": null, "source": [ "pc = pp.PathCollection()\n", "pc.add(n.nodes['a'], n.nodes['x'], n.nodes['c'], count=2)\n", "pc.add(n.nodes['b'], n.nodes['x'], n.nodes['d'], count=2)\n", "print(pc.counter)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "array([[ 0., 1., inf, 2., 2.],\n", - " [inf, 0., inf, 1., 1.],\n", - " [inf, 1., 0., 2., 2.],\n", - " [inf, inf, inf, 0., inf],\n", - " [inf, inf, inf, inf, 0.]])" - ] - }, - "metadata": {}, - "execution_count": 16 - } - ], + "execution_count": null, "source": [ "pp.algorithms.shortest_paths.distance_matrix(n)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "defaultdict(float, {'a': 2.0, 'x': 2.0, 'b': 2.0, 'c': 0.0, 'd': 0.0})" - ] - }, - "metadata": {}, - "execution_count": 17 - } - ], + "execution_count": null, "source": [ "pp.algorithms.closeness_centrality(n, disconnected=True)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "defaultdict(float, {'x': 4.0, 'c': 0.0, 'b': 0.0, 'd': 0.0, 'a': 0.0})" - ] - }, - "metadata": {}, - "execution_count": 18 - } - ], + "execution_count": null, "source": [ "pp.algorithms.centralities.betweenness_centrality(n)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "defaultdict(.()>,\n", - " {'a': defaultdict(...()>,\n", - " {'a': 0, 'c': 2}),\n", - " 'x': defaultdict(...()>,\n", - " {'x': 0}),\n", - " 'c': defaultdict(...()>,\n", - " {'c': 0}),\n", - " 'b': defaultdict(...()>,\n", - " {'b': 0, 'd': 2}),\n", - " 'd': defaultdict(...()>,\n", - " {'d': 0})})" - ] - }, - "metadata": {}, - "execution_count": 19 - } - ], + "execution_count": null, "source": [ "pp.algorithms.shortest_paths.distance_matrix(pc)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "defaultdict(.()>,\n", - " {'a': 0.5, 'x': 0.0, 'c': 0.0, 'b': 0.5, 'd': 0.0})" - ] - }, - "metadata": {}, - "execution_count": 20 - } - ], + "execution_count": null, "source": [ "pp.algorithms.centralities.closeness_centrality(pc, disconnected=True)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "defaultdict(float, {'x': 2.0, 'a': 0.0, 'c': 0.0, 'b': 0.0, 'd': 0.0})" - ] - }, - "metadata": {}, - "execution_count": 21 - } - ], + "execution_count": null, "source": [ "pp.algorithms.centralities.betweenness_centrality(pc)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "source": [], "outputs": [], - "source": [] + "metadata": {} } ], "metadata": { "kernelspec": { - "name": "python37364bitbaseconda0af53cac2fb5450b99ec78a7efde583f", - "display_name": "Python 3.7.3 64-bit ('base': conda)" + "name": "python3", + "display_name": "Python 3.9.6 64-bit ('pathp': venv)" }, "language_info": { "codemirror_mode": { @@ -679,12 +427,15 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.9.6" }, "metadata": { "interpreter": { "hash": "82db51cffef479cc4d0f53089378e5a2925f9e7adca31d741132ceba61ecca6f" } + }, + "interpreter": { + "hash": "cbc06d602e623f3da56a6fd8482a438fdef2ce96413bf7bd183f2e65bbc2f388" } }, "nbformat": 4, diff --git a/pathpy/algorithms/centralities.py b/pathpy/algorithms/centralities.py index 2498b22..b7fed5b 100644 --- a/pathpy/algorithms/centralities.py +++ b/pathpy/algorithms/centralities.py @@ -77,7 +77,20 @@ def betweenness_centrality(self, normalized: bool = False) -> Dict: @betweenness_centrality.register(PathCollection) def _bw_paths(self: PathCollection, normalized: bool = False) -> Dict: - """Betweenness Centrality for Paths.""" + """Betweenness Centrality for Paths. + + Parameters + ---------- + paths : PathCollection + + The :py:class:`PathCollection` object that contains a collection of edges. + + normalized : bool + + If True the resulting centralities will be normalized such that the + minimum centrality is zero and the maximum centrality is one. + + """ # TODO: Move sp calculation to shortest_paths # from pathpy.statistics.subpaths import SubPathCollection @@ -129,7 +142,20 @@ def _bw_paths(self: PathCollection, normalized: bool = False) -> Dict: @betweenness_centrality.register(BaseNetwork) def _bw_network(self: Network, normalized: bool = False) -> Dict: - """Betweenness Centrality for Networks.""" + """Betweenness Centrality for Networks. + + Parameters + ---------- + network : Network + + The :py:class:`Network` object that contains the network. + + normalized : bool + + If True the resulting centralities will be normalized such that the + minimum centrality is zero and the maximum centrality is one. + + """ all_paths = shortest_paths.all_shortest_paths( self, weight=False, return_distance_matrix=False) @@ -158,7 +184,20 @@ def _bw_network(self: Network, normalized: bool = False) -> Dict: @betweenness_centrality.register(ABCHigherOrderNetwork) def _bw_hon(self: HigherOrderNetwork, normalized: bool = False) -> Dict: - """Betweenness Centrality for Networks.""" + """Betweenness Centrality for Higher Order Networks. + + Parameters + ---------- + network : HigherOrderNetwork + + The :py:class:`HigherOrderNetwork` object that contains a Higher Order Network (HON). + + normalized : bool + + If True the resulting centralities will be normalized such that the + minimum centrality is zero and the maximum centrality is one. + + """ from pathpy.core.edge import Edge from pathpy.core.path import Path @@ -335,7 +374,20 @@ def _cl_network(network: BaseNetwork, normalized: bool = False, disconnected=Fal @closeness_centrality.register(PathCollection) def _cl_paths(paths: PathCollection, normalized: bool = False, disconnected=False, weight: Optional[str]=None, count: bool=False) -> Dict: - """Betweenness Centrality for Paths.""" + """Betweenness Centrality for Paths. + + Parameters + ---------- + paths : PathCollection + + The :py:class:`PathCollection` object that contains a collection of edges + + normalized : bool + + If True the resulting centralities will be normalized such that the + minimum centrality is zero and the maximum centrality is one. + + """ if disconnected and normalized: raise ParameterError('No meaningful definition for normalized closeness centrality in disconnected networks') diff --git a/pathpy/algorithms/community_detection.py b/pathpy/algorithms/community_detection.py index 467131b..1863659 100644 --- a/pathpy/algorithms/community_detection.py +++ b/pathpy/algorithms/community_detection.py @@ -38,7 +38,30 @@ def _Q_merge(network: Network, A, D, n: int, m: int, C: Dict, merge: Set = set() def modularity_maximisation(network: Network, iterations: int = 1000) -> Tuple[Dict, float]: - """Modularity maximisation.""" + """Performs modularity maximisation in the given Network to detect + communities. + + Parameters + ---------- + + network: Network + + :py:class:`Network` object that contains the network. + + iterations: int = 1000 + + Number of iterations for the detection algorithm. + + Returns + ------- + + Tuple[Dict, float] + + The Dict containes tuples of node identifiers and + their corresponding communities. + The second value of the tuple returns the modularity of the network. + + """ A = network.adjacency_matrix(weighted=False) D = network.degrees() diff --git a/pathpy/algorithms/components.py b/pathpy/algorithms/components.py index 38dc121..17a4eb2 100644 --- a/pathpy/algorithms/components.py +++ b/pathpy/algorithms/components.py @@ -26,14 +26,13 @@ def find_connected_components(network: Network) -> Dict: Parameters ---------- + network : Network - network: Network + The :py:class:`Network` object that contains the network - Network instance Returns ------- - dict dictionary mapping node uids to components (represented as integer IDs) @@ -96,6 +95,13 @@ def tarjan(v: str): def mean_component_size(network: Network) -> float: """Returns the mean connected component size of the network. + + Parameters + ---------- + network : Network + + The :py:class:`Network` object that contains the network + """ components = find_connected_components(network) component_sizes = [len(nodes) for comp, nodes in components.items()] @@ -104,6 +110,13 @@ def mean_component_size(network: Network) -> float: def largest_connected_component(network: Network) -> Network: """Returns the largest connected component of the network. + + Parameters + ---------- + network : Network + + The :py:class:`Network` object that contains the network + """ LOG.debug('Computing connected components') @@ -128,12 +141,25 @@ def largest_connected_component(network: Network) -> Network: @property def is_connected(network: Network) -> bool: """Returns whether the network is (strongly) connected + Parameters + ---------- + network : Network + + The :py:class:`Network` object that contains the network + """ return largest_component_size(network) == network.number_of_nodes() def largest_component_size(network: Network) -> int: - """Largest component size of the network.""" + """Largest component size of the network. + Parameters + ---------- + network : Network + + The :py:class:`Network` object that contains the network + + """ LOG.debug('Computing connected components') components = find_connected_components(network) if len(components): diff --git a/pathpy/algorithms/evaluation.py b/pathpy/algorithms/evaluation.py index c0b64be..05f30b9 100644 --- a/pathpy/algorithms/evaluation.py +++ b/pathpy/algorithms/evaluation.py @@ -109,7 +109,31 @@ def train_test_split(network: Network, test_size: Optional[float]=0.25, train_si @train_test_split.register(TemporalNetwork) def _(network: TemporalNetwork, test_size: Optional[float]=0.25, train_size: Optional[float]=None, split: Optional[str]='interactions') -> tuple(TemporalNetwork, TemporalNetwork): """ - Performs a random split of a temporal network into a training and test network. The split can be performed along nodes, interactions, or time + Performs a split of a temporal network into a training and test network. The split can be performed along interactions or time. + + Parameters + ---------- + + network: TemporalNetwork + + The :py:class:`TemporalNetwork` object that contains the network for which the train/test split is performed. + + test_size: Optional[float] = 0.25 + + Fraction of the network to include in the test network + + train_size: Optional[float] = None + + Fraction of the network to include in the training network + + split: Optional['str'] = 'interactions' + + Specifies how the train/test split shall be performed. Based on the provided test size, for the parameter 'interactions' subset of edges is selected, while for 'time' a subset of the network time is selected. + + Returns + ------- + + Tuple (n1, n2) where n1 is the training network and n2 is the test network """ test_network = TemporalNetwork(directed=network.directed, multiedges=network.multiedges, uid=network.uid+'_test') train_network = TemporalNetwork(directed=network.directed, multiedges=network.multiedges, uid=network.uid+'_train') @@ -160,6 +184,17 @@ def shuffle_temporal_network(net: TemporalNetwork): """ Randomly reassigns timestamps (start, end, duration) of edges in a temporal network. This is useful to generate a random baseline for temporal patterns in temporal networks. + + Parameters + ---------- + net: TemporalNetwork + + The :py:class:`TemporalNetwork` object for which the shuffle is to be performed. + + Returns + ------- + + Shuffled version of the provided temporal network """ timestamps = [] edges = [] diff --git a/pathpy/algorithms/path_extraction.py b/pathpy/algorithms/path_extraction.py index dfcb23c..4259160 100644 --- a/pathpy/algorithms/path_extraction.py +++ b/pathpy/algorithms/path_extraction.py @@ -312,6 +312,30 @@ def generate_causal_tree(dag, root, node_map) -> Tuple(ABCDirectedAcyclicGraph, causal tree capture that - starting from the root node at step 0 - there is a causal path to node v at distance d from the root. Note that the same node can be represented by multiple nodes in the causal tree (at different distances d). + + Parameters + ---------- + dag : Any + + directed acyclic graph + + root : Any + + Root node of the causal tree + + node_map : Any + + Mapped nodes for the tree + + Returns + ------- + Tuple(ABCDirectedAcyclicGraph, defaultdict) + + A Tuple of the new causal tree as Directed Acyclic Graph + and a Dictionary with tuples of all the nodes' unique identifiers + and their distance to the root node. Using the uids of the nodes + ensures that the same physical nodes can occur at different + distances from the root """ from pathpy.models.directed_acyclic_graph import DirectedAcyclicGraph causal_tree = DirectedAcyclicGraph() diff --git a/pathpy/algorithms/shortest_paths.py b/pathpy/algorithms/shortest_paths.py index 8936c69..34c6994 100644 --- a/pathpy/algorithms/shortest_paths.py +++ b/pathpy/algorithms/shortest_paths.py @@ -195,6 +195,25 @@ def single_source_shortest_paths(network: Network, ) -> Union[dict, np.array]: """Calculates all shortest paths from a single given source node using a custom implementation of Dijkstra's algorithm based on a priority queue. + + Parameters + ---------- + + network : Network + + The :py:class:`Network` object that contains the network + + source : str + + The uid of the source node + + weight : bool = None + + If True cheapest paths will be calculated. + + Returns + ------- + Array with the distance of each node corresponding the index """ Q: dict = dict() dist = dict() @@ -255,7 +274,23 @@ def shortest_path_tree(network: Network, source: str, weight: Union[bool, str, None] = None ) -> Network: """Computes a shortest path tree rooted at the node with the - given source uid.""" + given source uid. + + Parameters + ---------- + + network : Network + + The :py:class:`Network` object that contains the network + + source : str + + The uid of the root node + + weight : bool = None + + f True cheapest paths will be calculated. + """ n_tree = net.Network(directed=True) diff --git a/pathpy/algorithms/trees.py b/pathpy/algorithms/trees.py index a7a3728..eb42387 100644 --- a/pathpy/algorithms/trees.py +++ b/pathpy/algorithms/trees.py @@ -27,7 +27,14 @@ def check_tree(network: Network): - + """Returns if the Networks' Graph is a rooted tree. + + Parameters + ---------- + network : Network + + The :py:class:`Network` object + """ if network.directed: # identify node with zero indegree @@ -64,6 +71,19 @@ def dfs(network: Network, node: str): def tree_size(network: Network, node: str): + """ Returns the size of the graph tree, using the passed node as root. + + Parameters + ---------- + + network : Network + + The :py:class:`Network` object used for the tree + + node : str + + Identitifier of the root node + """ size = 1 for v in network.successors[node]: diff --git a/pathpy/core/temporal.py b/pathpy/core/temporal.py index 499b1d9..27cad35 100644 --- a/pathpy/core/temporal.py +++ b/pathpy/core/temporal.py @@ -21,7 +21,23 @@ class TemporalPathPyObject(PathPyObject): - """Base class for a temporal object.""" + """Base class for a temporal object. + + A temporal network consists of nodes which links are only active or + change their values at certain points in time. + + Parameters + ---------- + + uid : Optional[str] = None + + The parameter ``uid`` is the unique identifier for the path object. + + kwargs : Any + + Keyword Arguments to create new temporal network events. They are stored with theire beginning + and ending point. + """ def __init__(self, uid: Optional[str] = None, **kwargs: Any) -> None: """Initialize the temporal object.""" diff --git a/pathpy/generators/random_graphs.py b/pathpy/generators/random_graphs.py index 3228ad5..43437df 100644 --- a/pathpy/generators/random_graphs.py +++ b/pathpy/generators/random_graphs.py @@ -533,6 +533,16 @@ def Molloy_Reed(degrees: Union[np.array, Dict[int, float]], multiedge: bool = Fa def Molloy_Reed_randomize(network: Network) -> Optional[Network]: """Generates a random realization of a given network based on the observed degree sequence. + + Parameters + ---------- + network : Network + + The :py:class:`Network` object that contains the network + + Returns + ------- + Molley-Reed graph """ # degrees are listed in order of node indices degrees = network.degree_sequence() diff --git a/pathpy/io/csv.py b/pathpy/io/csv.py index 60c7eb6..1dfa9f0 100644 --- a/pathpy/io/csv.py +++ b/pathpy/io/csv.py @@ -35,7 +35,27 @@ def read_dataframe(filename: Optional[str] = None, sep: str = ',', header: bool = True, names: Optional[list] = None) -> pd.DataFrame: - """Read csv database as a pandas data frame.""" + """Read csv database as a pandas data frame. + + Parameters + ---------- + filename : Optional[str] = None + + Path to the .csv file + + sep : str = `` , `` + + Character seperating the data + + header : bool = ``True`` + + Use the file header + + names : Optional[list] = ``None`` + + List of column names to use + + """ if header: frame = pd.read_csv(filename, sep=sep) @@ -54,7 +74,47 @@ def read_network(filename: Optional[str] = None, header: bool = True, names: Optional[list] = None, **kwargs: Any) -> Network: - """Reads a network from a csvfile.""" + """Reads a network from a csvfile. + + Parameters + ---------- + filename : Optional[str] = None + + Path to the .csv file + + loops : bool = ``True`` + + Does the network have loops + + directed : bool = ``True`` + + Is the network directed + + multiedges : bool = ``False`` + + Does the network have multiedges + + sep : str = `` , `` + + Character seperating the data + + header : bool = ``True`` + + Use the file header + + names : Optional[list] = ``None`` + + List of column names to use + + **kwargs : Any + + Arbitrary keyword arguments that will be set as network-level attributes + + Returns + ------- + An object of the :py:class:`Network` generated from the csv file. + + """ # pylint: disable=too-many-arguments frame = read_dataframe(filename=filename, sep=sep, @@ -74,7 +134,47 @@ def read_temporal_network(filename: Optional[str] = None, header: bool = True, names: Optional[list] = None, **kwargs: Any) -> TemporalNetwork: - """Read temporal network from a csv database.""" + """Read temporal network from a csv database. + + Parameters + ---------- + filename : Optional[str] = None + + Path to the .csv file + + loops : bool = ``True`` + + Does the network have loops + + directed : bool = ``True`` + + Is the network directed + + multiedges : bool = ``False`` + + Does the network have multiedges + + sep : str = `` , `` + + Character seperating the data + + header : bool = ``True`` + + Use the file header + + names : Optional[list] = ``None`` + + List of column names to use + + **kwargs : Any + + Arbitrary keyword arguments that will be set as network-level attributes + + Returns + ------- + An object of the :py:class:`Network` generated from the csv database. + + """ # pylint: disable=too-many-arguments frame = read_dataframe(filename=filename, sep=sep, @@ -98,18 +198,30 @@ def read_pathcollection(filename: str, separator: str = ',', Parameters ---------- filename : str + path to edgelist file + separator : str + character separating the nodes + frequency : bool + is a frequency given? if ``True`` it is the last element in the edge (i.e. ``a,b,2``) + directed : bool + are the edges directed or undirected + maxlines : int + number of lines to read (useful to test large files). None means the entire file is read + Returns + ------- + An object of the :py:class:`PathCollection` with all the paths generated from the file. """ from pathpy.core.path import Path, PathCollection @@ -220,7 +332,42 @@ def write(network: Union[Network, TemporalNetwork], include_edge_uid: bool = False, export_indices: bool = False, **pdargs: Any) -> None: - """Stores all edges including edge attributes in a csv file.""" + """Stores all edges including edge attributes from a static or temporal network in a csv file. + + Parameters + ---------- + network : Network, TemporalNetwork + + An object of the :py:class:`Network` or :py:class:`TemporalNetwork` + + path_or_buf : Any = None + + This can be a string, a file buffer, or None (default). Follows + pandas.DataFrame.to_csv semantics. If a string filename is given, the + network will be saved in a file. If None, the csv file contents is + returned as a string. If a file buffer is given, the csv file will be + saved to the file. + + include_edge_ui : bool = False + + Whether to exclude edge uids in the exported csv file or not + (default). If this is set to True, each edge between nodes with uids v + and w will be exported to a line w,v. If this is set to False + (default), the uid of the edge will be additionally included, + i.e. exporting v,w,e_uid. + + export_indices : bool = False + + Whether or not to replace node uids by integer node indices. If False + (default), string node uids in pp.Network instance will be used. If + True, node integer indices are exported instead. + + **pdargs : Any + + Keyword args that will be passed to pandas.DataFrame.to_csv. This + allows full control of the csv export. + + """ frame = to_dataframe(network=network, include_edge_uid=include_edge_uid, export_indices=export_indices) diff --git a/pathpy/io/sql.py b/pathpy/io/sql.py index 3842cf2..9538576 100644 --- a/pathpy/io/sql.py +++ b/pathpy/io/sql.py @@ -37,7 +37,30 @@ def read_dataframe(db_file: Optional[str] = None, uri: Optional[bool] = False, sql: Optional[str] = None, table: Optional[str] = None) -> pd.DataFrame: - """Read sql database as a pandas data frame.""" + """Read sql database as a pandas data frame. The Database can exists locally or be read from an online ressource. + + Parameters + ---------- + db_file : Optional[str] = ``None`` + + The path to the databse file + + con : Optional[sqlite3.Connection] = ``None`` + + The SQLite3 connection in which the network will be stored + + uri : Optional[bool] = ``False`` + + Uniform Resource Identifier for the databse + + sql : Optional[str] = ``None`` + + Executable SQL query specify the datas + + table : Optional[str] = ``None`` + + Database table to read the data from + """ LOG.debug('Load sql file as pandas data frame.') @@ -111,7 +134,43 @@ def read_network(db_file: Optional[str] = None, table: Optional[str] = None, uri: Optional[bool] = False, **kwargs: Any) -> Network: - """Read network from a sqlite database.""" + """Read network from a sqlite database. + + Parameters + ---------- + db_file : Optional[str] = ``None`` + + The path to the databse file + + loops : bool = ``True`` + + Does the network have loops + + directed : bool = ``True`` + + Is the network directed + + multiedges : bool = ``True`` + + Does the network have multiedges + + con : Optional[sqlite3.Connection] = ``None`` + + The SQLite3 connection in which the network will be stored + + uri : Optional[bool] = ``False`` + + Uniform Resource Identifier for the databse + + sql : Optional[str] = ``None`` + + Executable SQL query specify the datas + + table : Optional[str] = ``None`` + + Database table to read the data from + + """ # pylint: disable=too-many-arguments frame = read_dataframe(db_file=db_file, con=con, sql=sql, table=table, uri=uri) @@ -152,23 +211,22 @@ def write_dataframe(frame: pd.DataFrame, Parameters ---------- + frame : pd.DataFrame + + Source pandas Data Frame to write into SQL - network: Network + table: str - The network to store in the sqlite database + Name of the table in the database in which the network will be stored. - filename: str + filename: Optional[str] = ``None`` The name of the SQLite database in which the network will be stored - con: sqlite3.Connection + con : Optional[sqlite3.Connection] = ``None`` The SQLite3 connection in which the network will be stored - table: str - - Name of the table in the database in which the network will be stored. - **pdargs: Keyword args that will be passed to pandas.DataFrame.to_sql. @@ -202,7 +260,39 @@ def write(network: Union[Network, TemporalNetwork], include_edge_uid: bool = False, export_indices: bool = False, **pdargs: Any) -> None: - """Stores all edges including edge attributes in a sql file.""" + """Stores all edges including edge attributes in a sql file. It stores regular as well as temporal networks. + + Parameters + ---------- + + network: Union[Network, TemporalNetwork] + + The network to store in the sqlite database + + table: str + + Name of the table in the database in which the network will be stored. + + filename: str + + The name of the SQLite database in which the network will be stored + + con: sqlite3.Connection + + The SQLite3 connection in which the network will be stored + + include_edge_ui : bool = ``False`` + + Whether or not to include a column that stores the uids of the edge objects + + export_indices : bool = ``False`` + + Whether or not to use node indices rather than node uids. This is useful to import network data in tools that only support integer node identifiers. + + **pdargs: + + Keyword args that will be passed to pandas.DataFrame.to_sql. + """ frame = to_dataframe(network=network, include_edge_uid=include_edge_uid, export_indices=export_indices) diff --git a/pathpy/models/temporal_network.py b/pathpy/models/temporal_network.py index 8330b89..7fce473 100644 --- a/pathpy/models/temporal_network.py +++ b/pathpy/models/temporal_network.py @@ -50,7 +50,11 @@ def __init__(self, *node: Union[str, PathPyObject], TemporalPathPyObject.__init__(self, uid=self.uid, **kwargs) def summary(self) -> str: - """Object summary. """ + """Returns a summary of the temporal node. + + The summary containes the observation periode of the node, i.e. + the observation start and the observation end time. + """ summary = [ 'Observation periode:\t{} - {}'.format(self.start, self.end) ] @@ -73,7 +77,11 @@ def __init__(self, v: Union[str, PathPyObject], TemporalPathPyObject.__init__(self, uid=uid, **kwargs) def summary(self) -> str: - """Object summary. """ + """Object summary. + + The summary containes the observation periode of the edge, i.e. + the observation start and the observation end time. + """ summary = [ 'Observation period:\t{} - {}'.format(self.start, self.end) ] @@ -112,17 +120,17 @@ def _(self, key: Union[int, float, slice]) -> Any: @property def start(self): - """start of the object""" + """Returns the start of the node collections' events""" return self._events.begin() @property def end(self): - """end of the object""" + """Returns the end of the node collections' events""" return self._events.end() @property def events(self): - """Temporal events""" + """Returns an interval tree of the node collections' temporal events""" return self._events @singledispatchmethod @@ -186,17 +194,17 @@ def _(self, key: Union[int, float, slice]) -> Any: @property def start(self): - """start of the object""" + """Returns the start of the edge object""" return self._events.begin() @property def end(self): - """end of the object""" + """Returns the end of the edge object""" return self._events.end() @property def events(self): - """Temporal events""" + """Returns all temporal events of the edge object""" return self._events @singledispatchmethod @@ -227,7 +235,36 @@ def _remove(self, obj) -> None: class TemporalNetwork(BaseTemporalNetwork, Network): - """Base class for a temporal networks.""" + """Base class for a temporal networks. In a temporal Network, properties of nodes, edges or paths + can change over time. + + Parameters + ---------- + uid : Optional[str] = ``None`` + + The parameter ``uid`` is the unique identifier for the network. This + option can late be used for multi-layer networks. Currently the ``uid`` + of the network is not in use. + + directed : bool = ``True`` + + Specifies if a network contains directed edges and paths, i.e u->v->w + or undirected edges and paths i.d. u-v-w. If ``True`` the all + subsequent objects are directed, i.e. quantities can only transmited + from the source node ``v`` to the traget node ``w``. If ``False`` the + al subsequent obects are undirected, i.e. quantities can be transmited + in both directions. Per default networks in ``pathpy`` are directed. + + multiedges : bool = ``False`` + + Specifies if a network contians multiple edges, i.e. two or more edges that have + the same head and tail vertices. + + **kwargs : Any + + Keyword arguments to store network attributes. Attributes are added to + the network as ``key=value`` pairs. + """ def __init__(self, uid: Optional[str] = None, directed: bool = True, multiedges: bool = False, **kwargs: Any) -> None: