Skip to content

Commit

Permalink
adjacency_matrix methods added (GH25) (#43)
Browse files Browse the repository at this point in the history
* adjacency_matrix methods added (GH25)

* extra tests

closes #25
  • Loading branch information
venaturum authored Nov 20, 2021
1 parent 59ea94d commit f3acc04
Show file tree
Hide file tree
Showing 8 changed files with 455 additions and 3 deletions.
3 changes: 2 additions & 1 deletion docs/reference/accessors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ Accessors
ArrayAccessor.coverage
ArrayAccessor.complement
ArrayAccessor.contains
ArrayAccessor.split
ArrayAccessor.split
ArrayAccessor.adjacency_matrix
2 changes: 1 addition & 1 deletion docs/reference/package.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ Top level functions
split
lookup
join

adjacency_matrix
2 changes: 2 additions & 0 deletions docs/release_notes/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Release notes
Added the following methods

- :func:`piso.split`
- :func:`piso.adjacency_matrix`
- :meth:`ArrayAccessor.split() <piso.accessor.ArrayAccessor.split>`
- :meth:`ArrayAccessor.adjacency_matrix() <piso.accessor.ArrayAccessor.adjacency_matrix>`

- removed :func:`piso.get_indexer` in favour of :meth:`pandas.IntervalIndex.get_indexer`

Expand Down
1 change: 1 addition & 0 deletions piso/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from piso.graph import adjacency_matrix
from piso.intervalarray import (
complement,
contains,
Expand Down
10 changes: 9 additions & 1 deletion piso/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pandas as pd

import piso.docstrings.accessor as docstrings
from piso import intervalarray
from piso import graph, intervalarray
from piso._decorators import Appender


Expand Down Expand Up @@ -170,6 +170,14 @@ def split(self, x):
x,
)

@Appender(docstrings.adjacency_matrix_docstring, join="\n", indents=1)
def adjacency_matrix(self, edges="intersect", include_index=True):
return graph.adjacency_matrix(
self._interval_array,
edges=edges,
include_index=include_index,
)


def _register_accessors():
_register_accessor("piso", pd.IntervalIndex)(ArrayAccessor)
Expand Down
60 changes: 60 additions & 0 deletions piso/docstrings/accessor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from piso.graph import adjacency_matrix

union_examples = """
Examples
-----------
Expand Down Expand Up @@ -832,3 +834,61 @@ def join_params(list_of_param_strings):
closed='neither',
dtype='interval[float64]')
"""


adjacency_matrix_docstring = """
Returns a 2D array (or dataframe) of boolean values indicating edges between nodes in a graph.
The set of nodes correspond to intervals and the edges are defined by the relationship
defined by the *edges* parameter.
Note that the diagonal is defined with False values by default.
Parameters
----------
edges : {"intersect", "disjoint"}, default "intersect"
Defines the relationship that edges between nodes represent.
include_index : bool, default True
If True then a :class:`pandas.DataFrame`, indexed by the intervals, is returned.
If False then a :class:`numpy.ndarray` is returned.
Returns
-------
:class:`pandas.DataFrame` or :class:`numpy.ndarray`
Boolean valued, symmetrical, with False along diagonal.
Examples
---------
>>> import pandas as pd
>>> import piso
>>> piso.register_accessors()
>>> arr = pd.arrays.IntervalArray.from_tuples(
... [(0,4), (3,6), (5, 7), (8,9), (9,10)],
... closed="both",
... )
>>> arr.piso.adjacency_matrix()
[0, 4] [3, 6] [5, 7] [8, 9] [9, 10]
[0, 4] False True False False False
[3, 6] True False True False False
[5, 7] False True False False False
[8, 9] False False False False True
[9, 10] False False False True False
>>> arr.piso.adjacency_matrix(arr, include_index=False)
array([[False, True, False, False, False],
[ True, False, True, False, False],
[False, True, False, False, False],
[False, False, False, False, True],
[False, False, False, True, False]])
>>> arr.piso.adjacency_matrix(arr, edges="disjoint")
[0, 4] [3, 6] [5, 7] [8, 9] [9, 10]
[0, 4] False False True True True
[3, 6] False False False True True
[5, 7] True False False True True
[8, 9] True True True False False
[9, 10] True True True False False
"""
89 changes: 89 additions & 0 deletions piso/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import numpy as np
import pandas as pd
from pandas.core.indexes import interval


def adjacency_matrix(interval_array, edges="intersect", include_index=True):
"""
Returns a 2D array (or dataframe) of boolean values indicating edges between nodes in a graph.
The set of nodes correspond to intervals and the edges are defined by the relationship
defined by the *edges* parameter.
Note that the diagonal is defined with False values by default.
Parameters
----------
interval_array : :class:`pandas.arrays.IntervalArray` or :class:`pandas.IntervalIndex`
Contains the intervals.
edges : {"intersect", "disjoint"}, default "intersect"
Defines the relationship that edges between nodes represent.
include_index : bool, default True
If True then a :class:`pandas.DataFrame`, indexed by the intervals, is returned.
If False then a :class:`numpy.ndarray` is returned.
Returns
-------
:class:`pandas.DataFrame` or :class:`numpy.ndarray`
Boolean valued, symmetrical, with False along diagonal.
Examples
---------
>>> import pandas as pd
>>> import piso
>>> arr = pd.arrays.IntervalArray.from_tuples(
... [(0,4), (3,6), (5, 7), (8,9), (9,10)],
... closed="both",
... )
>>> piso.adjacency_matrix(arr)
[0, 4] [3, 6] [5, 7] [8, 9] [9, 10]
[0, 4] False True False False False
[3, 6] True False True False False
[5, 7] False True False False False
[8, 9] False False False False True
[9, 10] False False False True False
>>> piso.adjacency_matrix(arr, include_index=False)
array([[False, True, False, False, False],
[ True, False, True, False, False],
[False, True, False, False, False],
[False, False, False, False, True],
[False, False, False, True, False]])
>>> piso.adjacency_matrix(arr, edges="disjoint")
[0, 4] [3, 6] [5, 7] [8, 9] [9, 10]
[0, 4] False False True True True
[3, 6] False False False True True
[5, 7] True False False True True
[8, 9] True True True False False
[9, 10] True True True False False
"""
if edges == "intersect":
result = _adj_mat_intersection(interval_array)
elif edges == "disjoint":
result = ~_adj_mat_intersection(interval_array, fill_diagonal=False)
else:
raise ValueError(f"Invalid value for edges parameter: {edges}")

if include_index:
result = pd.DataFrame(result, index=interval_array, columns=interval_array)

return result


def _adj_mat_intersection(interval_array, fill_diagonal=True):
result = np.greater.outer(
interval_array.right, interval_array.left
) & np.less.outer(interval_array.left, interval_array.right)
if interval_array.closed == "both":
result = (
result
| np.equal.outer(interval_array.right, interval_array.left)
| np.equal.outer(interval_array.left, interval_array.right)
)
if fill_diagonal:
np.fill_diagonal(result, False)
return result
Loading

0 comments on commit f3acc04

Please sign in to comment.