From a3fb9e2c1144feb42b66068505c1f234eae64a35 Mon Sep 17 00:00:00 2001 From: Alberto Cattaneo Date: Mon, 17 Jun 2024 09:34:03 +0000 Subject: [PATCH] release kg-topology-toolbox Co-authored-by: Daniel Justus Co-authored-by: Stephen Bonner Co-authored-by: Thomas Martynec --- .flake8 | 13 + .github/workflows/ci.yaml | 39 + .gitignore | 177 ++ LICENSE | 21 + NOTICE.md | 90 + README.md | 32 + dev | 171 ++ docs/Makefile | 22 + docs/make.bat | 36 + docs/source/API_reference.rst | 11 + docs/source/_templates/class.rst | 11 + docs/source/_templates/module.rst | 60 + docs/source/_templates/package.rst | 61 + docs/source/_templates/toc.rst | 12 + docs/source/conf.py | 67 + docs/source/images/edge_patterns.png | Bin 0 -> 32677 bytes docs/source/index.rst | 12 + docs/source/notebooks/ogb_biokg_demo.ipynb | 2269 +++++++++++++++++++ docs/source/user_guide.rst | 27 + pyproject.toml | 84 + requirements-dev.txt | 15 + requirements.txt | 3 + src/kg_topology_toolbox/__init__.py | 8 + src/kg_topology_toolbox/topology_toolbox.py | 588 +++++ src/kg_topology_toolbox/utils.py | 95 + tests/__init__.py | 1 + tests/test_edge_topology_toolbox.py | 84 + tests/test_node_topology_toolbox.py | 43 + tests/test_relation_topology_toolbox.py | 89 + 29 files changed, 4141 insertions(+) create mode 100644 .flake8 create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 NOTICE.md create mode 100644 README.md create mode 100755 dev create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/API_reference.rst create mode 100644 docs/source/_templates/class.rst create mode 100644 docs/source/_templates/module.rst create mode 100644 docs/source/_templates/package.rst create mode 100644 docs/source/_templates/toc.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/images/edge_patterns.png create mode 100644 docs/source/index.rst create mode 100644 docs/source/notebooks/ogb_biokg_demo.ipynb create mode 100644 docs/source/user_guide.rst create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt create mode 100644 requirements.txt create mode 100644 src/kg_topology_toolbox/__init__.py create mode 100644 src/kg_topology_toolbox/topology_toolbox.py create mode 100644 src/kg_topology_toolbox/utils.py create mode 100644 tests/__init__.py create mode 100644 tests/test_edge_topology_toolbox.py create mode 100644 tests/test_node_topology_toolbox.py create mode 100644 tests/test_relation_topology_toolbox.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..b7d7cb4 --- /dev/null +++ b/.flake8 @@ -0,0 +1,13 @@ +######################### +# Flake8 Configuration # +# (.flake8) # +######################### +[flake8] +ignore = + # line too long + E501 + # line break before binary operator + W503 + # whitespace before ':' + E203 +max-line-length = 90 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..487b761 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: + +concurrency: + # Run everything on main, most-recent on PR builds + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + ci: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install dev-requirements + run: | + sudo apt install pandoc + python -m pip install --upgrade pip + pip install -r requirements-dev.txt --no-cache-dir + shell: bash + - name: Run CI + run: ./dev ci + shell: bash + - name: Publish docs + if: ${{github.ref == 'refs/heads/main'}} + uses: Cecilapp/GitHub-Pages-deploy@3.2.1 + env: { GITHUB_TOKEN: "${{ github.token }}" } + with: + build_dir: docs/build/html/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59d7a62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,177 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# ONNX checkpoints +*.onnx + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Data +data/ +datasets/ +*.pkl +*.pt + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/build/ +docs/source/generated +docs/source/api + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# VSCode +.vscode/ + +#WandB +wandb/ + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv* +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..51ce057 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Graphcore Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..24bc565 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,90 @@ +Copyright (c) 2023 Graphcore Ltd. Licensed under the MIT License. + +The included code is released under an MIT license, (see [LICENSE](LICENSE)). + +## Dependencies + +Our dependencies are (see [requirements.txt](requirements.txt)): + +| Component | About | License | +| --- | --- | --- | +| numpy | Array processing library | BSD 3-Clause | +| pandas | Structured data analysis library | BSD 3-Clause | +| scipy | Mathematical routines library | BSD 3-Clause | + +We also use additional Python dependencies for development/testing/documentation (see [requirements-dev.txt](requirements-dev.txt)). + +The [tutorial notebook](docs/source/notebooks/ogb_biokg_demo.ipynb) make use of the [ogbl-biokg](https://ogb.stanford.edu/docs/linkprop/#ogbl-biokg) dataset, licensed under CC-0; + +## Derived work + +This directory includes derived work from the following: + +--- + +Sphinx: https://github.com/sphinx-doc/sphinx, licensed under: + +> Unless otherwise indicated, all code in the Sphinx project is licenced under the +> two clause BSD licence below. +> +> Copyright (c) 2007-2023 by the Sphinx team (see AUTHORS file). +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are +> met: +> +> * Redistributions of source code must retain the above copyright +> notice, this list of conditions and the following disclaimer. +> +> * Redistributions in binary form must reproduce the above copyright +> notice, this list of conditions and the following disclaimer in the +> documentation and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +> A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +> HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +> SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +> LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +> DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +> THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +this applies to: +- `docs/source/_templates/module.rst` (modified) +- `docs/source/_templates/package.rst` (modified) +- `docs/source/_templates/toc.rst` (modified) + +--- + +The Example: Basic Sphinx project for Read the Docs: https://github.com/readthedocs-examples/example-sphinx-basic, licensed under: + +> MIT License +> +> Copyright (c) 2022 Read the Docs Inc +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +this applies to: +- `docs/source/conf.py` (modified) +- `docs/make.bat` +- `docs/Makefile` \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..babb423 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# KG Topology Toolbox +![Continuous integration](https://github.com/graphcore-research/kg-topology-toolbox/actions/workflows/ci.yaml/badge.svg) + +A Python toolbox to compute topological metrics and statistics for Knowledge Graphs. + +Documentation can be found at https://curly-barnacle-lnejye6.pages.github.io/ + +For a walkthrough of the main functionalities, we provide an introductory [Jupyter notebook](docs/source/notebooks/ogb_biokg_demo.ipynb). + +## Usage + +Tested on Ubuntu 20.04, Python >=3.8 + +To install the `kg-topology-toolbox` library, run + +``` +pip install wheel +pip install git+ssh://git@github.com/graphcore-research/kg-topology-toolbox +``` + +4\. Import and use: +```python +from kg_topology_toolbox import KGTopologyToolbox +``` + +## License + +Copyright (c) 2023 Graphcore Ltd. Licensed under the MIT License. + +The included code is released under the MIT license (see [details of the license](LICENSE)). + +See [notices](NOTICE.md) for dependencies, credits, derived work and further details. diff --git a/dev b/dev new file mode 100755 index 0000000..763e4b9 --- /dev/null +++ b/dev @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. + +# Code derived from +# https://github.com/graphcore-research/poptorch-experimental-addons/blob/main/dev +# Copyright (c) 2023 Graphcore Ltd +# Licensed under the MIT License (credits @DouglasOrr) + +"""Dev task launcher.""" + +import argparse +import datetime +import os +import subprocess +import sys +from pathlib import Path +from typing import Any, Callable, Iterable, List, Optional, TypeVar + +# Utilities + + +def run(command: Iterable[Any], gdb: bool = False) -> None: + """Run a command, terminating on failure.""" + cmd = [str(arg) for arg in command if arg is not None] + if gdb: + cmd = ["gdb", "-ex", "catch throw", "-ex", "run", "--args"] + cmd + print("$ " + " ".join(cmd), file=sys.stderr) + environ = os.environ.copy() + environ["PYTHONPATH"] = f"{os.getcwd()}:{environ.get('PYTHONPATH', '')}" + exit_code = subprocess.call(cmd, env=environ) + if exit_code: + sys.exit(exit_code) + + +T = TypeVar("T") + + +def cli(*args: Any, **kwargs: Any) -> Callable[[T], T]: + """Declare a CLI command / arguments for that command.""" + + def wrap(func: T) -> T: + if not hasattr(func, "cli_args"): + setattr(func, "cli_args", []) + if args or kwargs: + getattr(func, "cli_args").append((args, kwargs)) + return func + + return wrap + + +# Commands + +PYTHON_ROOTS = ["src/kg_topology_toolbox", "tests", "dev"] + + +@cli("-k", "--filter") +@cli("--gdb", action="store_true") +def tests(filter: Optional[str], gdb: bool) -> None: + """run Python tests""" + run( + [ + "python", + "-m", + "pytest", + "tests", + None if filter else "--cov=kg_topology_toolbox", + *(["-k", filter] if filter else []), + ], + gdb=gdb, + ) + + +@cli() +def lint() -> None: + """run static analysis""" + run(["python", "-m", "flake8", *PYTHON_ROOTS]) + run(["python", "-m", "mypy", *PYTHON_ROOTS]) + + +@cli("--check", action="store_true") +def format(check: bool) -> None: + """autoformat all sources""" + run(["python", "-m", "black", "--check" if check else None, *PYTHON_ROOTS]) + run(["python", "-m", "isort", "--check" if check else None, *PYTHON_ROOTS]) + + +@cli() +def copyright() -> None: + """check for Graphcore copyright headers on relevant files""" + command = ( + "find " + " ".join(PYTHON_ROOTS) + " -type f -not -name *.pyc" + " | xargs grep -L 'Copyright (c) 202. Graphcore Ltd[.] All rights reserved[.]'" + ) + print(f"$ {command}", file=sys.stderr) + # Note: grep exit codes are not consistent between versions, so we don't use + # check=True + output = ( + subprocess.run( + command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + .stdout.decode() + .strip() + ) + if output: + print( + "Error - failed copyright header check in:\n " + + output.replace("\n", "\n "), + file=sys.stderr, + ) + print("Template(s):") + comment_prefixes = { + {".cpp": "//"}.get(Path(f).suffix, "#") for f in output.split("\n") + } + for prefix in comment_prefixes: + print( + f"{prefix} Copyright (c) {datetime.datetime.now().year}" + " Graphcore Ltd. All rights reserved.", + file=sys.stderr, + ) + sys.exit(1) + + +@cli() +def doc() -> None: + """generate Sphinx documentation""" + subprocess.call(["rm", "-r", "docs/build"]) + subprocess.call(["rm", "-r", "docs/source/api"]) + subprocess.call(["rm", "-r", "docs/source/generated"]) + run(["make", "clean", "-C", "docs/"]) + run(["make", "html", "-C", "docs/"]) + + +@cli("--skip", nargs="*", default=[], help="commands to skip") +def ci(skip: List[str] = []) -> None: + """run continuous integration tests & checks + doc build""" + if "lint" not in skip: + lint() + if "format" not in skip: + format(check=True) + if "copyright" not in skip: + copyright() + if "tests" not in skip: + tests(filter=None, gdb=False) + if "doc" not in skip: + doc() + + +# Script + + +def _main() -> None: + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.set_defaults(action=ci) + + subs = parser.add_subparsers() + for key, value in globals().items(): + if hasattr(value, "cli_args"): + sub = subs.add_parser(key.replace("_", "-"), help=value.__doc__) + for args, kwargs in value.cli_args: + sub.add_argument(*args, **kwargs) + sub.set_defaults(action=value) + + cli_args = vars(parser.parse_args()) + action = cli_args.pop("action") + action(**cli_args) + + +if __name__ == "__main__": + _main() diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..43eab3f --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,22 @@ +# Copyright (c) 2022 Read the Docs Inc. All rights reserved. + +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..c370eac --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +:: Copyright (c) 2022 Read the Docs Inc. All rights reserved. +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/API_reference.rst b/docs/source/API_reference.rst new file mode 100644 index 0000000..eb5d47d --- /dev/null +++ b/docs/source/API_reference.rst @@ -0,0 +1,11 @@ +API Reference +====================================== + +.. autosummary:: + :toctree: generated + :template: module.rst + :recursive: + + kg_topology_toolbox.topology_toolbox + kg_topology_toolbox.utils + \ No newline at end of file diff --git a/docs/source/_templates/class.rst b/docs/source/_templates/class.rst new file mode 100644 index 0000000..bcfcb17 --- /dev/null +++ b/docs/source/_templates/class.rst @@ -0,0 +1,11 @@ +.. + # Copyright (c) 2023 Graphcore Ltd. All rights reserved. + # Copyright (c) 2007-2023 by the Sphinx team. All rights reserved. + +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :inherited-members: Module \ No newline at end of file diff --git a/docs/source/_templates/module.rst b/docs/source/_templates/module.rst new file mode 100644 index 0000000..b3fda69 --- /dev/null +++ b/docs/source/_templates/module.rst @@ -0,0 +1,60 @@ +.. + # Copyright (c) 2023 Graphcore Ltd. All rights reserved. + # Copyright (c) 2007-2023 by the Sphinx team. All rights reserved. + +{{ fullname | escape | underline}} + +.. automodule:: {{ fullname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Module Attributes') }} + + .. autosummary:: + :toctree: + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + :toctree: + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + :toctree: + :template: class.rst + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :template: class.rst + :recursive: +{% for item in modules %} + {% if "test" not in item and "docs" not in item %} + {{ item }} + {% endif %} +{%- endfor %} +{% endif %} +{% endblock %} diff --git a/docs/source/_templates/package.rst b/docs/source/_templates/package.rst new file mode 100644 index 0000000..325848d --- /dev/null +++ b/docs/source/_templates/package.rst @@ -0,0 +1,61 @@ +.. + # Copyright (c) 2023 Graphcore Ltd. All rights reserved. + # Copyright (c) 2007-2023 by the Sphinx team. All rights reserved. + +{%- macro automodule(modname, options) -%} +.. automodule:: {{ modname }} +{%- for option in options %} + :{{ option }}: +{%- endfor %} +{%- endmacro %} + +{%- macro toctree(docnames) -%} +.. toctree:: + :maxdepth: {{ maxdepth }} +{% for docname in docnames %} + {{ docname }} +{%- endfor %} +{%- endmacro %} + +{%- if is_namespace %} +{{- [pkgname, "namespace"] | join(" ") | e | heading }} +{% else %} +{{- [pkgname, "package"] | join(" ") | e | heading }} +{% endif %} + +{%- if is_namespace %} +.. py:module:: {{ pkgname }} +{% endif %} + +{%- if modulefirst and not is_namespace %} +{{ automodule(pkgname, automodule_options) }} +{% endif %} + +{%- if subpackages %} +Subpackages +----------- + +{{ toctree(subpackages) }} +{% endif %} + +{%- if submodules %} +Submodules +---------- +{% if separatemodules %} +{{ toctree(submodules) }} +{% else %} +{%- for submodule in submodules %} +{% if show_headings %} +{{- submodule | e | heading(2) }} +{% endif %} +{{ automodule(submodule, automodule_options) }} +{% endfor %} +{%- endif %} +{%- endif %} + +{%- if not modulefirst and not is_namespace %} +Module contents +--------------- + +{{ automodule(pkgname, automodule_options) }} +{% endif %} diff --git a/docs/source/_templates/toc.rst b/docs/source/_templates/toc.rst new file mode 100644 index 0000000..39ab508 --- /dev/null +++ b/docs/source/_templates/toc.rst @@ -0,0 +1,12 @@ +.. + # Copyright (c) 2023 Graphcore Ltd. All rights reserved. + # Copyright (c) 2007-2023 by the Sphinx team. All rights reserved. + +{{ header | heading }} + +.. toctree:: + :maxdepth: {{ maxdepth }} +{% for docname in docnames %} + {{ docname }} +{%- endfor %} + diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..aae352e --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,67 @@ +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. +# Copyright (c) 2022 Read the Docs Inc. All rights reserved. + +import os +import sys + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) +sys.path.insert(0, os.path.abspath("../../src/")) + +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "KG Topology Toolbox" +copyright = "(c) 2023 Graphcore Ltd. All rights reserved" +author = "Alberto Cattaneo, Daniel Justus" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.autosummary", + "sphinx_autodoc_typehints", + "sphinx_automodapi.automodapi", + "sphinx_automodapi.smart_resolver", + "sphinx.ext.intersphinx", + "sphinx.ext.autosectionlabel", + "myst_parser", + "nbsphinx", +] +numpydoc_show_class_members = False +todo_include_todos = True +autosummary_generate = True +autoclass_content = "both" +autodoc_typehints = "both" +napoleon_google_docstring = True +napoleon_numpy_docstring = False + +templates_path = ["_templates"] +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] + + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable", None), + "pandas": ("http://pandas.pydata.org/pandas-docs/dev", None), + "scipy": ("https://docs.scipy.org/doc/scipy/", None), + "rtd": ("https://docs.readthedocs.io/en/stable/", None), + "sphinx": ("https://www.sphinx-doc.org/en/master/", None), +} diff --git a/docs/source/images/edge_patterns.png b/docs/source/images/edge_patterns.png new file mode 100644 index 0000000000000000000000000000000000000000..85b9dd90bd11594c7e3dc9be2285a24387a142ca GIT binary patch literal 32677 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 literal 0 HcmV?d00001 diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..480c493 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,12 @@ +KG Topology Toolbox +====================================== + +.. automodule:: kg_topology_toolbox + +.. toctree:: + :maxdepth: 3 + :caption: Contents + + User guide + notebooks/ogb_biokg_demo + API reference diff --git a/docs/source/notebooks/ogb_biokg_demo.ipynb b/docs/source/notebooks/ogb_biokg_demo.ipynb new file mode 100644 index 0000000..83729b7 --- /dev/null +++ b/docs/source/notebooks/ogb_biokg_demo.ipynb @@ -0,0 +1,2269 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# KGTopologyToolbox walk-through\n", + "\n", + "Copyright (c) 2024 Graphcore Ltd. All rights reserved.\n", + "\n", + "In this notebook we give a general overview of the classes and methods included in the `kg-topology-toolbox` library and explain how to use them to extract topological data from any knowledge graph. As an example, we use the open-source biomedical dataset [ogbl-biokg](https://ogb.stanford.edu/docs/linkprop/#ogbl-biokg).\n", + "\n", + "## Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found existing installation: kg-topology-toolbox 0.1.0\n", + "Uninstalling kg-topology-toolbox-0.1.0:\n", + " Successfully uninstalled kg-topology-toolbox-0.1.0\n" + ] + } + ], + "source": [ + "import sys\n", + "!{sys.executable} -m pip uninstall -y kg_topology_toolbox\n", + "!pip install -q git+ssh://git@github.com/graphcore-research/kg-topology-toolbox\n", + "!pip install -q jupyter ipywidgets ogb seaborn" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "import ogb.linkproppred\n", + "from kg_topology_toolbox import KGTopologyToolbox\n", + "\n", + "dataset_directory = \"../../../data/ogb-biokg/\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data preparation\n", + "\n", + "We load the OGBL-BioKG dataset using the `ogb.linkproppred.LinkPropPredDataset` class and store all (h, r, t) triples in a `pandas` DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
hrt
0171803207
14903013662
25480015999
3314807247
410300016202
............
50884292451505097
50884306456508833
508843194845015873
5088432636550496
508843313860506368
\n", + "

