Source code for classes.hyp_view

# Copyright © 2024 Battelle Memorial Institute
# All rights reserved.
from __future__ import annotations

import warnings
from copy import deepcopy
from collections import UserList
import pandas as pd

warnings.filterwarnings("default", category=DeprecationWarning)

__all__ = ["HypergraphView"]


[docs] class HypergraphView(object): """ Wrapper for Property and Incidence Stores holding structural and metadata for hypergraph. Provides methods matching EntitySet methods in previous versions. Only nodes and edges in the Incidence Store will be seeable in this view. """ def __init__(self, incidence_store, level, property_store=None): """ _summary_ Parameters ---------- incidence_store : IncidenceStore All incidence pairs stored as a dataframe level : int Which type of store: 0 = Edges, 1 = Nodes, 2 = Incidences property_store : PropertyStore, optional Properties assigned to object in this view, by default None """ self._incidence_store = incidence_store self._level = level self._property_store = property_store ### incidence store needs index or columns if level == 0: self._items = self._incidence_store.edges elif level == 1: self._items = self._incidence_store.nodes elif level == 2: self._items = self._incidence_store @property def items(self): """ If level 0 or 1, the list of edges or nodes, respectively. If level 2, the IncidenceStore Returns ------- IncidenceStore | array """ return set(self._items) @property def level(self): """ The type of store: 0 = Edges, 1 = Nodes, 2 = Incidences Returns ------- int """ return self._level @property def incidence_store(self): """ IncidenceStore Returns ------- IncidenceStore """ return self._incidence_store @property def property_store(self): """ PropertyStore Returns ------- PropertyStore """ return self._property_store @property def default_weight(self): """ Default weight for an edge, node, or incidence Returns ------- int | float """ return self._property_store._default_weight @property def to_dataframe(self): """ Dataframe of properties (user defined and default) for all items in the HypergraphView. Creates a properties dataframe of non-user-defined items with default values. Combines user-defined and non-user-defined properties into one dataframe. Returns ------- pd.DataFrame """ df = self.user_defined_properties.copy(deep=True) ### deep copy dictionaries in the misc_properties column temp = [deepcopy(d) for d in df.misc_properties.values] df.misc_properties = temp non_user_defined_items = list( set(self._items).difference(self.user_defined_properties.index) ) # skip combining df and non_user_defined_items if non_user_defined_items is empty if not non_user_defined_items: return df.loc[list(self._items)] default_data = self.property_store.default_properties default_data.update({"misc_properties": [{}]}) non_user_defined_properties = pd.DataFrame( index=non_user_defined_items, data=default_data ) return pd.concat([df, non_user_defined_properties]).loc[list(self.items)] @property ### will remove this later def dataframe(self): """ All properties for objects in the HypergraphView. Same as to_dataframe. Returns ------- pd.DataFrame """ return self.to_dataframe @property def properties(self): """ All properties for objects in the HypergraphView. Same as to_dataframe. Returns ------- pd.DataFrame """ return self.to_dataframe @property def user_defined_properties(self): """ User-defined properties. Does not include items in the HypergraphView that the user has not explicitly defined properties for. Returns ------- pd.DataFrame """ return self._property_store.properties @property def memberships(self): """ See :term:`memberships` Returns ------- dict """ if self._level == 1 or self._level == 2: return self.incidence_store.memberships else: return {}
[docs] def is_empty(self): """ Returns true if HypergraphView has no edges, nodes, or incidences depending on the level; otherwise, false Returns ------- bool """ return len(self._items) == 0
@property def incidence_dict(self): """ incidence dictionary Returns ------- dict | None """ if self._level in [0, 2]: return self.incidence_store.elements @property def elements(self): """ See :term:`elements` Returns ------- dict """ if self._level == 0 or self._level == 2: return self.incidence_store.elements else: return {} def __iter__(self): """ Defined by level store iterate over the associated level in the incidence store """ return iter(self._items) def __len__(self): """ Defined by incidence store """ return len(self._items) def __contains__(self, item): """ Defined by incidence store Parameters ---------- item : _type_ _description_ Returns ------- bool """ return item in self._items def __call__(self): """ Returns ------- iterator """ return iter(self._items) def __getitem__(self, uid): """ Returns incident objects (neighbors in bipartite graph) to keyed object as an AttrList. Returns AttrList associated with item, attributes/properties may be called from AttrList If level 0 - elements, if level 1 - memberships, if level 2 - TBD - uses getitem from stores and links to props These calls will go to the neighbors method in the incidence store Parameters ---------- uid : hashable unique identifier for object in HypergraphView Returns ------- AttrList UserList of incident objects (neighbors in the bipartite graph) """ if uid in self._items: neighbors = self.incidence_store.neighbors(self.level, uid) return AttrList(uid, self, initlist=neighbors)
[docs] def set_defaults(self, defaults_dict): """ Creates or updates default values in PropertyStore associated with this HypergraphView. Does not overwrite existing user-defined properties Parameters ---------- defaults_dict : dict Dictionary of prop_names to their default values Returns ------- None """ self.property_store.set_defaults(defaults_dict)
class AttrList(UserList): """Custom list wrapper for integrating PropertyStore data with IncidenceStore relationships Parameters ---------- hypergraph_view : hypernetx.HypergraphView uid : str | int Returns ------- AttrList """ def __init__(self, uid, hypergraph_view, initlist=None): self._hypergraph_view = hypergraph_view self._uid = uid self._level = hypergraph_view._level if initlist is None: initlist = hypergraph_view._incidence_store.neighbors(self._level, uid) super().__init__(initlist) def __getattr__(self, attr: str): """Get attribute value from properties of :attr:`entity` Parameters ---------- attr : str Returns ------- any attribute value; None if not found """ if attr == "memberships": if self._level == 1: return self.data else: return [] elif attr == "elements": if self._level == 0: return self.data else: return [] elif attr == "properties": return self._hypergraph_view._property_store.get_properties(self._uid) else: return self._hypergraph_view._property_store.get_property(self._uid, attr) def __setattr__(self, attr, val): """Set attribute value in properties Parameters ---------- attr : str val : any """ if attr in ["_hypergraph_view", "_uid", "data"]: object.__setattr__(self, attr, val) else: self._hypergraph_view._property_store.set_property(self._uid, attr, val) def flatten(my_dict): """Recursive method to flatten dictionary for returning properties as a dictionary instead of a Series, from [StackOverflow](https://stackoverflow.com/a/71952620) Redundant keys are kept in the order of hierarchy (first seen kept by default) """ result = {} for key, value in my_dict.items(): if isinstance(value, dict): temp = flatten(value) temp.update(result) result = temp else: result[key] = value return result