5088434 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " h r t\n", + "0 1718 0 3207\n", + "1 4903 0 13662\n", + "2 5480 0 15999\n", + "3 3148 0 7247\n", + "4 10300 0 16202\n", + "... ... .. ...\n", + "5088429 2451 50 5097\n", + "5088430 6456 50 8833\n", + "5088431 9484 50 15873\n", + "5088432 6365 50 496\n", + "5088433 13860 50 6368\n", + "\n", + "[5088434 rows x 3 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dataset = ogb.linkproppred.LinkPropPredDataset(\n", + " name=\"ogbl-biokg\", root=dataset_directory\n", + ")\n", + "\n", + "all_triples = []\n", + "for split in dataset.get_edge_split().values():\n", + " all_triples.append(np.stack([split[\"head\"], split[\"relation\"], split[\"tail\"]]).T)\n", + "biokg_df = pd.DataFrame(np.concatenate(all_triples), columns=[\"h\", \"r\", \"t\"])\n", + "biokg_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Based on this representation of the knowledge graph, we can proceed to compute its topological properties using the `KGTopologyToolbox` class." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "kgtt = KGTopologyToolbox()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Node-level analysis\n", + "\n", + "The method `node_degree_summary` provides a summary of the degrees of each individual node in the knowledge graph. The returned dataframe is indexed on the node ID.\n", + "\n", + "- `h_degree` is the number of edges coming out from the node;\n", + "- `t_degree` is the number of edges going into the node;\n", + "- `tot_degree` is the number of edges that use the node as either head or tail;\n", + "- `h_unique_rel` (resp. `t_unique_rel`) is the number of unique relation types come out from (resp. go into) the node;\n", + "- `n_loops` is the number of loop edges around the node." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
h_degreet_degreetot_degreeh_unique_relt_unique_reln_loops
0277299440
11494108360
220895303570
328999261545515310110
436230266411120
.....................
45080212243110
45081293261110
45082283058110
45083171936110
45084283159110
\n", + "

45085 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " h_degree t_degree tot_degree h_unique_rel t_unique_rel n_loops\n", + "0 27 72 99 4 4 0\n", + "1 14 94 108 3 6 0\n", + "2 208 95 303 5 7 0\n", + "3 28999 26154 55153 10 11 0\n", + "4 362 302 664 11 12 0\n", + "... ... ... ... ... ... ...\n", + "45080 21 22 43 1 1 0\n", + "45081 29 32 61 1 1 0\n", + "45082 28 30 58 1 1 0\n", + "45083 17 19 36 1 1 0\n", + "45084 28 31 59 1 1 0\n", + "\n", + "[45085 rows x 6 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "node_ds = kgtt.node_degree_summary(biokg_df)\n", + "node_ds" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/nethome/albertoc/research/knowledge_graphs/kg-topology-toolbox/.venv38/lib/python3.8/site-packages/pandas/core/arraylike.py:396: RuntimeWarning: divide by zero encountered in log2\n", + " result = getattr(ufunc, method)(*inputs, **kwargs)\n" + ] + }, + { + "data": { + "image/png": "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iYn2fV5eXkpMTFReXl6dj8nLy3MYL0lJSUn1jne1EydOSJK6dOly0XGnTp1Sz549FR4erpEjR2rnzp2tMb0G2bNnj8LCwtSrVy/dc889Kioqqnespx2fqqoq/eUvf9F9990nm81W7zh3Pj7/bf/+/SouLnY4DoGBgYqPj6/3ODTlb9HVTpw4IZvNpk6dOl10XGN+f1tbbm6uunfvrr59++qBBx7QsWPH6h3riccI1u9z9Dj3PDZW621W72v0M/c9NhIf12yS0tJSVVdXKzg42GF/cHCwiouL63xMcXFxo8a7Uk1NjR555BFdf/31uvbaa+sd17dvX2VlZem9997TX/7yF9XU1Gjo0KH65ptvWnG2dYuPj9eSJUuUnZ2tRYsWaf/+/brxxht18uTJOsd70vGRpNWrV+v48eOaOHFivWPc+fjU5cL/68Ych6b8LbrSmTNnNHXqVI0bN04BAQH1jmvs729rGj58uJYtW6acnBzNnTtX69ev14gRI1RdXV3neE87RjjPyn2OHneeOx4bq/U2K/c1+tn33O3YXNDG1ROA+3nooYe0Y8eOS35mOiEhQQkJCfbbQ4cO1dVXX60//elPmj17dktP86JGjBhh/3nAgAGKj49Xz5499cYbb+j+++934cyc489//rNGjBihsLCwese48/G5HJ09e1Z33323jDFatGjRRce68+/v2LFj7T/3799fAwYMUO/evZWbm6ubb77ZhTMDGoYe577obZ6BfuYZeCevCYKCguTt7a2SkhKH/SUlJQoJCanzMSEhIY0a7yopKSn6+9//rnXr1umKK65o1GPbtm2rgQMHau/evS00u6br1KmTrrrqqnrn5inHR5K+/vprffjhh/rVr37VqMe58/GRZP9/3Zjj0JS/RVe40BC//vprffDBBxd91bMul/r9daVevXopKCio3rl5yjGCI6v2OXrc99zt2Fixt1mxr9HP3PfY/DdCXhP4+Pho8ODBysnJse+rqalRTk6Ow6tLP5SQkOAwXpI++OCDese3NmOMUlJS9O677+pf//qXoqKiGl2jurpaX375pUJDQ1tghs1z6tQp7du3r965ufvx+aHXXntN3bt31+23396ox7nz8ZGkqKgohYSEOByHsrIybdy4sd7j0JS/xdZ2oSHu2bNHH374obp27droGpf6/XWlb775RseOHat3bp5wjFCb1focPc59j80FVuxtVutr9DP3PTZ1cu15XzzXypUrja+vr1myZInZtWuX+fWvf206depkiouLjTHG3HvvveaJJ56wj//0009NmzZtzPz5801+fr5JS0szbdu2NV9++aWrluDggQceMIGBgSY3N9ccPnzYvlVUVNjH/PeaZs2aZdauXWv27dtnNm/ebMaOHWv8/PzMzp07XbEEB//7v/9rcnNzzf79+82nn35qEhMTTVBQkDly5IgxxvOOzwXV1dUmIiLCTJ06tdZ9nnB8Tp48abZu3Wq2bt1qJJkFCxaYrVu32s/MNWfOHNOpUyfz3nvvme3bt5uRI0eaqKgoc/r0aXuNm266ybz88sv225f6W3TlmqqqqszPfvYzc8UVV5ht27Y5/G1VVlbWu6ZL/f66aj0nT540jz76qMnLyzP79+83H374oRk0aJDp06ePOXPmTL3rcfUxQtNYqc/R49z32Bjj2b3NSn2NfmatfkbIa4aXX37ZREREGB8fHxMXF2c+++wz+33Dhg0zEyZMcBj/xhtvmKuuusr4+PiYa665xqxZs6aVZ1w/SXVur732mn3Mf6/pkUcesa8/ODjY3HbbbWbLli2tP/k6jBkzxoSGhhofHx/To0cPM2bMGLN37177/Z52fC5Yu3atkWQKCgpq3ecJx2fdunV1/p5dmHdNTY2ZPn26CQ4ONr6+vubmm2+utdaePXuatLQ0h30X+1tsaRdb0/79++v921q3bl29a7rU76+r1lNRUWFuvfVW061bN9O2bVvTs2dPM3ny5FrNzd2OEZrOKn2OHue+x8YYz+5tVupr9DNr9TObMcY44x1BAAAAAIDr8Z08AAAAALAQQh4AAAAAWAghDwAAAAAshJAHAAAAABZCyAMAAAAACyHkAQAAAICFEPIAAAAAwEIIeQAAAABgIYQ8oAF+/OMf65FHHmn15z127Ji6d++uwsJCSVJubq5sNpuOHz/utOeYOXOmYmNjnVavpY0dO1YvvPCCq6cBAJbkLv3OWVy1nqaoqqpSZGSkPv/8c1dPBRZAyANc4J133tEtt9yibt26KSAgQAkJCVq7dm2tcc8++6xGjhypyMjI1p+km3rqqaf07LPP6sSJE66eCgDgEpra71riRU135+Pjo0cffVRTp0519VRgAYQ8wAU++ugj3XLLLXr//fe1efNm/eQnP9Edd9yhrVu3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/xwcHKywsDD95z//0T333FNnjb59++ovf/mLKisr7eGsrvX9t379+snX11dFRUUaNmxYnWMGDRqkt99+W5GRkWrTpv5/nnbs2KErrrhCQUFBl3xeAEDjubrf+fj4SDr/TldD9O7dW23bttXGjRvtn2r57rvv9NVXX9l7zsCBA1VdXa0jR47oxhtvrLNOQ9f03wYNGqTi4mK1adOm3sseDRo0SAUFBbryyisvWmvHjh32T7gATebqz4sCnuDCdxSMOf/dgX79+pmPP/7YbNu2zQwfPtxceeWVpqqqyhhjzCeffGK8vLzMCy+8YL766iuTmZlpunbtajp16mSvt3z5ctOmTRuzcOFCc/jwYft2/Phx+5jt27ebNm3amG+//da+r67vKGzdutVIMvv377/kOlauXGn8/PxMVlaWKSgoMDNmzDAdO3Z0+E7e4sWLjb+/v3nxxRdNQUGB2b59u8nKyjIvvPCCMcaYEydOmC5dupjk5GSza9cuk52dbaKjo40ks23btnrnaYwxv//9703Xrl3NkiVLzN69e83mzZvNSy+9ZJYsWWKMMebgwYOmW7du5he/+IXZtGmT2bt3r8nOzjYTJ040586ds9eZMGFCre9vAACaz1363TfffGNsNptZsmSJOXLkiDl58uQl5/6b3/zG9OzZ0+Tk5Jgvv/zS/OxnPzMdOnSwr8eY89+vi4yMNG+//bb5z3/+YzZu3Giee+458/e//73Ba0pLS3Pom8YYU1NTY2644QYTExNj1q5da/bv328+/fRT8+STT5p///vfxhhjsrOzTZs2bczMmTPNjh07zK5du8zrr79ufv/73zvU6tmzp1m2bNkl1wtcDCEPaIAfNr1vv/3W3HvvvSYwMND4+/ubpKQk89VXXzmMf+WVV0yPHj2Mv7+/GTVqlHnmmWdMSEiIQz1JtbYJEyY41ImLizOZmZn2280NecYY8+yzz5qgoCDToUMHM2HCBPP444/XalbLly83sbGxxsfHx3Tu3Nn8v//3/8w777xjv//TTz81AwYMMD4+Pmbw4MFmxYoVRpLZvXt3vfM05nwTzMjIMH379jVt27Y13bp1M0lJSWb9+vX2MV999ZW58847TadOnYy/v7+Jjo42jzzyiKmpqTHGGHP69GkTGBho8vLyGrReAEDDuUu/M8aYp59+2oSEhBibzVZrfF1Onjxpxo8fb9q1a2eCg4PNvHnzHNZjjDFVVVVmxowZJjIy0rRt29aEhoaaO++802zfvr3Ba6or5BljTFlZmXn44YdNWFiYadu2rQkPDzf33HOPKSoqso/Jzs42Q4cONf7+/iYgIMDExcWZV155xX7/hg0bTKdOnUxFRcUl1wtcjM0YY1r73UPgcjN58mTt3r270adEXrNmjR577DHt2LFDXl7ue8WT5cuXa9KkSTpx4oT9pCgtZdGiRXr33Xf1z3/+s0WfBwDQeFbsd01dU1OMGTNGMTExevLJJ1v8uWBtfCcPaAHz58/XLbfcovbt2+sf//iHli5dWut6dA1x++23a8+ePTp48KDCw8NbYKZNs2zZMvXq1Us9evTQF198oalTp+ruu+9u8YAnnT/pzMsvv9zizwMAuDQr9jtnramxqqqq1L9/f/3ud79r8eeC9fFOHtAC7r77buXm5urkyZPq1auXHn744VqXLmgJ11xzjb7++us67/vTn/5U78lUGmvevHn64x//qOLiYoWGhmrUqFF69tln7ad/BgBcHlqr3xUVFdlP+FKXXbt2XfIyQg3lqh4OOBMhD7CQr7/+WmfPnq3zvuDgYHXs2LGVZwQAQPOdO3dOhYWF9d5/qbMyA5cbQh4AAAAAWIh7fbMVAAAAANAshDwAAAAAsBBCHgAAAABYCCEPAAAAACyEkAcAAAAAFkLIAwAAAAALIeQBAAAAgIX8f4K0IXf0X61/AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "metrics = [\n", + " \"h_degree\",\n", + " \"t_degree\",\n", + "]\n", + "fig, ax = plt.subplots(1, len(metrics), figsize=(4.5 * len(metrics), 4))\n", + "\n", + "for i, metric in enumerate(metrics):\n", + " x = np.log2(node_ds[metric])\n", + " sns.histplot(\n", + " x=x, stat=\"probability\", binwidth=1, binrange=[0, x.max() + 1], ax=ax[i]\n", + " )\n", + " ax[i].set_xlabel(f\"log2({metric})\")\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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6emqrKxUZGRkq/5lZWWaNWuWLr/8coWEhGjlypWaOnWq9uzZo7i4OFe/adOm6fHHH3d9Dg4O9sn5AAB6ny7dyXv11Ve9FkBGRoZWr16tvLw8JSUlaffu3SotLXUtxlJVVaUjR4547fsAAPgmb+a1M18NNG7cOBUXF2vAgAEqKSnx2P+ZZ57R3LlzlZSUpISEBD322GOuWS1nCg4OVnR0tGsbOnSo12IGABhLl+7kTZs2Teeff76ysrKUmZl5zs+45eTkKCcnx+O+srKydo9dv379OX03AADeymtdfTXQmU6cOKFTp05p2LBhbu1lZWWKjIzU0KFDddVVV2n58uU677zzPI7BytIA0Ld16U7ep59+qpycHD377LMaNWqU0tPT9cc//lFNTU3ejg8AgG7nrbzW3quB7HZ7h8b4xS9+odjYWFksFlfbtGnT9OSTT8pms2nlypXatm2brr32WjU3N3scg5WlAaBv61KRFxERoZ/97GfavXu3duzYoQsuuEBz585VbGysfvrTn+rtt9/2dpwAAHSbnpLXVqxYoY0bN+r5559XSEiIq33mzJn6wQ9+oPHjx2v69Ol64YUX9Oabb7Y52yU3N1d1dXWu7dChQz6JHwDQM5zz6pqXXnqpcnNzlZOTo+PHj6ukpETJycmaNGmS9uzZ440YAQDwmXPJa519NdCZVq9erRUrVuill15SYmJiu31HjRqliIgI7du3z+N+VpYGgL6ty0XeqVOn9Oyzz+r73/++RowYoa1bt+qRRx6Rw+HQvn37NGLECN1yyy3ejBUAgG7jjbzW2VcDnbZq1SotW7ZMpaWlSklJOWusn3zyiT777DPFxMR0/AQBAH1GlxZeueuuu/SHP/xBTqdTt912m1atWuX2CoOBAwdq9erVio2N9VqgAAB0F2/mtc68GkiSVq5cqby8PG3YsEFms9n17F5YWJjCwsJ0/PhxLVmyRDNmzFB0dLT279+vhQsXasyYMUpPT++GqwEA6O26VOS9//77+s1vfqObbrqpzff0REREeHVJagAAuos381pGRoaOHj2qvLw82e12JSUltXo1UEDA1xNp1q1bp6amJt18881u4+Tn52vx4sUKDAzUO++8oyeeeEK1tbWKjY3V1KlTtWzZMt6VBwDwqEtFXn5+vi6//HL16+d++JdffqnXX39dV155pfr166fJkyd7JUgA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p4KCAknS/PnzNXnyZP3qV7/Sddddp40bN+qtt97S7373O3+eBgAAkryf10wmkxYsWKDly5dr7NixGjlypB544AHFxsa6CkkAAM7k9yIvIyNDR48eVV5enux2u5KSklRaWup6wLyqqkoBAV+/zu/yyy/Xhg0btGjRIt13330aO3asNm/erIsvvthfpwAAgEt35LWFCxeqoaFBt99+u2pra3XFFVeotLRUISEhPj8/AEDP5/ciT5JycnLanMZSVlbWqu2WW27RLbfc0s1RtS04OFj5+fmtpsL0ZVyT1rgmrXFNWuOatGaEa+LtvGYymbR06VItXbrUWyF2mhH+vXgb16Q1rklrXJPWuCatefuamJzOHrT2NAAAAADgnAScvQsAAAAAoLegyAMAAAAAA6HIAwAAAAADocjrgqKiIpnNZoWEhCg1NVU7d+70d0g+849//EPXX3+9YmNjZTKZtHnzZrf9TqdTeXl5iomJUWhoqCwWiz766CP/BOsDBQUFuuyyyzRo0CBFRkZq+vTpqqysdOtz8uRJzZs3T+edd57CwsI0Y8aMVi8+NpJ169YpMTHR9Z6XtLQ0/e1vf3Pt72vXw5MVK1a4lsU/ra9dl8WLF8tkMrltCQkJrv197Xr0BuQ+ct9p5L7WyH1nR+7zbe6jyOukTZs2yWq1Kj8/X7t27dKECROUnp6u6upqf4fmEw0NDZowYYKKioo87l+1apXWrl2r4uJi7dixQwMHDlR6erpOnjzp40h9Y9u2bZo3b57eeOMNvfzyyzp16pSmTp2qhoYGV5+f/exn+utf/6o//elP2rZtmw4fPqybbrrJj1F3r/PPP18rVqxQRUWF3nrrLV111VW64YYbtGfPHkl973p805tvvqn//d//VWJiolt7X7wu3/72t3XkyBHXtn37dte+vng9ejJyH7nvTOS+1sh97SP3fc1nuc+JTpk4caJz3rx5rs/Nzc3O2NhYZ0FBgR+j8g9Jzueff971uaWlxRkdHe186KGHXG21tbXO4OBg5x/+8Ac/ROh71dXVTknObdu2OZ3Or86/f//+zj/96U+uPh988IFTkrO8vNxfYfrc0KFDnY899lifvx7Hjh1zjh071vnyyy87J0+e7Jw/f77T6eybf07y8/OdEyZM8LivL16Pno7c9zVyX2vkPs/IfV8h933Nl7mPO3md0NTUpIqKClks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nnMl1nMdEwDQczz88MNKS0tTdna2jhw5oiNHjig+Pv6cx92/f782b96sF154QS+88IK2bdumFStWtNl/zpw5OnTokF599VU9++yz+u1vf6vq6upOfWdtba2uuuoqXXLJJXrrrbdUWloqh8OhH/7whx0e44knnlBQUJBee+01FRcXe2VMwJuYrgl0UmJiovLz8yVJY8eO1SOPPCKbzaZrrrnGK+M/+OCDmjx5siTp3nvv1XXXXaeTJ0+67pCdqbCwULm5ubrpppskScXFxdq6dWunvm/79u3auXOnqqurFRwcLElavXq1Nm/erGeffVa33377WcdoamrSk08+qeHDh3ttTABAzxEeHq6goCANGDBA0dHRXhu3paVF69ev16BBgyRJt912m2w2mx588MFWfT/88EP97W9/086dO3XZZZdJkn7/+9/roosu6tR3PvLII7rkkkv0y1/+0tVWUlKi+Ph4ffjhh7rgggvOOsbYsWO1atUq1+fly5ef85iAN1HkAZ2UmJjo9jkmJqbTv0Xs6PgxMTGSvpqe8q1vfcutX11dnY4cOaLU1FRXW79+/ZSSkiKn09nh73v77bd1/PhxnXfeeW7tX3zxhfbv39+hMUaMGOEq8Lw1JgDA+Mxms6vAk9rPqR988IH69eun5ORkV1tCQkKraaFn8/bbb+vVV1/1+Iz7/v37O1SQnRmDt8YEvIkiD+ikb67yZTKZ1NLS0u4xAQFfzYw+s/g6derUWcc//ezC2cY/23d/s+g787uPHz+umJgYt2cVTuto4hw4cKDbZ2+MCQDovc6We07rSk492/dK7efb48eP6/rrr9fKlStbHX/6l6tn4ynvneuYgDdR5AE+cPou15EjR3TJJZdIkttD4V0RHh6umJgY7dixQ1deeaUk6csvv3Q9/3bmdx85csT1ub6+XgcOHHB9vvTSS2W329WvXz+ZzeZziqk7xwQA+FdQUJCam5s71Hf48OE6duyYGhoaXAXRuea9hIQEV547PV2zsrJStbW1bt8rtZ9vL730Uv35z3+W2WxWv37e+V/h7hgTOBcsvAL4QGhoqL7zne9oxYoV+uCDD7Rt2zYtWrTonMedP3++VqxYoc2bN2vv3r2aO3euW7KTpKuuukpPPfWU/vnPf+rdd99VZmamAgMDXfstFovS0tI0ffp0vfTSSzp48KBef/113X///Xrrrbe6FFd3jAkA8C+z2awdO3bo4MGDqqmpafeOW2pqqgYMGKD77rtP+/fv14YNG7R+/fpz+v4LL7xQ06ZN0//7f/9PO3bsUEVFhX7yk58oNDTU1acj+XbevHn6/PPPNWvWLL355pvav3+/tm7dqqysrA4Xsd/UHWMC54IiD/CRkpISffnll0pOTtaCBQu0fPnycx7z7rvv1m233abMzEylpaVp0KBBuvHGG9365ObmavLkyfqv//ovXXfddZo+fbpGjx7t2m8ymbRlyxZdeeWVysrK0gUXXKCZM2fq448/VlRUVJfi6o4xAQD+dc899ygwMFDjxo3T8OHDVVVV1WbfYcOG6emnn9aWLVs0fvx4/eEPf+jQK4fO5vHHH1dsbKwmT56sm266SbfffrsiIyPd+pwt38bGxuq1115Tc3Ozpk6dqvHjx2vBggUaMmSIa7pnZ3XHmMC5MDk7s0IDgB5vzpw5qq2t1ebNm/0dCgAA3c5sNmvBggVu79MD+jp+tQAAAAAABkKRB3jBM888o7CwMI/bt7/9bX+Hd06+/e1vt3luzzzzjL/DAwD4wR133NFmbrjjjjv8HV6XVVVVtXleYWFh7U5RBXoSpmsCXnDs2DE5HA6P+/r3768RI0b4OCLv+fjjj9t83UNUVJTb+40AAH1DdXW16uvrPe4bPHhwq+fkeosvv/xSBw8ebHM/q2eit6DIAwAAAAADYbomAAAAABgIRR4AAAAAGAhFHgAAAAAYCEUeAAAAABgIRR4AAAAAGAhFHgAAAAAYCEUeAAAAABgIRR4AAAAAGMj/B1S3y012LQY9AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "metrics = [\n", + " \"h_unique_rel\",\n", + " \"t_unique_rel\",\n", + "]\n", + "fig, ax = plt.subplots(1, len(metrics), figsize=(4.5 * len(metrics), 4))\n", + "\n", + "for i, metric in enumerate(metrics):\n", + " x = node_ds[metric]\n", + " sns.histplot(\n", + " x=x, stat=\"probability\", binwidth=1, binrange=[0, x.max() + 1], ax=ax[i]\n", + " )\n", + " ax[i].set_xlabel(f\"{metric}\")\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Edge-level analysis\n", + "\n", + "### Edge degrees and cardinality\n", + "\n", + "The method `edge_degree_cardinality_summary` provides, for each edge (h, r, t) in the KG, detailed information on the connectivity patterns of the head and tail nodes:\n", + "\n", + "- `h_unique_rel` (resp. `t_unique_rel`) is the number of unique relation types coming out of the head node (resp. going into the tail node);\n", + "- `h_degree` is the out-degree of the head node and `h_degree_same_rel` is the degree when only considering edges of the same relation type `r`;\n", + "- `t_degree` is the in-degree of the tail node and `t_degree_same_rel` is the degree when only considering edges of the same relation type `r`;\n", + "- `tot_degree` is the total number of edges with either head entity `h` or tail entity `t` (in particular, `tot_degree <= h_degree + t_degree`); `tot_degree_same_rel` is computed only considering edges of the same relation type `r`;\n", + "- `triple_cardinality` is the cardinality type of the edge:\n", + " - _one-to-one_ (1:1) if `h_degree = 1`, `t_degree = 1`;\n", + " - _one-to-many_ (1:M) if `h_degree > 1`, `t_degree = 1`;\n", + " - _many-to-one_ (M:1) if `h_degree = 1`, `t_degree > 1`;\n", + " - _many-to-many_ (M:M) if `h_degree > 1`, `t_degree > 1`.\n", + "- `triple_cardinality_same_rel` is defined as `triple_cardinality` but using `h_degree_same_rel`, `t_degree_same_rel`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
hrth_unique_relh_degreeh_degree_same_relt_unique_relt_degreet_degree_same_reltot_degreetot_degree_same_reltriple_cardinalitytriple_cardinality_same_rel
0171803207519111664614236129M:MM:M
149030136628544339197550251882M:MM:M
25480015999310854722217926M:MM:M
331480724741109911673271782369M:MM:M
4103000162024414315614831561345M:MM:M
..........................................
508842924515050975636272108032721437543M:MM:M
5088430645650883310743259103711001111358M:MM:M
508843194845015873865221364861631135375M:MM:M
50884326365504969922277196181731537449M:MM:M
50884331386050636874851758455147939321M:MM:M
\n", + "

5088434 rows × 13 columns

\n", + "
" + ], + "text/plain": [ + " h r t h_unique_rel h_degree h_degree_same_rel \\\n", + "0 1718 0 3207 5 191 116 \n", + "1 4903 0 13662 8 544 33 \n", + "2 5480 0 15999 3 108 5 \n", + "3 3148 0 7247 4 110 99 \n", + "4 10300 0 16202 4 414 315 \n", + "... ... .. ... ... ... ... \n", + "5088429 2451 50 5097 5 636 272 \n", + "5088430 6456 50 8833 10 743 259 \n", + "5088431 9484 50 15873 8 652 213 \n", + "5088432 6365 50 496 9 922 277 \n", + "5088433 13860 50 6368 7 485 175 \n", + "\n", + " t_unique_rel t_degree t_degree_same_rel tot_degree \\\n", + "0 6 46 14 236 \n", + "1 9 1975 50 2518 \n", + "2 4 72 22 179 \n", + "3 11 673 271 782 \n", + "4 6 148 31 561 \n", + "... ... ... ... ... \n", + "5088429 10 803 272 1437 \n", + "5088430 10 371 100 1111 \n", + "5088431 6 486 163 1135 \n", + "5088432 19 618 173 1537 \n", + "5088433 8 455 147 939 \n", + "\n", + " tot_degree_same_rel triple_cardinality triple_cardinality_same_rel \n", + "0 129 M:M M:M \n", + "1 82 M:M M:M \n", + "2 26 M:M M:M \n", + "3 369 M:M M:M \n", + "4 345 M:M M:M \n", + "... ... ... ... \n", + "5088429 543 M:M M:M \n", + "5088430 358 M:M M:M \n", + "5088431 375 M:M M:M \n", + "5088432 449 M:M M:M \n", + "5088433 321 M:M M:M \n", + "\n", + "[5088434 rows x 13 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "edge_dcs = kgtt.edge_degree_cardinality_summary(biokg_df)\n", + "edge_dcs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data on the distribution of degrees and cardinalities can be then easily visualized." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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jQ0bNmDu3LlYsWIF1q9fjzJlyqBatWoYPXq00bDsL774AhUqVMCqVauwYcMGPP300/jvf/9b4LlrPz8/rF+/HtHR0XjnnXcQGhqKuLg4nDlzxqhTDQATJkxAzZo1MXfuXEybNg2A/jnuTp06oXv37sU+HycnJ/z000+YPn06Vq9eje+++w5+fn5o06ZNgZnWXV1d0adPH3z22WecoIyIiBRLJcyZPYSIiKiUaNSoESpUqICtW7fauilmU6lUiI2NxdSpU23dlGIbO3Ysli5dimvXrqFMmTK2bg4REVEBfKaaiIgIQE5ODnJzc4327dy5E0ePHkX79u1t06hSLisrCytXrkSvXr3YoSYiIsXi8G8iIiIAqampiIiIwMCBA1GxYkWcPHkS8fHxCAwMxGuvvWbr5pUq169fx7Zt2/Dtt9/i5s2bGD16tK2bREREVCR2qomIiKBfZqpp06b4v//7P/z999/w8PBA165d8eGHH8LPz8/WzStV/vzzTwwYMAD+/v749NNP0ahRI1s3iYiIqEh8ppqIiIiIiIjITHymmoiIiIiIiMhM7FQTERERERERmYmdaiIiIiIiIiIzsVNNREREREREZCZ2qomIiIiIiIjMxE41ERERERERkZnYqSYiIiIiIiIyEzvVRERERERERGZip5qIiIiIiIjITOxUExEREREREZmJnWoiIiIiIiIiM7FTTURERERERGQmdqqJiIiIiIiIzMRONREREREREZGZ2KkmIiIiIiIiMhM71URERERERERmYqeaiIiIiIiIyEzsVBMRERERERGZiZ1qIiIiIiIiIjOxU01ERERERERkJnaqiYiIiIiIiMzETjUREZV6CxcuREhICNzc3NCyZUscOHDgkcevW7cOtWvXhpubG+rXr4+ffvrJ6PWhQ4dCpVIZbZ07d7bmKRARETk8peY1O9VERFSqff3114iOjkZsbCwSExPRsGFDREZG4vr164Uev2/fPvTr1w8jRozAkSNHEBUVhaioKJw4ccLouM6dO+Pq1auGbc2aNSVxOkRERA5JyXmtEkIIs86KiIjIAbRs2RLNmzfHggULAAA6nQ5VqlTBqFGjMGHChALH9+nTB5mZmfjxxx8N+5588kk0atQI8fHxAPRXvu/cuYMNGzaUyDkQERE5OiXnNe9UExGRQ9Fqtbh7967RptVqCz02Ozsbhw8f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r4v1BDYREZEDmj9/vggODhaurq6iRYsW4rfffjO81q5dOzFkyBCj47/55htRs2ZN4erqKp544gnx3//+1+h1nU4nJk+eLAICAoRGoxEdO3YUp06dKolTISIiclhKzWuuU01ERERERERkJj5TTURERERERGQmdqqJiIiIiIiIzMRONREREREREZGZ2KkmIiIiIiIiMhM71VQA564jIiKyP8xvIiLbYKeaCtBoNPjrr79s3QwiKdLS0nDt2jUptc6cOYOEhAQkJydLqUdEJBPzm0ozmXkPMPOpeNiptrFLly5h+PDhJh//4MED7NmzB3/++WeB17KysrBixQqTa0VHRxe65eXl4cMPPzR8XRwLFizA4MGDsXbtWgDAV199hbp166J27dqYOHEicnNzi1XPHlWrVg1nzpyxdTMKyM3NxdatW7F06VJs27YNeXl5tm5SkZYvX4709PRivefWrVt44YUXEBwcjNdffx15eXl46aWXEBQUhEqVKqFVq1a4evWqyfXi4uKQkJAAALh9+zYiIiJQq1YtPPPMM6hVqxa6dOmCO3fuFKuNJcGcn93j8O4XkV5xMzufzOwGrJPf+ZjjxpSa6Q+zp3zPZ0lWyc57wH4z/2HWyH8qhmKvbE1SJSUlCbVabdKxp06dElWrVhUqlUqo1WrRtm1bceXKFcPr165dM7mWEEKoVCrRqFEj0b59e6NNpVKJ5s2bi/bt24sOHTqYXO/9998XXl5eolevXiIwMFB8+OGHws/PT3zwwQdixowZokKFCmLKlCkm1xNCiEuXLom///7b8PWvv/4q+vfvL9q0aSMGDBgg9u3bV6x6QgiRl5dX5P6LFy+aXOeTTz4pdHNychIxMTGGr4srLS1NJCQkiDt37ggh9H+uM2fOFHFxceLYsWMm13njjTfEpk2bhBD6n2Pt2rWFk5OTCAgIEE5OTqJ+/fri8uXLxW5fSXBxcRF//vlnsd4zfPhwUa9ePTF//nzRrl070aNHD9GgQQOxZ88esW/fPtG8eXMxePBgk+tVrlxZJCYmCiGEeOmll0Tjxo1FYmKiePDggUhKShJPPvmkGDFihMn1srOzxbhx40T16tVF8+bNxdKlS41eL+6/36KY87MTQoisrCzx1ltviaeeekp8+OGHQgj9v2kPDw/h4eEh+vXrJ9LT0y1uH5E9K05m55Od3ULIz+989pLj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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Edge frequency when binning by head and tail degree\n", + "\n", + "metrics = [(\"h_degree\", \"t_degree\"), (\"h_degree_same_rel\", \"t_degree_same_rel\")]\n", + "fig, ax = plt.subplots(1, len(metrics), figsize=[5 * len(metrics), 4.5])\n", + "\n", + "for i, (group_metric_1, group_metric_2) in enumerate(metrics):\n", + " df_empty = pd.DataFrame(\n", + " columns=np.int32(2 ** np.arange(15)), index=np.int32(2 ** np.arange(15))\n", + " )\n", + " df_tmp = edge_dcs[[group_metric_1, group_metric_2]]\n", + " df_tmp.insert(\n", + " 0,\n", + " f\"log_{group_metric_1}\",\n", + " np.int32(2 ** np.floor(np.log2(df_tmp[group_metric_1]))),\n", + " )\n", + " df_tmp.insert(\n", + " 0,\n", + " f\"log_{group_metric_2}\",\n", + " np.int32(2 ** np.floor(np.log2(df_tmp[group_metric_2]))),\n", + " )\n", + " df_tmp = (\n", + " df_tmp.groupby([f\"log_{group_metric_1}\", f\"log_{group_metric_2}\"])\n", + " .count()\n", + " .reset_index()\n", + " )\n", + " df_tmp[group_metric_1] /= df_tmp[group_metric_1].sum()\n", + " sns.heatmap(\n", + " df_tmp.reset_index()\n", + " .pivot(\n", + " columns=f\"log_{group_metric_2}\",\n", + " index=f\"log_{group_metric_1}\",\n", + " values=group_metric_1,\n", + " )\n", + " .combine_first(df_empty),\n", + " annot=False,\n", + " vmin=0,\n", + " vmax=0.05,\n", + " ax=ax[i],\n", + " )\n", + " ax[i].set_xlabel(group_metric_2)\n", + " ax[i].set_ylabel(group_metric_1)\n", + " ax[i].invert_yaxis()\n", + "fig.suptitle(\"Edge frequency\")\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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ePFiPfDAA64uBwBwHeAaCwDwQAsWLJCfn5/+9Kc/uboUAMB1gmssAMCDrFq1Sj/88IPmz5+vhIQE7lAEAHAaToUCAA8SFhamzMxMRUdH65133inVt5gDAFASggUAAAAA07jGAgAAAIBpBAsAAAAAphEsAAAAAJhGsAAAAABgGsECAAAAgGkECwAAAACmESwAAAAAmEawAAAAAGAawQIAAACAaQQLAAAAAKYRLAAAAACYRrAAAAAAYBrBAgAAAIBpBAsAAAAAphEsAAAAAJhGsAAAAABgGsECAAAAgGkECwAAAACmESwAAAAAmObj6gIqWkFBgY4cOaKaNWvKYrG4uhwAqBQMw9DZs2cVHBwsLy/P/cyJHgEA9krTHzwuWBw5ckQhISGuLgMAKqVDhw7phhtucHUZLkOPAICiOdIfPC5Y1KxZU9LljePv7+/iagCgcsjOzlZISIhtH+mp6BEAYK80/cHjgsWVQ9v+/v40DQD4HU8//YceAQBFc6Q/eO6JtAAAAACchmABAAAAwDSCBQAAAADTCBYAAAAATCNYAAAAADCNYAEAqJQ2bdqkmJgYBQcHy2KxaOXKldecZ+PGjbrppptktVrVvHlzpaSklHudAIDLCBYAgEopJydHHTp00Jw5cxyaPj09XX379tUf/vAH7dy5U4899phGjhypzz77rJwrBQBILg4WfBoFAChO79699be//U0DBw50aPp58+apSZMmmjZtmlq3bq2EhATdc889mjFjRjlXCgCQXBws+DQKAOAs27ZtU69evezGoqOjtW3btmLnyc3NVXZ2tt0PAKBsXPrN271791bv3r0dnv7qT6MkqXXr1tq8ebNmzJih6Ojo8ioTAOAGjh07pqCgILuxoKAgZWdn68KFC6pWrVqheZKTkzVlypSKKhEArmtudY0Fn0YBAJxp4sSJysrKsv0cOnTI1SUBgNty6RGL0uLTKABlEfHEYleX4HJpU2NdXUK5a9CggTIzM+3GMjMz5e/vX2R/kCSr1Sqr1VoR5QGopOgRzusRbnXEoiz4NAoAPENUVJRSU1PtxtatW6eoqCgXVQQAnsWtjljwaRQAeI5z585p7969tsfp6enauXOn6tSpo8aNG2vixIk6fPiwFi++/GnjmDFjNHv2bD355JOKj4/X559/rvfff1+rV6921UsAAI/iVkcs+DQKADzH9u3b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9Prrr8tischisSglJUUWi0X//ve/FRERIavVqs2bNxd7iHjKlCmqV6+e/P39NWbMGOXl5RW7vtzcXD3++ONq1KiRatSoocjISG3cuNHherds2aKePXuqevXqql27tqKjo/Xrr79KktasWaNbb71VtWrVUt26ddWvXz/t27fPNu+BAwdksVi0bNky9ejRQ1WrVtW7776r/Px8JSYm2uZ78skn7T6hkgofcg8LC9NLL72k+Ph41axZU40bN9b8+fPt5nnqqafUsmVLVa9eXU2bNtVzzz2nS5cuFfvarj7kXtTvJT09Xc2bN9drr71mN9/OnTtlsVi0d+9eh7cjgMqL/bLn7JcNw9DkyZPVuHFjWa1WBQcH69FHH7U9/84776hz586qWbOmGjRooPvuu0/Hjx+3PX/l9KLPPvtMnTp1UrVq1fTHP/5Rx48f17///W+1bt1a/v7+uu+++3T+/HnbfAUFBUpOTlaTJk1UrVo1dejQQR988EGJtf5+nb9/L5pZJkqPYIFK6fXXX1dUVJRGjRqlo0eP6ujRowoJCZEkTZgwQS+//LJ2796t9u3bFzl/amqqdu/erY0bN+q9997TihUrNGXKlGLXl5CQoG3btmnp0qX673//q0GDBumuu+7Szz//fM1ad+7cqdtvv13h4eHatm2bNm/erJiYGOXn50uScnJylJiYqO3btys1NVVeXl4aOHCgCgoK7JYzYcIEjRs3Trt371Z0dLSmTZumlJQULVy4UJs3b9bp06f10UcfXbOeadOmqXPnzvr222/18MMP66GHHtKePXtsz9esWVMpKSn64Ycf9Prrr2vBggWaMWPGNZcrFf17ady4seLj47Vo0SK7aRctWqTbbrtNzZs3d2jZACo39sues1/+8MMPNWPGDL3xxhv6+eeftXLlSrVr1872/KVLl/TCCy/ou+++08qVK3XgwAENHz680HImT56s2bNna+vWrTp06JDuvfdezZw5U0uWLNHq1au1du1azZo1yzZ9cnKyFi9erHnz5ul///ufxo8fr6FDh+qLL75waFtIhd+LzlgmSsEAKqkePXoY48aNsz3esGGDIclYuXKl3XRJSUlGhw4dbI+HDRtm1KlTx8jJybGNzZ071/Dz8zPy8/MLLfvgwYOGt7e3cfjwYbvl3n777cbEiROvWeeQIUOMbt26Ofy6Tpw4YUgyvv/+e8MwDCM9Pd2QZMycOdNuuoYNGxqvvvqq7fGlS5eMG264wbj77rttY7/fRqGhocbQoUNtjwsKCoz69esbc+fOLbaeqVOnGhEREbbHRW3PktZpGIZx+PBhw9vb2/jqq68MwzCMvLw8IzAw0EhJSSl2vQDcD/tlz9gvT5s2zWjZsqWRl5d3zWkNwzC++eYbQ5Jx9uxZwzD+732xfv162zTJycmGJGPfvn22sdGjRxvR0dGGYRjGxYsXjerVqxtbt261W/aIESOMIUOGXLOGot6Ljizzyny//vqrQ68VJeOIBdxO586drzlNhw4dVL16ddvjqKgonTt3TocOHSo07ffff6/8/Hy1bNnSdl6wn5+fvvjiC7tD48W58slYcX7++WcNGTJETZs2lb+/v8LCwiRJGRkZxb6urKwsHT16VJGRkbYxHx8fh1771Z8WWiwWNWjQwO4Q9bJly9StWzc1aNBAfn5+evbZZwvVUlrBwcHq27evFi5cKElatWqVcnNzNWjQIFPLBeAe2C+XzN32y4MGDdKFCxfUtGlTjRo1Sh999JF+++032/NpaWmKiYlR48aNVbNmTfXo0UNS4e139esOCgqynep19diV7bB3716dP39ed9xxh93vfPHixQ79zq+4+vfhrGXCcVzRArdTo0YNpy7v3Llz8vb2Vlpamry9ve2ec+Tiw2rVqpX4fExMjEJDQ7VgwQIFBweroKBAbdu2LXRusbNe1+8vLrRYLLbD+9u2bdP999+vKVOmKDo6WgEBAVq6dKmmTZtmer0jR47UAw88oBkzZmjRokUaPHiw3R8RAK5f7JdL5m775ZCQEO3Zs0fr16/XunXr9PDDD2vq1Kn64osvlJeXp+joaEVHR+vdd99VvXr1lJGRoejo6ELb7+rXbbFYStwO586dkyStXr1ajRo1spvOarU6/Jqv/p05a5lwHMEClZavr6/tfNjS+u6773ThwgVbc/nyyy/l5+dnOx/4ap06dVJ+fr6OHz+u7t27l3pd7du3V2pqapHnCp86dUp79uzRggULbMvevHnzNZcZEBCghg0b6quvvtJtt90mSfrtt9+Ulpamm266qdQ1XrF161aFhobqmWeesY0dPHiwVMso7vfSp08f1ahRQ3PnztWaNWu0adOmMtcJoHJiv+w5++Vq1aopJiZGMTExGjt2rFq1aqXvv/9ehmHo1KlTevnll22/u+3bt5eq3qKEh4fLarUqIyPDdgSkMi4TJSNYoNIKCwvTV199pQMHDsjPz6/QRXUlycvL04gRI/Tss8/qwIEDSkpKUkJCgry8Cp/917JlS91///2KjY3VtGnT1KlTJ504cUKpqalq3769+vbtW+K6Jk6cqHbt2unhhx/WmDFj5Ovrqw0bNmjQoEGqU6eO6tatq/nz56thw4bKyMjQhAkTHHoN48aN08svv6wWLVqoVatWmj59uukv8GnRooUyMjK0dOlS3XzzzVq9erVDFx5e7fe/lzp16sjLy0ve3t4aPny4Jk6cqBYtWigqKspUrQAqH/bLnrFfTklJUX5+viIjI1W9enX985//VLVq1RQaGqqCggL5+vpq1qxZGjNmjHbt2qUXXnihLC/dTs2aNfX4449r/PjxKigo0K233qqsrCxt2bJF/v7+GjZsWKVYJkrGNRaotB5//HF5e3srPDzcdqjVUbfffrtatGih2267TYMHD1b//v01efLkYqdftGiRYmNj9de//lU33nijBgwYoG+++UaNGze+5rpatmyptWvX6rvvvlOXLl0UFRWljz/+WD4+PvLy8tLSpUuVlpamtm3bavz48Zo6dapDr+Gvf/2rHnjgAQ0bNkxRUVGqWbOmBg4c6OgmKFL//v01fvx4JSQkqGPHjtq6dauee+65Ui2jpN/LiBEjlJeXp7i4OFN1Aqic2C97xn65Vq1aWrBggbp166b27dtr/fr1WrVqlerWrat69eopJSVFy5cvV3h4uF5++eVCt7UtqxdeeEHPPfeckpOT1bp1a911111avXq1mjRpUqmWieJZDON3N2AG3Nzw4cN15swZrVy50tWleJz//Oc/uv3223Xo0CEFBQW5uhwAlQT7Zddhv4yKxKlQAEzLzc3ViRMnNHnyZA0aNIjmBQAuxn4ZrsCpUMA19O7d2+42dVf/vPTSS64ur1J47733FBoaqjNnzujVV191dTkArnPsl6+tpP3yu+++W+z2a9OmjYsqLtmYMWOKrXnMmDGuLg//H6dCAddw+PBhXbhwocjn6tSpozp16lRwRQDg2dgvm3P27FllZmYW+VyVKlUUGhpawRVd2/Hjx5WdnV3kc/7+/qpfv34FV4SiECwAAAAAmMapUAAAAABMI1gAAAAAMI1gAQAAAMA0ggUAAAAA0wgWAAAAAEwjWAAAAAAwjWABAAAAwLT/B4B9o3cEomC7AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "metrics = [\"triple_cardinality\", \"triple_cardinality_same_rel\"]\n", + "fig, ax = plt.subplots(1, len(metrics), figsize=[4 * len(metrics), 4])\n", + "\n", + "for i, metric in enumerate(metrics):\n", + " sns.countplot(\n", + " x=edge_dcs[metric],\n", + " order=[\"1:1\", \"1:M\", \"M:1\", \"M:M\"],\n", + " stat=\"probability\",\n", + " ax=ax[i],\n", + " )\n", + "fig.suptitle(\"Cardinality distribution\")\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Edge topological patterns\n", + "\n", + "The second method provided by `KGTopologyToolbox` for topological analysis at the edge level is `edge_pattern_summary`, which extracts information on several significant edge topological patterns. In particular, it detects whether the edge (h,r,t) is a loop, is symmetric or has inverse, inference, composition (directed and undirected):\n", + "\n", + "![image info](../images/edge_patterns.png)\n", + "\n", + "For inverse/inference, the method also provides the number and types of unique relations `r'` realizing the counterpart edges; for composition, the number of triangles supported by the edge is provided (the unique metapaths `[r_1, r_2]` can also be listed by setting `return_metapath_list=True` when calling the method)." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
hrtis_loopis_symmetrichas_inversen_inverse_relationsinverse_edge_typeshas_inferencen_inference_relationsinference_edge_typeshas_compositionhas_undirected_compositionn_trianglesn_undirected_triangles
0171803207FalseFalseFalse0[]False0[0]FalseTrue015
14903013662FalseFalseFalse0[]False0[0]TrueTrue44153
25480015999FalseFalseFalse0[]False0[0]FalseTrue01
3314807247FalseFalseFalse0[]False0[0]TrueTrue1029
410300016202FalseFalseFalse0[]False0[0]TrueTrue379
................................................
50884292451505097FalseFalseTrue1[46]True1[46, 50]TrueTrue15325722
50884306456508833FalseFalseTrue2[45, 46]True2[45, 46, 50]TrueTrue234913
508843194845015873FalseFalseTrue1[46]True2[46, 45, 50]TrueTrue13265004
5088432636550496FalseFalseTrue2[45, 46]True2[45, 46, 50]TrueTrue14335554
508843313860506368FalseFalseFalse0[]False0[50]TrueTrue119489
\n", + "

5088434 rows × 15 columns

\n", + "
" + ], + "text/plain": [ + " h r t is_loop is_symmetric has_inverse \\\n", + "0 1718 0 3207 False False False \n", + "1 4903 0 13662 False False False \n", + "2 5480 0 15999 False False False \n", + "3 3148 0 7247 False False False \n", + "4 10300 0 16202 False False False \n", + "... ... .. ... ... ... ... \n", + "5088429 2451 50 5097 False False True \n", + "5088430 6456 50 8833 False False True \n", + "5088431 9484 50 15873 False False True \n", + "5088432 6365 50 496 False False True \n", + "5088433 13860 50 6368 False False False \n", + "\n", + " n_inverse_relations inverse_edge_types has_inference \\\n", + "0 0 [] False \n", + "1 0 [] False \n", + "2 0 [] False \n", + "3 0 [] False \n", + "4 0 [] False \n", + "... ... ... ... \n", + "5088429 1 [46] True \n", + "5088430 2 [45, 46] True \n", + "5088431 1 [46] True \n", + "5088432 2 [45, 46] True \n", + "5088433 0 [] False \n", + "\n", + " n_inference_relations inference_edge_types has_composition \\\n", + "0 0 [0] False \n", + "1 0 [0] True \n", + "2 0 [0] False \n", + "3 0 [0] True \n", + "4 0 [0] True \n", + "... ... ... ... \n", + "5088429 1 [46, 50] True \n", + "5088430 2 [45, 46, 50] True \n", + "5088431 2 [46, 45, 50] True \n", + "5088432 2 [45, 46, 50] True \n", + "5088433 0 [50] True \n", + "\n", + " has_undirected_composition n_triangles n_undirected_triangles \n", + "0 True 0 15 \n", + "1 True 44 153 \n", + "2 True 0 1 \n", + "3 True 10 29 \n", + "4 True 3 79 \n", + "... ... ... ... \n", + "5088429 True 1532 5722 \n", + "5088430 True 234 913 \n", + "5088431 True 1326 5004 \n", + "5088432 True 1433 5554 \n", + "5088433 True 119 489 \n", + "\n", + "[5088434 rows x 15 columns]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "edge_eps = kgtt.edge_pattern_summary(biokg_df)\n", + "edge_eps" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fraction of triples with property:\n" + ] + }, + { + "data": { + "text/plain": [ + "is_loop 0.000011\n", + "is_symmetric 0.713743\n", + "has_inverse 0.409704\n", + "has_inference 0.410111\n", + "has_composition 0.997605\n", + "dtype: float64" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Fraction of triples with property:\")\n", + "edge_eps[\n", + " [\"is_loop\", \"is_symmetric\", \"has_inverse\", \"has_inference\", \"has_composition\"]\n", + "].mean()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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pmJiYrxmOS319ddf64wzzlB1dbXb1gkAreENvWzmzJkKDw+Xj4+PVq1a5ZRazMRZfyO4+2+N2tpaRUVFadOmTW5ZH1qPkIdTVlMN54033lBpaaluvvlmp6xn5MiR2rNnj0JDQ52yPDj+3T3wwAPKz893Ww2DBg3SBRdcoLlz57ptnQBwPG/uZVu2bNEjjzyiv/3tb9qzZ4+uuuoqp9Ryqrv00kt177332k1z998aAQEBeuCBB/Tggw+6ZX1oPUIeTjm1tbXNvv7ss88qLS1Nvr7O+d8jICBAERER8vHxccry2upE293e19elSxd1797dretMS0vTggULdOTIEbeuFwDaQy/77rvvJEnXXXedIiIiFBgY2KZ1Hz58uE3zeZJhGG7tDZ74W+OWW27RBx98oK+++spt60TLEfLQLqxcuVJDhgxRx44d1b17dyUmJqq6ulp1dXWyWCzq2rWrunfvrj//+c9KTU1VcnKybd5LL71U6enpuvfeexUWFqakpCRFRUVJkq6//nr5+PjYnu/du1fvvvuufvvb39qt38fHRy+88IKuv/56derUSf369dMbb7zRotqPP4TipZdeUteuXfXOO+9o4MCB6tKli0aPHq09e/ZIktasWaOgoKBGh1xMmTJFv/nNb2zPP/jgA1188cXq2LGjIiMjNXnyZLtDB6OiojRr1iyNHz9eISEhuvPOO1VbW6v09HT17NlTQUFB6tOnj7Kysmzz/PLLL/rDH/6g008/XSEhIfrNb36jzz77rEXb2XC45AsvvKC+ffsqKCioTcv85JNPdMUVVygsLEyhoaEaNWqUiouL7bZLavy7O/5wzfr6ev31r3/VGWecocDAQMXExCgvL8/2+o4dO+Tj46PXXntNl112mTp16qTo6GgVFhbaxvz444/67W9/q27duqlz584677zz9Pbbb9tev+KKK7Rv3z6tX7++Re8RgFPbqdTLZs6caVu/r6+vXfh44YUXNHDgQAUFBWnAgAF6/vnnba81fDYvX75co0aNUlBQkF555ZUWz9fcZ7okffjhh7r00kvVqVMndevWTUlJSfr5558lHe0bWVlZ6tu3rzp27Kjo6GitXLmyVe/Pf/7zH8XGxiowMFAffPBBq5f5008/ady4cerdu7c6deqkIUOG6J///Kft9dtvv13r16/XvHnz5OPjIx8fH+3YscPh4ZqvvvqqzjvvPAUGBioqKkpz5syxW1dUVJQef/xx3XHHHQoODtaZZ56phQsX2l4/0d8M3bp104UXXqhly5a16D2CmxmAl9u9e7fh7+9vzJ071/jhhx+Mzz//3MjJyTGqqqqMJ5980ujWrZvx6quvGl9//bUxYcIEIzg42Ljuuuts848aNcro0qWLMXXqVGPr1q3G1q1bjbKyMkOSsWTJEmPPnj1GWVmZYRiG8dprrxmdO3c26urq7GqQZJxxxhnG0qVLjW+//daYPHmy0aVLF+Onn346Yf3r1q0zJBk///yzYRiGsWTJEqNDhw5GYmKi8cknnxhFRUXGwIEDjd///veGYRjGkSNHjPDwcOOFF16wLeP4adu3bzc6d+5sPPPMM8Y333xjfPjhh8bQoUON22+/3TZPnz59jJCQEGP27NnG9u3bje3btxtPP/20ERkZabz33nvGjh07jPfff99YunSpbZ7ExETjt7/9rfHJJ58Y33zzjXH//fcb3bt3b9F2ZmZmGp07dzZGjx5tFBcXG5999lmLlpmZmWlER0fblpOfn2/83//9n7Flyxbb7zQ8PNyorKw0DMNo8nd3/HLmzp1rhISEGP/85z+NrVu3Gn/+85+NDh06GN98841hGIbxww8/GJKMAQMGGG+99Zaxbds246abbjL69OljHD582DAMwxgzZoxxxRVXGJ9//rnx3XffGW+++aaxfv16u+2Oj483MjMzT/j+ADi1nWq9rKqqyliyZIkhydizZ4+xZ88ewzAM4x//+IfRs2dP49VXXzW+//5749VXXzVOO+0046WXXjIM49fP5qioKNuY3bt3t3i+5j7TP/30UyMwMNC4++67jc2bNxtffvml8dxzzxl79+41DMMwHn30UWPAgAFGXl6e8d133xlLliwxAgMDjYKCgha/P+eff76xZs0aY/v27cZPP/10wmUe/77+73//M55++mnj008/Nb777jvj2WefNfz8/IyPP/7YMAzD+OWXX4yEhARj4sSJtvf1yJEjjZazadMmw9fX1/jrX/9qbNu2zViyZInRsWNHY8mSJbaa+/TpY5x22mlGTk6O8e233xpZWVmGr6+vsXXrVsMwjBP+zWAYhvHggw8ao0aNOuH7A/cj5MHrFRUVGZKMHTt2NHqtZ8+exlNPPWV7fvjwYeOMM85o1BiHDh3aaF5Jxuuvv2437ZlnnjHOOussh2OnT59ue75//35DkvGf//znhPU7aoySjO3bt9vG5OTkGOHh4bbnU6ZMMX7zm9/Ynr/zzjtGYGCgbRkTJkww7rzzTrv1vP/++4avr69x8OBBwzCOfngnJyfbjbnnnnuM3/zmN0Z9fX2jOt9//30jJCTEOHTokN30s88+2/jb3/52wu3MzMw0OnToYPsjo6XLPD6cHa+urs4IDg423nzzTds0R7+745fTq1cv47HHHrMbM3z4cONPf/qTYRi//kFwbJj+6quvDEnGli1bDMMwjCFDhhgzZ85sdruvv/56u3ANAI6cir3s9ddfN47fn3D22Wc3CgqzZs0yEhISDMP49bM5Ozu7TfM195k+btw448ILL3S4fYcOHTI6depkbNiwwW76hAkTjHHjxjl+U47R8P6sWrWqVcs8/n11ZMyYMcb9999vez5q1ChjypQpDtffsJzf//73xhVXXGE3ZurUqcagQYNsz/v06WPceuuttuf19fVGjx49jAULFhiG0fzfDA3mzZtnREVFNfk6PMfftfsJgZMXHR2tyy+/XEOGDFFSUpKuvPJK3XTTTfL19dWePXsUHx9vG+vv76+4uDgZhmG3jNjY2Bat6+DBg7bDDI93/vnn237u3LmzQkJCVFZW1oYtkjp16qSzzz7b9rxnz552y7rlllt0wQUXaPfu3erVq5deeeUVjRkzRl27dpUkffbZZ/r8889th7BIR4//r6+v1w8//KCBAwdKkuLi4uzWe/vtt+uKK67Queeeq9GjR+uaa67RlVdeaVvm/v37G53XdvDgQdt5FSfSp08fnX766bbnbVlmaWmppk+froKCApWVlamurk4HDhxQSUlJi2qQpMrKSu3evVsXXnih3fQLL7yw0aGix/5ee/bsKUkqKyvTgAEDNHnyZN19991as2aNEhMTdeONN9qNl6SOHTvqwIEDLa4NwKnpVOxlx6uurtZ3332nCRMmaOLEibbpR44caXTBkGP7V2vma+4zffPmzRo7dqzD2rZv364DBw7oiiuusJteW1uroUOHNrlNxzu27rYss66uTo8//rj+9a9/adeuXaqtrVVNTY06derU4hqkoxe9ue666+ymXXjhhcrOzlZdXZ38/Pwk2b9fPj4+ioiIsP0Om/uboQE90HsR8uD1/Pz8tHbtWm3YsEFr1qzRc889p7/85S9au3Zti5fRuXPnFo0LCwuzHZt/vA4dOtg99/HxUX19fYtrONGyjm3mw4cP19lnn61ly5bp7rvv1uuvv66XXnrJ9vr+/fv1xz/+UZMnT2607DPPPNP28/HbPWzYMP3www/6z3/+o//+97/63e9+p8TERK1cuVL79+9Xz549VVBQ0GiZDeHyRI5fX1uWmZqaqp9++knz5s1Tnz59FBgYqISEBJddyOXY30XDOSMNv9c//OEPSkpK0urVq7VmzRplZWVpzpw5uueee2zz7Nu3z+6PHABw5FTsZcfbv3+/JGnRokV2oVaSLXQ0OHZbWzNfc5/pHTt2PGFtq1evVu/eve1ea80FYxzV3ZplPv3005o3b56ys7M1ZMgQde7cWffee69beqBk/++hub8ZGuzbt8/uy114D0Ie2gUfHx9deOGFuvDCCzVjxgz16dNH+fn56tmzpz7++GNdcsklko5+q1dUVKRhw4adcJkdOnRQXV2d3bShQ4fKarXq559/Vrdu3VyyLS11yy236JVXXtEZZ5whX19fjRkzxvbasGHD9PXXX+ucc85p9XJDQkKUkpKilJQU3XTTTRo9erT27dunYcOGyWq1yt/f33by/slqyzI//PBDPf/887r66qslSTt37lR5ebndGEe/u2OFhISoV69e+vDDDzVq1Ci7ZY8YMaJV2xAZGam77rpLd911lzIyMrRo0SK7kPfll1/qpptuatUyAZyaTsVedqzw8HD16tVL33//vW655RaXz3e8888/X/n5+XrkkUcavTZo0CAFBgaqpKTErm+cjLYs88MPP9R1112nW2+9VdLRgPrNN99o0KBBtjEBAQHN9kBJGjhwoD788MNGy+7fv3+jYNycpv5mOO200yQd7YGt2dMJ9yHkwet9/PHHys/P15VXXqkePXro448/1t69ezVw4EBNmTJFTzzxhPr166cBAwZo7ty5Lb4RaFRUlPLz83XhhRcqMDBQ3bp109ChQxUWFqYPP/xQ11xzjWs37ARuueUWzZw5U4899phuuukmu2/9HnzwQV1wwQVKT0/XH/7wB3Xu3Flff/211q5dq/nz5ze5zLlz56pnz54aOnSofH19tWLFCkVERKhr165KTExUQkKCkpOT9dRTT6l///7avXu3Vq9ereuvv77RoZ8t0ZZl9uvXT//3f/+nuLg4VVZWaurUqY2+fXX0uzve1KlTlZmZqbPPPlsxMTFasmSJNm/ebHeI64nce++9uuqqq9S/f3/9/PPPWrdune1QWOno1dx27dqlxMTEVrwrAE5Fp2ovO94jjzyiyZMnKzQ0VKNHj1ZNTY02bdqkn3/+WRaLxenzHSsjI0NDhgzRn/70J911110KCAjQunXrNHbsWIWFhemBBx7Qfffdp/r6el100UWqqKjQhx9+qJCQEKWmprZ6W4ODg1u9zH79+mnlypXasGGDunXrprlz56q0tNQu5EVFRenjjz/Wjh071KVLF1vgOtb999+v4cOHa9asWUpJSVFhYaHmz59vd0XSE2nub4YG77//vmbNmtW6NwZuwS0U4PVCQkL03nvv6eqrr1b//v01ffp0zZkzR1dddZXuv/9+3XbbbUpNTVVCQoKCg4N1/fXXt2i5c+bM0dq1axUZGWn7FsrPz09paWmtCgKucs4552jEiBH6/PPPG31zef7552v9+vX65ptvdPHFF2vo0KGaMWOGevXq1ewyg4OD9dRTTykuLk7Dhw/Xjh079Pbbb9sub/3222/rkksuUVpamvr376+bb75ZP/74o8LDw9u0DW1Z5osvvqiff/5Zw4YN02233abJkyerR48edmMc/e6ON3nyZFksFt1///0aMmSI8vLy9MYbb6hfv34trr+urk6TJk3SwIEDNXr0aPXv39+uQf7zn//UlVdeqT59+rR4mQBOTadqLzveH/7wB73wwgtasmSJhgwZolGjRumll15S3759XTLfsfr37681a9bos88+04gRI5SQkKB///vf8vc/us9j1qxZevjhh5WVlWX73F+9enWr1nG81i5z+vTpGjZsmJKSknTppZcqIiLC7lYakvTAAw/Iz89PgwYN0umnn+7wnPVhw4bpX//6l5YtW6bBgwdrxowZ+utf/6rbb7+9xbU39zeDJBUWFqqiooKjWbyUj9HcwdNAO3T77bfrl19+0apVq9o0v9Vq1Xnnnafi4mL+eEeTamtr1a9fPy1durTRBV4A4GTRy+DtUlJSFB0drWnTpnm6FDjAnjzgOBEREXrxxRdbdTVHnHpKSko0bdo0Ah4Ar0QvgyvV1tZqyJAhuu+++zxdCppAyAMcSE5O1sUXX9yisXfddZe6dOni8HHXXXe5uFL3Oe+885rcTm88JMjVzjnnHP3xj3/0dBkA0CR6mfPw/tgLCAjQ9OnT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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "metrics = [\n", + " \"n_inverse_relations\",\n", + " \"n_inference_relations\",\n", + " \"n_triangles\",\n", + " \"n_undirected_triangles\",\n", + "]\n", + "fig, ax = plt.subplots(2, 2, figsize=(9, 7))\n", + "\n", + "for axn, metric in zip(ax.flatten(), metrics):\n", + " x = np.sqrt(edge_eps[metric])\n", + " sns.histplot(x=x, stat=\"probability\", binwidth=1, binrange=[0, x.max() + 1], ax=axn)\n", + " axn.set_xlabel(f\"sqrt({metric})\")\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Relation-level analysis\n", + "\n", + "The method `aggregate_by_relation` allows the user to aggregate at the relation-level the statistics outputted by the edge-level methods `edge_degree_cardinality_summary` and `edge_pattern_summary`. This converts DataFrames indexed on the KG edges to DataFrames indexed on the IDs of the unique relation types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
num_triplesfrac_triplesunique_hunique_th_unique_rel_meanh_unique_rel_stdh_unique_rel_quartile1h_unique_rel_quartile2h_unique_rel_quartile3h_degree_mean...tot_degree_same_rel_quartile1tot_degree_same_rel_quartile2tot_degree_same_rel_quartile3triple_cardinality_1:M_fractriple_cardinality_M:1_fractriple_cardinality_M:M_fractriple_cardinality_same_rel_1:1_fractriple_cardinality_same_rel_1:M_fractriple_cardinality_same_rel_M:1_fractriple_cardinality_same_rel_M:M_frac
r
0810660.015931974293378.1102938.2472774.05.08.0569.252202...45.0112.0211.00.00.01.00.0016280.0235860.0649590.909827
156690.001114698153627.04815712.93641017.031.036.02518.765391...14.032.060.00.00.01.00.0028220.1042510.0275180.865408
2669540.01315861261236.4043075.60070633.036.041.04129.511919...332.0404.0482.00.00.01.00.0000000.0002540.0002390.999507
3195850.00384949149137.0959415.54738933.037.041.04527.399592...114.0157.0202.00.00.01.00.0000000.0008680.0009700.998162
4320340.00629552652537.3195675.38452334.038.041.04511.067834...188.0243.0299.00.00.01.00.0000620.0005310.0005930.998814
\n", + "

5 rows × 51 columns

\n", + "
" + ], + "text/plain": [ + " num_triples frac_triples unique_h unique_t h_unique_rel_mean \\\n", + "r \n", + "0 81066 0.015931 9742 9337 8.110293 \n", + "1 5669 0.001114 698 1536 27.048157 \n", + "2 66954 0.013158 612 612 36.404307 \n", + "3 19585 0.003849 491 491 37.095941 \n", + "4 32034 0.006295 526 525 37.319567 \n", + "\n", + " h_unique_rel_std h_unique_rel_quartile1 h_unique_rel_quartile2 \\\n", + "r \n", + "0 8.247277 4.0 5.0 \n", + "1 12.936410 17.0 31.0 \n", + "2 5.600706 33.0 36.0 \n", + "3 5.547389 33.0 37.0 \n", + "4 5.384523 34.0 38.0 \n", + "\n", + " h_unique_rel_quartile3 h_degree_mean ... tot_degree_same_rel_quartile1 \\\n", + "r ... \n", + "0 8.0 569.252202 ... 45.0 \n", + "1 36.0 2518.765391 ... 14.0 \n", + "2 41.0 4129.511919 ... 332.0 \n", + "3 41.0 4527.399592 ... 114.0 \n", + "4 41.0 4511.067834 ... 188.0 \n", + "\n", + " tot_degree_same_rel_quartile2 tot_degree_same_rel_quartile3 \\\n", + "r \n", + "0 112.0 211.0 \n", + "1 32.0 60.0 \n", + "2 404.0 482.0 \n", + "3 157.0 202.0 \n", + "4 243.0 299.0 \n", + "\n", + " triple_cardinality_1:M_frac triple_cardinality_M:1_frac \\\n", + "r \n", + "0 0.0 0.0 \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "4 0.0 0.0 \n", + "\n", + " triple_cardinality_M:M_frac triple_cardinality_same_rel_1:1_frac \\\n", + "r \n", + "0 1.0 0.001628 \n", + "1 1.0 0.002822 \n", + "2 1.0 0.000000 \n", + "3 1.0 0.000000 \n", + "4 1.0 0.000062 \n", + "\n", + " triple_cardinality_same_rel_1:M_frac triple_cardinality_same_rel_M:1_frac \\\n", + "r \n", + "0 0.023586 0.064959 \n", + "1 0.104251 0.027518 \n", + "2 0.000254 0.000239 \n", + "3 0.000868 0.000970 \n", + "4 0.000531 0.000593 \n", + "\n", + " triple_cardinality_same_rel_M:M_frac \n", + "r \n", + "0 0.909827 \n", + "1 0.865408 \n", + "2 0.999507 \n", + "3 0.998162 \n", + "4 0.998814 \n", + "\n", + "[5 rows x 51 columns]" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kgtt.aggregate_by_relation(edge_dcs).head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice on the left the columns `num_triples`, `frac_triples`, `unique_h`, `unique_t` giving additional statistics for relation types (number of edges and relative frequency, number of unique entities used as heads/tails by triples of the relation type).\n", + "\n", + "Similarly, by aggregating the `edge_eps` DataFrame we can look at the distribution of edge topological patterns within each relation type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
num_triplesfrac_triplesunique_hunique_tis_loop_fracis_symmetric_frachas_inverse_fracn_inverse_relations_meann_inverse_relations_stdn_inverse_relations_quartile1...n_triangles_meann_triangles_stdn_triangles_quartile1n_triangles_quartile2n_triangles_quartile3n_undirected_triangles_meann_undirected_triangles_stdn_undirected_triangles_quartile1n_undirected_triangles_quartile2n_undirected_triangles_quartile3
r
0810660.015931974293370.0000120.0002220.0094740.0187620.3361200.0...49.615572816.7767383.07.016.00136.4528411421.83000818.0036.068.0
156690.00111469815360.0000000.0003530.0615630.5277832.5023230.0...1630.9121546563.52273613.084.0234.002864.1044289520.11681254.00224.0586.0
2669540.0131586126120.0000000.9473670.99825311.0191184.7072468.0...27666.69492515797.64974614990.025934.038868.5032678.99356318619.01605616691.0032647.548637.0
3195850.0038494914910.0000000.9473580.99959213.4172584.58515010.0...30250.85897417053.92541016204.028873.043798.0032696.12535118685.28168616563.0032808.048653.0
4320340.0062955265250.0000000.9473680.99937613.2995884.42789810.0...30942.23119216888.95665617303.030137.544161.2532685.21046418685.26715416645.2532580.048767.0
\n", + "

5 rows × 32 columns

\n", + "
" + ], + "text/plain": [ + " num_triples frac_triples unique_h unique_t is_loop_frac \\\n", + "r \n", + "0 81066 0.015931 9742 9337 0.000012 \n", + "1 5669 0.001114 698 1536 0.000000 \n", + "2 66954 0.013158 612 612 0.000000 \n", + "3 19585 0.003849 491 491 0.000000 \n", + "4 32034 0.006295 526 525 0.000000 \n", + "\n", + " is_symmetric_frac has_inverse_frac n_inverse_relations_mean \\\n", + "r \n", + "0 0.000222 0.009474 0.018762 \n", + "1 0.000353 0.061563 0.527783 \n", + "2 0.947367 0.998253 11.019118 \n", + "3 0.947358 0.999592 13.417258 \n", + "4 0.947368 0.999376 13.299588 \n", + "\n", + " n_inverse_relations_std n_inverse_relations_quartile1 ... \\\n", + "r ... \n", + "0 0.336120 0.0 ... \n", + "1 2.502323 0.0 ... \n", + "2 4.707246 8.0 ... \n", + "3 4.585150 10.0 ... \n", + "4 4.427898 10.0 ... \n", + "\n", + " n_triangles_mean n_triangles_std n_triangles_quartile1 \\\n", + "r \n", + "0 49.615572 816.776738 3.0 \n", + "1 1630.912154 6563.522736 13.0 \n", + "2 27666.694925 15797.649746 14990.0 \n", + "3 30250.858974 17053.925410 16204.0 \n", + "4 30942.231192 16888.956656 17303.0 \n", + "\n", + " n_triangles_quartile2 n_triangles_quartile3 n_undirected_triangles_mean \\\n", + "r \n", + "0 7.0 16.00 136.452841 \n", + "1 84.0 234.00 2864.104428 \n", + "2 25934.0 38868.50 32678.993563 \n", + "3 28873.0 43798.00 32696.125351 \n", + "4 30137.5 44161.25 32685.210464 \n", + "\n", + " n_undirected_triangles_std n_undirected_triangles_quartile1 \\\n", + "r \n", + "0 1421.830008 18.00 \n", + "1 9520.116812 54.00 \n", + "2 18619.016056 16691.00 \n", + "3 18685.281686 16563.00 \n", + "4 18685.267154 16645.25 \n", + "\n", + " n_undirected_triangles_quartile2 n_undirected_triangles_quartile3 \n", + "r \n", + "0 36.0 68.0 \n", + "1 224.0 586.0 \n", + "2 32647.5 48637.0 \n", + "3 32808.0 48653.0 \n", + "4 32580.0 48767.0 \n", + "\n", + "[5 rows x 32 columns]" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kgtt.aggregate_by_relation(edge_eps).head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Additional methods are provided for the analysis at the relation level: `jaccard_similarity_relation_sets` to compute the Jaccard similarity of the sets of head/tail entities used by each relation; `relational_affinity_ingram` to compute the InGram pairwise relation similarity (see [paper](https://arxiv.org/abs/2305.19987)). " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
r1r2num_triples_bothfrac_triples_bothnum_entities_bothnum_h_r1num_h_r2num_t_r1num_t_r2jaccard_head_headjaccard_head_tailjaccard_tail_headjaccard_tail_tailjaccard_both
101867350.017046143389742698933715360.0641120.0553010.0373170.0796350.112289
2021480200.02908913934974261293376120.0565310.0565310.0319470.0319470.041768
3031006510.01978013929974249193374910.0450370.0450370.0265300.0265300.033527
4041131000.02222713931974252693375250.0482900.0481880.0276100.0275060.035819
5051322760.02599513931974257693375780.0532870.0534910.0298150.0299160.039624
.............................................
24464749180210.0035422414806188580918860.1311480.1315680.1324090.1323530.135874
244747503741930.0735385592806522480952280.0823910.0825260.0833180.0834530.084764
24974849431220.008475340727281885272918860.3712840.3699520.3719890.3710640.370707
249848503992940.078471620127285224272952280.2873560.2873790.2860610.2860840.289147
254949503880920.076269616918855224188652280.1568760.1567730.1568500.1567480.158373
\n", + "

1275 rows × 14 columns

\n", + "
" + ], + "text/plain": [ + " r1 r2 num_triples_both frac_triples_both num_entities_both \\\n", + "1 0 1 86735 0.017046 14338 \n", + "2 0 2 148020 0.029089 13934 \n", + "3 0 3 100651 0.019780 13929 \n", + "4 0 4 113100 0.022227 13931 \n", + "5 0 5 132276 0.025995 13931 \n", + "... .. .. ... ... ... \n", + "2446 47 49 18021 0.003542 2414 \n", + "2447 47 50 374193 0.073538 5592 \n", + "2497 48 49 43122 0.008475 3407 \n", + "2498 48 50 399294 0.078471 6201 \n", + "2549 49 50 388092 0.076269 6169 \n", + "\n", + " num_h_r1 num_h_r2 num_t_r1 num_t_r2 jaccard_head_head \\\n", + "1 9742 698 9337 1536 0.064112 \n", + "2 9742 612 9337 612 0.056531 \n", + "3 9742 491 9337 491 0.045037 \n", + "4 9742 526 9337 525 0.048290 \n", + "5 9742 576 9337 578 0.053287 \n", + "... ... ... ... ... ... \n", + "2446 806 1885 809 1886 0.131148 \n", + "2447 806 5224 809 5228 0.082391 \n", + "2497 2728 1885 2729 1886 0.371284 \n", + "2498 2728 5224 2729 5228 0.287356 \n", + "2549 1885 5224 1886 5228 0.156876 \n", + "\n", + " jaccard_head_tail jaccard_tail_head jaccard_tail_tail jaccard_both \n", + "1 0.055301 0.037317 0.079635 0.112289 \n", + "2 0.056531 0.031947 0.031947 0.041768 \n", + "3 0.045037 0.026530 0.026530 0.033527 \n", + "4 0.048188 0.027610 0.027506 0.035819 \n", + "5 0.053491 0.029815 0.029916 0.039624 \n", + "... ... ... ... ... \n", + "2446 0.131568 0.132409 0.132353 0.135874 \n", + "2447 0.082526 0.083318 0.083453 0.084764 \n", + "2497 0.369952 0.371989 0.371064 0.370707 \n", + "2498 0.287379 0.286061 0.286084 0.289147 \n", + "2549 0.156773 0.156850 0.156748 0.158373 \n", + "\n", + "[1275 rows x 14 columns]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kgtt.jaccard_similarity_relation_sets(biokg_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
h_relationt_relationedge_weight
0015.565931
1020.244410
2030.049564
3040.079068
4050.159787
............
25455045393.082900
25465046421.818843
254750471.194898
2548504818.124874
254950495.420267
\n", + "

2550 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " h_relation t_relation edge_weight\n", + "0 0 1 5.565931\n", + "1 0 2 0.244410\n", + "2 0 3 0.049564\n", + "3 0 4 0.079068\n", + "4 0 5 0.159787\n", + "... ... ... ...\n", + "2545 50 45 393.082900\n", + "2546 50 46 421.818843\n", + "2547 50 47 1.194898\n", + "2548 50 48 18.124874\n", + "2549 50 49 5.420267\n", + "\n", + "[2550 rows x 3 columns]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kgtt.relational_affinity_ingram(biokg_df)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv38", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/source/user_guide.rst b/docs/source/user_guide.rst new file mode 100644 index 0000000..9e6714e --- /dev/null +++ b/docs/source/user_guide.rst @@ -0,0 +1,27 @@ +User guide +================ + +Installation and usage +------------------------ + +1. Pip install :code:`kg-topology-toolbox`: + +.. code-block:: + + pip install git+https://github.com/graphcore-research/kg-topology-toolbox.git + +2. Import and use: + +.. code-block:: + + from kg_topology_toolbox import KGTopologyToolbox + +.. Note:: The library has been tested on Ubuntu 20.04, Python >= 3.8. + + +Getting started +------------------------ + +For a walkthrough of the library functionalities, see the `Jupyter notebook `_. + +For more details, have a look at the `API reference `_ page. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6628c79 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,84 @@ +[build-system] +requires = ["setuptools>=61.0.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "kg-topology-toolbox" +version = "0.1.0" +authors = [ + {name = "Alberto Cattaneo"}, + {name = "Daniel Justus"}, + {name = "Thomas Martynec"}, + {name = "Stephen Bonner"}, +] +description = "A Python toolbox for Knowledge Graph topology metrics." +readme = "README.md" +license = {text = "MIT License"} +requires-python = ">=3.8" +dependencies = [ + 'numpy >= 1.24.4', + 'pandas >= 2.0.3', + 'scipy >= 1.10.1', +] + +[project.optional-dependencies] +dev = [ + 'black', + 'flake8', + 'isort', + 'mypy', + 'pandas-stubs >= 2.0.3.230814', + 'pytest >= 8.1.1', + 'pytest-cov', + 'sphinx >= 7.1.2', + 'sphinx_rtd_theme', + 'sphinx_autodoc_typehints', + 'sphinx-automodapi', + 'myst-parser', +] + +[project.urls] +repository = "https://github.com/graphcore-research/kg-topology-toolbox" + +[tool.setuptools.packages.find] +where = ["src"] +exclude = ["tests"] +namespaces = true + +[tool.black] +target-version = ["py38", "py39", "py310", "py311"] + +[tool.isort] +profile = "black" + +[tool.mypy] +pretty = true +show_error_codes = true +strict = true +check_untyped_defs = true +plugins = ["numpy.typing.mypy_plugin"] + +[[tool.mypy.overrides]] +module = "scipy.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "setuptools.*" +ignore_missing_imports = true + +[tool.pytest] +addopts = ["--no-cov-on-fail"] + +[tool.pytest.ini_options] +pythonpath = [ + "src" +] + +[tool.coverage.report] +skip_covered = true +show_missing = true +exclude_lines = [ + "pragma: no cover", + "raise NotImplementedError", + "assert False", +] \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..45a15b1 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,15 @@ +-r requirements.txt +black +flake8 +isort +mypy +pandas-stubs>=2.0.3.230814 +pandoc +pytest>=8.1.1 +pytest-cov +sphinx>=7.1.2 +sphinx_rtd_theme +sphinx_autodoc_typehints +sphinx-automodapi +nbsphinx +myst-parser diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f4eff4d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy>=1.24.4 +pandas>=2.0.3 +scipy>=1.10.1 diff --git a/src/kg_topology_toolbox/__init__.py b/src/kg_topology_toolbox/__init__.py new file mode 100644 index 0000000..7ff262f --- /dev/null +++ b/src/kg_topology_toolbox/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. + +""" +A Python toolbox for computing topological metrics and statistics for Knowledge Graphs. +""" + +from . import utils # NOQA:F401,E402,F403 +from .topology_toolbox import * # NOQA:F401,E402,F403 diff --git a/src/kg_topology_toolbox/topology_toolbox.py b/src/kg_topology_toolbox/topology_toolbox.py new file mode 100644 index 0000000..4fffe64 --- /dev/null +++ b/src/kg_topology_toolbox/topology_toolbox.py @@ -0,0 +1,588 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. + +""" +Topology toolbox main functionalities +""" + +from collections.abc import Iterable + +import numpy as np +import pandas as pd +from scipy.sparse import coo_array + +from kg_topology_toolbox.utils import composition_count, jaccard_similarity + + +class KGTopologyToolbox: + """ + Toolbox class to compute various Knowledge Graph topology statistics. + """ + + def node_degree_summary( + self, df: pd.DataFrame, return_relation_list: bool = False + ) -> pd.DataFrame: + """ + For each entity, this function computes the number of edges having it as a head + (head-degree, or out-degree), as a tail (tail-degree, or in-degree) + or one of the two (total-degree) in the Knowledge Graph. + The in-going and out-going relation types are also identified. + + The output dataframe is indexed on the IDs of the graph entities. + + :param df: A graph represented as a pd.DataFrame. + Must contain at least three columns `h`, `r`, `t`. + :param return_relation_list: If True, return the list of unique relations going + in/out of an entity. WARNING: expensive for large graphs. + + :return: The results dataframe, indexed over the same entity ID `e` used in df, + with columns: + + - **h_degree** (int): Number of triples with head entity `e`. + - **t_degree** (int): Number of triples with tail entity `e`. + - **tot_degree** (int): Number of triples with head entity `e` or tail entity `e`. + - **h_unique_rel** (int): Number of distinct relation types + among edges with head entity `e`. + - **h_rel_list** (list): List of unique relation types among edges + with head entity `e`. + - **t_unique_rel** (int): Number of distinct relation types + among edges with tail entity `e`. + - **t_rel_list** (list): List of unique relation types among edges + with tail entity `e`. + - **n_loops** (int): number of loops around entity `e`. + """ + n_entity = df[["h", "t"]].max().max() + 1 + h_rel_list = {"h_rel_list": ("r", "unique")} if return_relation_list else {} + t_rel_list = {"t_rel_list": ("r", "unique")} if return_relation_list else {} + nodes = pd.DataFrame( + df.groupby("h").agg( + h_degree=("r", "count"), h_unique_rel=("r", "nunique"), **h_rel_list # type: ignore + ), + index=np.arange(n_entity), + ) + nodes = nodes.merge( + df.groupby("t").agg( + t_degree=("r", "count"), t_unique_rel=("r", "nunique"), **t_rel_list # type: ignore + ), + left_index=True, + right_index=True, + how="left", + ) + nodes = nodes.merge( + df[df.h == df.t].groupby("h").agg(n_loops=("r", "count")), + left_index=True, + right_index=True, + how="left", + ) + nodes[["h_degree", "h_unique_rel", "t_degree", "t_unique_rel", "n_loops"]] = ( + nodes[["h_degree", "h_unique_rel", "t_degree", "t_unique_rel", "n_loops"]] + .fillna(0) + .astype(int) + ) + nodes["tot_degree"] = nodes["h_degree"] + nodes["t_degree"] - nodes["n_loops"] + + return nodes[ + ["h_degree", "t_degree", "tot_degree", "h_unique_rel"] + + (["h_rel_list"] if return_relation_list else []) + + ["t_unique_rel"] + + (["t_rel_list"] if return_relation_list else []) + + ["n_loops"] + ] + + def edge_degree_cardinality_summary(self, df: pd.DataFrame) -> pd.DataFrame: + """ + For each triple, this function computes the number of edges with the same head + (head-degree, or out-degree), the same tail (tail-degree, or in-degree) + or one of the two (total-degree) in the Knowledge Graph. + Based on entity degrees, each triple is classified as either one-to-one + (out-degree=in-degree=1), one-to-many (out-degree>1, in-degree=1), + many-to-one(out-degree=1, in-degree>1) or many-to-many + (in-degree>1, out-degree>1). + + The output dataframe maintains the same indexing and ordering of triples + as the input one. + + :param df: A graph represented as a pd.DataFrame. + Must contain at least three columns `h`, `r`, `t`. + + :return: The results dataframe. Contains the following columns + (in addition to `h`, `r`, `t` in ``df``): + + - **h_unique_rel** (int): Number of distinct relation types + among edges with head entity h. + - **h_degree** (int): Number of triples with head entity h. + - **h_degree_same_rel** (int): Number of triples with head entity h + and relation type r. + - **t_unique_rel** (int): Number of distinct relation types + among edges with tail entity t. + - **t_degree** (int): Number of triples with tail entity t. + - **t_degree_same_rel** (int): Number of triples with tail entity t + and relation type r. + - **tot_degree** (int): Number of triples with head entity h or + tail entity t. + - **tot_degree_same_rel** (int): Number of triples with head entity h or + tail entity t, and relation type r. + - **triple_cardinality** (int): cardinality type of the edge. + - **triple_cardinality_same_rel** (int): cardinality type of the edge in + the subgraph of edges with relation type r. + """ + gr_by_h_count = df.groupby("h", as_index=False).agg( + h_unique_rel=("r", "nunique"), h_degree=("t", "count") + ) + gr_by_hr_count = df.groupby(["h", "r"], as_index=False).agg( + h_degree_same_rel=("t", "count") + ) + gr_by_t_count = df.groupby("t", as_index=False).agg( + t_unique_rel=("r", "nunique"), t_degree=("h", "count") + ) + gr_by_rt_count = df.groupby(["r", "t"], as_index=False).agg( + t_degree_same_rel=("h", "count") + ) + + df_res = df.merge(gr_by_h_count, left_on=["h"], right_on=["h"], how="left") + df_res = df_res.merge( + gr_by_hr_count, left_on=["h", "r"], right_on=["h", "r"], how="left" + ) + df_res = df_res.merge(gr_by_t_count, left_on=["t"], right_on=["t"], how="left") + df_res = df_res.merge( + gr_by_rt_count, left_on=["t", "r"], right_on=["t", "r"], how="left" + ) + # compute number of parallel edges to avoid double-counting them + # in total degree + num_parallel = df_res.merge( + df.groupby(["h", "t"], as_index=False).agg(n_parallel=("r", "count")), + left_on=["h", "t"], + right_on=["h", "t"], + how="left", + ) + df_res["tot_degree"] = ( + df_res.h_degree + df_res.t_degree - num_parallel.n_parallel + ) + # when restricting to the relation type, there is only one edge + # (the edge itself) that is double-counted + df_res["tot_degree_same_rel"] = ( + df_res.h_degree_same_rel + df_res.t_degree_same_rel - 1 + ) + + # check if the values in the pair (h_degree, t_degree) are =1 or >1 + # to determine the edge cardinality + legend = { + 0: "M:M", + 1: "1:M", + 2: "M:1", + 3: "1:1", + } + for suffix in ["", "_same_rel"]: + edge_type = 2 * (df_res["h_degree" + suffix] == 1) + ( + df_res["t_degree" + suffix] == 1 + ) + df_res["triple_cardinality" + suffix] = edge_type.apply(lambda x: legend[x]) + return df_res + + def edge_pattern_summary( + self, + df: pd.DataFrame, + return_metapath_list: bool = False, + composition_chunk_size: int = 2**8, + composition_workers: int = 32, + ) -> pd.DataFrame: + """ + This function analyses the structural properties of each edge in the graph: + symmetry, presence of inverse/inference(=parallel) edges and + triangles supported on the edge. + + The output dataframe maintains the same indexing and ordering of triples + as the input one. + + :param df: A graph represented as a pd.DataFrame. + Must contain at least three columns `h`, `r`, `t`. + :param return_metapath_list: If True, return the list of unique metapaths for all + triangles supported over one edge. WARNING: very expensive for large graphs. + :param composition_chunk_size: Size of column chunks of sparse adjacency matrix + to compute the triangle count. + :param composition_workers: Number of workers to compute the triangle count. + + :return: The results dataframe. Contains the following columns + (in addition to `h`, `r`, `t` in ``df``): + + - **is_loop** (bool): True if the triple is a loop (``h == t``). + - **is_symmetric** (bool): True if the triple (t, r, h) is also contained + in the graph (assuming t and h are different). + - **has_inverse** (bool): True if the graph contains one or more triples + (t, r', h) with ``r' != r``. + - **n_inverse_relations** (int): The number of inverse relations r'. + - **inverse_edge_types** (list): All relations r' (including r if the edge + is symmetric) such that (t, r', h) is in the graph. + - **has_inference** (bool): True if the graph contains one or more triples + (h, r', t) with ``r' != r``. + - **n_inference_relations** (int): The number of inference relations r'. + - **inference_edge_types** (list): All relations r' (including r) such that + (h, r', t) is in the graph. + - **has_composition** (bool): True if the graph contains one or more triangles + supported on the edge: (h, r1, x) + (x, r2, t). + - **n_triangles** (int): The number of triangles. + - **has_undirected_composition** (bool): True if the graph contains one or more + undirected triangles supported on the edge. + - **n_undirected_triangles** (int): The number of undirected triangles + (considering all edges as bidirectional). + - **metapath_list** (list): The list of unique metapaths "r1-r2" + for the directed triangles. + """ + # symmetry-asymmetry + # edges with h/t switched + df_inv = df.reindex(columns=["t", "r", "h"]).rename( + columns={"t": "h", "r": "r", "h": "t"} + ) + df_res = pd.DataFrame({"h": df.h, "r": df.r, "t": df.t, "is_symmetric": False}) + df_res.loc[ + df.reset_index().merge(df_inv)["index"], + "is_symmetric", + ] = True + # loops are treated separately + df_res["is_loop"] = df_res.h == df_res.t + df_res.loc[df_res.h == df_res.t, "is_symmetric"] = False + + # inverse + unique_inv_r_by_ht = df_inv.groupby(["h", "t"], as_index=False).agg( + inverse_edge_types=("r", list), + ) + df_res = df_res.merge( + unique_inv_r_by_ht, left_on=["h", "t"], right_on=["h", "t"], how="left" + ) + df_res["inverse_edge_types"] = df_res["inverse_edge_types"].apply( + lambda agg: agg if isinstance(agg, list) else [] + ) + # if the edge (h,r,t) is symmetric or loop, we do not consider the relation + # r as a proper inverse + df_res["n_inverse_relations"] = ( + df_res.inverse_edge_types.str.len() - df_res.is_symmetric - df_res.is_loop + ) + df_res["n_inverse_relations"] = ( + df_res["n_inverse_relations"].fillna(0).astype(int) + ) + df_res["has_inverse"] = df_res["n_inverse_relations"] > 0 + + # inference + edges_between_ht = unique_inv_r_by_ht.reindex( + columns=["t", "h", "inverse_edge_types"] + ).rename( + columns={"t": "h", "h": "t", "inverse_edge_types": "inference_edge_types"} + ) + df_res = df_res.merge( + edges_between_ht, left_on=["h", "t"], right_on=["h", "t"], how="left" + ) + # inference_edge_types always contains the edge itself, which we need to drop + df_res["n_inference_relations"] = df_res.inference_edge_types.str.len() - 1 + df_res["has_inference"] = df_res["n_inference_relations"] > 0 + + # composition & metapaths + # discard loops as edges of a triangle + df_wo_loops = df[df.h != df.t] + if return_metapath_list: + # 2-hop paths + df_bridges = df_wo_loops.merge( + df_wo_loops, left_on="t", right_on="h", how="inner" + ) + df_triangles = df_wo_loops.merge( + df_bridges, left_on=["h", "t"], right_on=["h_x", "t_y"], how="inner" + ) + df_triangles["metapath"] = ( + df_triangles["r_x"].astype(str) + "-" + df_triangles["r_y"].astype(str) + ) + grouped_triangles = df_triangles.groupby( + ["h", "r", "t"], as_index=False + ).agg( + n_triangles=("metapath", "count"), metapath_list=("metapath", "unique") + ) + df_res = df_res.merge( + grouped_triangles, + left_on=["h", "r", "t"], + right_on=["h", "r", "t"], + how="left", + ) + df_res["metapath_list"] = df_res["metapath_list"].apply( + lambda agg: agg.tolist() if isinstance(agg, np.ndarray) else [] + ) + df_res["n_triangles"] = df_res["n_triangles"].fillna(0).astype(int) + else: + counts = composition_count( + df_wo_loops, + chunk_size=composition_chunk_size, + workers=composition_workers, + directed=True, + ) + df_res = df_res.merge( + counts, + on=["h", "t"], + how="left", + ) + df_res["n_triangles"] = df_res["n_triangles"].fillna(0).astype(int) + + df_res["has_composition"] = df_res["n_triangles"] > 0 + + counts = composition_count( + df_wo_loops, + chunk_size=composition_chunk_size, + workers=composition_workers, + directed=False, + ) + df_res = df_res.merge( + counts.rename(columns={"n_triangles": "n_undirected_triangles"}), + on=["h", "t"], + how="left", + ) + df_res["n_undirected_triangles"] = ( + df_res["n_undirected_triangles"].fillna(0).astype(int) + ) + df_res["has_undirected_composition"] = df_res["n_undirected_triangles"] > 0 + + return df_res[ + [ + "h", + "r", + "t", + "is_loop", + "is_symmetric", + "has_inverse", + "n_inverse_relations", + "inverse_edge_types", + "has_inference", + "n_inference_relations", + "inference_edge_types", + "has_composition", + "has_undirected_composition", + "n_triangles", + "n_undirected_triangles", + ] + + (["metapath_list"] if return_metapath_list else []) + ] + + def aggregate_by_relation(self, edge_topology_df: pd.DataFrame) -> pd.DataFrame: + """ + Aggregate topology metrics of all triples of the same relation type. + To be applied to the output dataframe of either + :meth:`KGTopologyToolbox.edge_degree_cardinality_summary` or + :meth:`KGTopologyToolbox.edge_pattern_summary`. + + The returned dataframe is indexed over relation type IDs, with columns + giving the aggregated statistics of triples of the correspondig relation. + The name of the columns is of the form ``column_name_in_input_df + suffix``. + The aggregation is perfomed by returning: + + - for numerical metrics: mean, standard deviation and quartiles + (``suffix`` = "_mean", "_std", "_quartile1", "_quartile2", "_quartile3"); + - for boolean metrics: the fraction of triples of the relation type + with metric = True (``suffix`` = "_frac"); + - for string metrics: for each possible label, the fraction of triples + of the relation type with that metric value (``suffix`` = "_{label}_frac") + - for list metrics: the unique metric values across triples of the relation + type (``suffix`` = "_unique"). + + :param edge_topology_df: pd.DataFrame of edge topology metrics. + Must contain at least three columns `h`, `r`, `t`. + + :return: The results dataframe. In addition to the columns with the aggregated + metrics by relation type, it also contains columns: + + - **num_triples** (int): Number of triples for each relation type. + - **frac_triples** (float): Fraction of overall triples represented by each + relation type. + - **unique_h** (int): Number of unique head entities used by triples of each + relation type. + - **unique_t** (int): Number of unique tail entities used by triples of each + relation type. + """ + df_by_r = edge_topology_df.groupby("r") + df_res = df_by_r.agg(num_triples=("r", "count")) + df_res["frac_triples"] = df_res["num_triples"] / edge_topology_df.shape[0] + col: str + for col, col_dtype in edge_topology_df.drop(columns=["r"]).dtypes.items(): # type: ignore + if col in ["h", "t"]: + df_res[f"unique_{col}"] = df_by_r[col].nunique() + elif col_dtype == object: + if isinstance(edge_topology_df[col].iloc[0], str): + for label in np.unique(edge_topology_df[col]): + df_res[f"{col}_{label}_frac"] = ( + edge_topology_df[edge_topology_df[col] == label] + .groupby("r")[col] + .count() + / df_res["num_triples"] + ).fillna(0) + elif isinstance(edge_topology_df[col].iloc[0], Iterable): + df_res[f"{col}_unique"] = ( + df_by_r[col] + .agg(np.unique) + .apply( + lambda x: ( + np.unique( + np.concatenate( + [lst for lst in x if len(lst) > 0] or [[]] + ) + ).tolist() + ) + ) + ) + else: + print(f"Skipping column {col}: no known aggregation mode") + continue + elif col_dtype == int or col_dtype == float: + df_res[f"{col}_mean"] = df_by_r[col].mean() + df_res[f"{col}_std"] = df_by_r[col].std() + for q in range(1, 4): + df_res[f"{col}_quartile{q}"] = df_by_r[col].agg( + lambda x: np.quantile(x, 0.25 * q) + ) + elif col_dtype == bool: + df_res[f"{col}_frac"] = df_by_r[col].mean() + return df_res + + def jaccard_similarity_relation_sets(self, df: pd.DataFrame) -> pd.DataFrame: + """ + Compute the similarity between relations defined as the Jaccard Similarity + between sets of entities (heads and tails) for all pairs + of relations in the graph. + + :param df: A graph represented as a pd.DataFrame. + Must contain at least three columns `h`, `r`, `t`. + + :return: The results dataframe. Contains the following columns: + + - **r1** (int): Index of the first relation. + - **r2** (int): Index of the second relation. + - **num_triples_both** (int): Number of triples with relation r1/r2. + - **frac_triples_both** (float): Fraction of triples with relation r1/r2. + - **num_entities_both** (int): Number of unique entities (h or t) for triples + with relation r1/r2. + - **num_h_r1** (int): Number of unique head entities for relation r1. + - **num_h_r2** (int): Number of unique head entities for relation r2. + - **num_t_r1** (int): Number of unique tail entities for relation r1. + - **num_t_r2** (int): Number of unique tail entities for relation r2. + - **jaccard_head_head** (float): Jaccard similarity between the head set of r1 + and the head set of r2. + - **jaccard_tail_tail** (float): Jaccard similarity between the tail set of r1 + and the tail set of r2. + - **jaccard_head_tail** (float): Jaccard similarity between the head set of r1 + and the tail set of r2. + - **jaccard_tail_head** (float): Jaccard similarity between the tail set of r1 + and the head set of r2. + - **jaccard_both** (float): Jaccard similarity between the full entity set + of r1 and r2. + """ + ent_unique = df.groupby("r", as_index=False).agg( + num_triples=("r", "count"), head=("h", "unique"), tail=("t", "unique") + ) + ent_unique["both"] = ent_unique.apply( + lambda x: np.unique(np.concatenate([x["head"], x["tail"]])), axis=1 + ) + ent_unique["num_h"] = ent_unique["head"].str.len() + ent_unique["num_t"] = ent_unique["tail"].str.len() + r_num = ent_unique[["r", "num_h", "num_t", "num_triples"]] + # combinations of relations + df_res = pd.merge( + r_num.rename(columns={"r": "r1"}), + r_num.rename(columns={"r": "r2"}), + suffixes=["_r1", "_r2"], + how="cross", + ) + df_res = df_res[df_res.r1 < df_res.r2] + + df_res["num_triples_both"] = df_res["num_triples_r1"] + df_res["num_triples_r2"] + df_res["frac_triples_both"] = df_res["num_triples_both"] / df.shape[0] + df_res["num_entities_both"] = df_res.apply( + lambda x: len( + np.unique( + np.concatenate( + [ + ent_unique.loc[x["r1"], "both"], + ent_unique.loc[x["r2"], "both"], + ] + ) + ) + ), + axis=1, + ) + df_res = df_res[ + [ + "r1", + "r2", + "num_triples_both", + "frac_triples_both", + "num_entities_both", + "num_h_r1", + "num_h_r2", + "num_t_r1", + "num_t_r2", + ] + ] + for r1_ent in ["head", "tail"]: + for r2_ent in ["head", "tail"]: + df_res[f"jaccard_{r1_ent}_{r2_ent}"] = [ + jaccard_similarity(a, b) + for a, b in zip( + ent_unique.loc[df_res.r1, r1_ent], + ent_unique.loc[df_res.r2, r2_ent], + ) + ] + df_res["jaccard_both"] = [ + jaccard_similarity(a, b) + for a, b in zip( + ent_unique.loc[df_res.r1, "both"], ent_unique.loc[df_res.r2, "both"] + ) + ] + return df_res + + def relational_affinity_ingram( + self, df: pd.DataFrame, min_max_norm: bool = False + ) -> pd.DataFrame: + """ + Compute the similarity between relations based on the approach proposed in + InGram: Inductive Knowledge Graph Embedding via Relation Graphs, + https://arxiv.org/abs/2305.19987. + + Only the pairs of relations witn ``affinity > 0`` are shown in the + returned dataframe. + + :param df: A graph represented as a pd.DataFrame. + Must contain at least three columns `h`, `r`, `t`. + :param min_max_norm: min-max normalization of edge weights. Defaults to False. + + :return: The results dataframe. Contains the following columns: + + - **h_relation** (int): Index of the head relation. + - **t_relation** (int): Index of the tail relation. + - **edge_weight** (float): Weight for the affinity between + the head and the tail relation. + """ + n_entities = df[["h", "t"]].max().max() + 1 + n_rels = df.r.max() + 1 + + hr_freqs = df.groupby(["h", "r"], as_index=False).count() + # normalize by global h frequency + hr_freqs["t"] = hr_freqs["t"] / hr_freqs.groupby("h")["t"].transform("sum") + rt_freqs = df.groupby(["t", "r"], as_index=False).count() + # normalize by global t frequency + rt_freqs["h"] = rt_freqs["h"] / rt_freqs.groupby("t")["h"].transform("sum") + + E_h = coo_array( + (hr_freqs.t, (hr_freqs.h, hr_freqs.r)), + shape=[n_entities, n_rels], + ) + E_t = coo_array( + (rt_freqs.h, (rt_freqs.t, rt_freqs.r)), + shape=[n_entities, n_rels], + ) + + A = (E_h.T @ E_h).toarray() + (E_t.T @ E_t).toarray() + A[np.diag_indices_from(A)] = 0 + + if min_max_norm: + A = (A - np.min(A)) / (np.max(A) - np.min(A)) + + h_rels, t_rels = np.nonzero(A) + return pd.DataFrame( + { + "h_relation": h_rels, + "t_relation": t_rels, + "edge_weight": A[h_rels, t_rels], + } + ) diff --git a/src/kg_topology_toolbox/utils.py b/src/kg_topology_toolbox/utils.py new file mode 100644 index 0000000..64b01fa --- /dev/null +++ b/src/kg_topology_toolbox/utils.py @@ -0,0 +1,95 @@ +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. + +""" +Utility functions +""" + +from multiprocessing import Pool + +import numpy as np +import pandas as pd +from numpy.typing import NDArray +from scipy.sparse import coo_array, csc_array, csr_array + + +def jaccard_similarity( + entities_1: NDArray[np.int32], entities_2: NDArray[np.int32] +) -> float: + """ + Jaccard Similarity function for two sets of entities. + + :param entities_1: the array of IDs for the first set of entities. + :param entities_2: the array of IDs for the second set of entities. + + :return: Jaccard Similarity score for two sets of entities. + """ + intersection = len(np.intersect1d(entities_1, entities_2)) + union = len(entities_1) + len(entities_2) - intersection + return float(intersection / union) + + +def _composition_count_worker( + adj_csr: csr_array, adj_csc: csc_array, tail_shift: int = 0 +) -> pd.DataFrame: + adj_2hop = adj_csr @ adj_csc + adj_composition = (adj_2hop.tocsc() * (adj_csc > 0)).tocoo() + df_composition = pd.DataFrame( + dict( + h=adj_composition.row, + t=adj_composition.col + tail_shift, + n_triangles=adj_composition.data, + ) + ) + return df_composition + + +def composition_count( + df: pd.DataFrame, chunk_size: int, workers: int, directed: bool = True +) -> pd.DataFrame: + """A helper function to compute the composition count of a graph. + + :param df: A graph represented as a pd.DataFrame. Must contain the columns + `h` and `t`. No self-loops should be present in the graph. + :param chunk_size: Size of chunks of columns of the adjacency matrix to be + processed together. + :param workers: Number of workers processing chunks concurrently + :param directed: Boolean flag. If false, bidirectional edges are considered for + triangles by adding the adjacency matrix and its transposed. Defaults to True. + + :return: The results dataframe. Contains the following columns: + + - **h** (int): Index of the head entity. + - **t** (int): Index of the tail entity. + - **n_triangles** (int): Number of compositions for the (h, t) edge. + """ + + adj = coo_array( + (np.ones(len(df)), (df.h, df.t)), + shape=[max(df.max()) + 1, max(df.max()) + 1], + ).astype(np.uint16) + if not directed: + adj = adj + adj.T + n_cols = adj.shape[1] + adj_csr = adj.tocsr() + adj_csc = adj.tocsc() + adj_csc_slices = { + i: adj_csc[:, i * chunk_size : min((i + 1) * chunk_size, n_cols)] + for i in range(int(np.ceil(n_cols / chunk_size))) + } + + if len(adj_csc_slices) > 1 and workers > 1: + with Pool(workers) as pool: + df_composition_list = pool.starmap( + _composition_count_worker, + ( + (adj_csr, adj_csc_slice, i * chunk_size) + for i, adj_csc_slice in adj_csc_slices.items() + ), + ) + else: + df_composition_list = [ + _composition_count_worker(adj_csr, adj_csc_slice, i * chunk_size) + for i, adj_csc_slice in adj_csc_slices.items() + ] + + return pd.concat(df_composition_list) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..9dc9fcb --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. diff --git a/tests/test_edge_topology_toolbox.py b/tests/test_edge_topology_toolbox.py new file mode 100644 index 0000000..a6aab47 --- /dev/null +++ b/tests/test_edge_topology_toolbox.py @@ -0,0 +1,84 @@ +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. + +import numpy as np +import pandas as pd +import pytest + +from kg_topology_toolbox import KGTopologyToolbox + +df = pd.DataFrame( + dict( + h=[0, 0, 0, 1, 2, 2, 1, 2], + t=[1, 1, 2, 2, 0, 0, 1, 2], + r=[0, 1, 0, 1, 0, 1, 1, 0], + ) +) + +tools = KGTopologyToolbox() + + +@pytest.mark.parametrize("return_metapath_list", [True, False]) +def test_small_graph_metrics(return_metapath_list: bool) -> None: + # Define a small graph with all the features tested by + # the edge_topology_toolbox + + # entity degrees statistics + res = tools.edge_degree_cardinality_summary(df) + assert np.allclose(res["h_unique_rel"], [2, 2, 2, 1, 2, 2, 1, 2]) + assert np.allclose(res["h_degree"], [3, 3, 3, 2, 3, 3, 2, 3]) + assert np.allclose(res["h_degree_same_rel"], [2, 1, 2, 2, 2, 1, 2, 2]) + assert np.allclose(res["t_unique_rel"], [2, 2, 2, 2, 2, 2, 2, 2]) + assert np.allclose(res["t_degree"], [3, 3, 3, 3, 2, 2, 3, 3]) + assert np.allclose(res["t_degree_same_rel"], [1, 2, 2, 1, 1, 1, 2, 2]) + assert np.allclose(res["tot_degree"], [4, 4, 5, 4, 3, 3, 4, 5]) + assert np.allclose(res["tot_degree_same_rel"], [2, 2, 3, 2, 2, 1, 3, 3]) + + # triple cardinality + assert res["triple_cardinality"].tolist() == [ + "M:M", + "M:M", + "M:M", + "M:M", + "M:M", + "M:M", + "M:M", + "M:M", + ] + assert res["triple_cardinality_same_rel"].tolist() == [ + "1:M", + "M:1", + "M:M", + "1:M", + "1:M", + "1:1", + "M:M", + "M:M", + ] + + # relation pattern symmetry + res = tools.edge_pattern_summary(df, return_metapath_list=return_metapath_list) + assert np.allclose( + res["is_loop"], [False, False, False, False, False, False, True, True] + ) + assert np.allclose( + res["is_symmetric"], [False, False, True, False, True, False, False, False] + ) + # relation pattern inverse + assert np.allclose( + res["has_inverse"], [False, False, True, False, False, True, False, False] + ) + assert np.allclose(res["n_inverse_relations"], [0, 0, 1, 0, 0, 1, 0, 0]) + # relation pattern inference + assert np.allclose( + res["has_inference"], [True, True, False, False, True, True, False, False] + ) + assert np.allclose(res["n_inference_relations"], [1, 1, 0, 0, 1, 1, 0, 0]) + + # relation_pattern_composition & metapaths + assert np.allclose( + res["has_composition"], [False, False, True, False, False, False, False, False] + ) + assert np.allclose(res["n_triangles"], [0, 0, 2, 0, 0, 0, 0, 0]) + assert np.allclose(res["n_undirected_triangles"], [3, 3, 2, 6, 2, 2, 0, 0]) + if return_metapath_list: + assert res["metapath_list"][2] == ["0-1", "1-1"] diff --git a/tests/test_node_topology_toolbox.py b/tests/test_node_topology_toolbox.py new file mode 100644 index 0000000..371180c --- /dev/null +++ b/tests/test_node_topology_toolbox.py @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. + +import numpy as np +import pandas as pd +import pytest + +from kg_topology_toolbox import KGTopologyToolbox + +df = pd.DataFrame( + dict( + h=[0, 0, 0, 1, 2, 2, 2], + t=[1, 1, 2, 2, 0, 0, 2], + r=[0, 1, 0, 1, 0, 1, 1], + ) +) + +tools = KGTopologyToolbox() + + +@pytest.mark.parametrize("return_relation_list", [True, False]) +def test_small_graph_metrics(return_relation_list: bool) -> None: + # Define a small graph with all the features tested by + # the node_topology_toolbox + + # entity degrees statistics + res = tools.node_degree_summary(df, return_relation_list=return_relation_list) + assert np.allclose(res["h_degree"], [3, 1, 3]) + assert np.allclose(res["t_degree"], [2, 2, 3]) + assert np.allclose(res["tot_degree"], [5, 3, 5]) + assert np.allclose(res["h_unique_rel"], [2, 1, 2]) + assert np.allclose(res["t_unique_rel"], [2, 2, 2]) + assert np.allclose(res["n_loops"], [0, 0, 1]) + if return_relation_list: + assert [x.tolist() for x in res["h_rel_list"].to_list()] == [ + [0, 1], + [1], + [0, 1], + ] + assert [x.tolist() for x in res["t_rel_list"].to_list()] == [ + [0, 1], + [0, 1], + [0, 1], + ] diff --git a/tests/test_relation_topology_toolbox.py b/tests/test_relation_topology_toolbox.py new file mode 100644 index 0000000..cdbdaa7 --- /dev/null +++ b/tests/test_relation_topology_toolbox.py @@ -0,0 +1,89 @@ +# Copyright (c) 2023 Graphcore Ltd. All rights reserved. + +from typing import List + +import numpy as np +import pandas as pd +import pytest + +from kg_topology_toolbox import KGTopologyToolbox + +df = pd.DataFrame( + dict( + h=[0, 0, 0, 1, 2, 2, 2, 3, 3, 4], + t=[1, 1, 2, 2, 0, 3, 4, 2, 4, 3], + r=[0, 1, 0, 1, 0, 1, 1, 0, 0, 1], + ) +) + +tools = KGTopologyToolbox() + + +def test_small_graph_metrics() -> None: + # Define a small graph on five nodes with all the features tested by + # the relation_topology_toolbox + + dcs = tools.aggregate_by_relation(tools.edge_degree_cardinality_summary(df)) + eps = tools.aggregate_by_relation( + tools.edge_pattern_summary(df, return_metapath_list=True) + ) + + assert np.allclose(dcs["num_triples"], [5, 5]) + assert np.allclose(dcs["frac_triples"], [0.5, 0.5]) + assert np.allclose(dcs["unique_h"], [3, 4]) + assert np.allclose(dcs["unique_t"], [4, 4]) + + # entity_degree_statistics + assert np.allclose(dcs["h_degree_mean"], [2.6, 2.2]) + assert np.allclose(dcs["t_degree_mean"], [2.2, 2.2]) + assert np.allclose(dcs["tot_degree_mean"], [3.6, 3.2]) + + # triple_relation_cardinality + assert np.allclose(dcs["triple_cardinality_1:M_frac"], [1 / 5, 0]) + assert np.allclose(dcs["triple_cardinality_M:1_frac"], [0, 2 / 5]) + assert np.allclose(dcs["triple_cardinality_M:M_frac"], [4 / 5, 3 / 5]) + assert np.allclose(dcs["triple_cardinality_same_rel_1:1_frac"], [1 / 5, 2 / 5]) + assert np.allclose(dcs["triple_cardinality_same_rel_1:M_frac"], [2 / 5, 1 / 5]) + assert np.allclose(dcs["triple_cardinality_same_rel_M:1_frac"], [0, 1 / 5]) + assert np.allclose(dcs["triple_cardinality_same_rel_M:M_frac"], [2 / 5, 1 / 5]) + + # relation_pattern_loop + assert np.allclose(eps["is_loop_frac"], [0, 0]) + + # relation_pattern_symmetric + assert np.allclose(eps["is_symmetric_frac"], [2 / 5, 0]) + + # relation_pattern_inverse + assert np.allclose(eps["has_inverse_frac"], [2 / 5, 2 / 5]) + assert eps["inverse_edge_types_unique"][0] == [0, 1] + assert eps["inverse_edge_types_unique"][1] == [0] + + # relation_pattern_composition + assert np.allclose(eps["has_composition_frac"], [2 / 5, 2 / 5]) + assert np.allclose(eps["has_undirected_composition_frac"], [1, 1]) + assert eps["metapath_list_unique"][0] == ["0-1", "1-1"] + assert eps["metapath_list_unique"][1] == ["1-0", "1-1"] + + # relation_pattern_inference + assert np.allclose(eps["has_inference_frac"], [1 / 5, 1 / 5]) + assert eps["inference_edge_types_unique"][0] == [0, 1] + assert eps["inference_edge_types_unique"][1] == [0, 1] + + +def test_jaccard_similarity() -> None: + # jaccard_similarity_relation_sets + res = tools.jaccard_similarity_relation_sets(df) + assert np.allclose(res["jaccard_head_head"], [2 / 5]) + assert np.allclose(res["jaccard_tail_tail"], [3 / 5]) + assert np.allclose(res["jaccard_head_tail"], [2 / 5]) + assert np.allclose(res["jaccard_tail_head"], [1]) + assert np.allclose(res["jaccard_both"], [1]) + + +@pytest.mark.parametrize( + "min_max_norm,expected", [(True, [1, 1]), (False, [7 / 6, 7 / 6])] +) +def test_ingram_affinity(min_max_norm: bool, expected: List[float]) -> None: + # relational_affinity_ingram + res = tools.relational_affinity_ingram(df, min_max_norm) + assert np.allclose(res["edge_weight"], expected)