.. _data_structures_graph_group: The :py:class:`~dantro.groups.graph.GraphGroup` =============================================== The :py:class:`~dantro.groups.graph.GraphGroup` is specialized on managing and handling graph-related data. The group defines a graph via data groups and data containers that store nodes, edges, and optionally their properties. .. contents:: :local: :depth: 2 ---- Creating a GraphGroup --------------------- The :py:class:`~dantro.groups.graph.GraphGroup` holds the following, customizable variables that describe in which containers or attributes to find the info on the nodes and edges: - ``_GG_node_container = "nodes"``: The name of the container or group (``node_container``) containing the node data - ``_GG_edge_container = "edges"``: The name of the container or group (``edge_container``) containing the edge data - ``_GG_attr_directed = "directed"``: The :py:class:`~dantro.groups.graph.GraphGroup` attribute (boolean) describing whether the graph is directed or not - ``_GG_attr_parallel = "parallel"``: The :py:class:`~dantro.groups.graph.GraphGroup` attribute (boolean) describing whether the graph allows for parallel edges or not - ``_GG_attr_edge_container_is_transposed = "edge_container_is_transposed"``: The :py:class:`~dantro.groups.graph.GraphGroup` attribute (boolean) describing whether the edge container is transposed, i.e., has the shape ``(edge-tuple-size, edge-number)`` - ``_GG_attr_keep_dim = "keep_dim"``: The :py:class:`~dantro.groups.graph.GraphGroup` attribute (iterable) describing which dimensions are not to be squeezed during data selection. If you do not change anything, the default values are taken. The :py:class:`~dantro.groups.graph.GraphGroup` only holds and manages graph data but itself is not a graph, in the sense that no functionality such as finding the neighborhood of a node is implemented there. Instead, the :py:class:`~dantro.groups.graph.GraphGroup` uses the `networkx `_ library and its interface for creating ``Graph`` objects. In the following, an overview is given how graphs can be created from a :py:class:`~dantro.groups.graph.GraphGroup`. Creating Graphs from a GraphGroup --------------------------------- The :py:class:`~dantro.groups.graph.GraphGroup` contains the :py:meth:`~dantro.groups.graph.GraphGroup.create_graph` method that creates a graph from the containers and groups that are part of the :py:class:`~dantro.groups.graph.GraphGroup` together with information provided by the :py:class:`~dantro.groups.graph.GraphGroup` attributes. However, the function also allows you to *explicitly* set graph properties such as whether the graph should be directed or allow for parallel edges. The function returns a `networkx graph object `_ corresponding to the provided data and information. The :py:meth:`~dantro.groups.graph.GraphGroup.create_graph` function further allows you to optionally set node and edge properties by specifying ``node_props`` or ``edge_props`` lists. Any data can be pre-selected using the ``sel`` and ``isel`` arguments. The selectors are applied to all data involved, i.e., to node, edge, as well as property data. It is also possible to provide both ``sel`` and ``isel`` as long as the intersection of both key-sets is empty. .. warning:: **Invalid keys in** ``sel`` **and** ``isel`` **are ignored silently.** This means that the node, edge, and property data need not have the same set of dimensions in order to apply a selection. Moreover, all dimensions of size 1 are squeezed, hence no selection has to be specified in such scenarios, i.e. when the selection is unambiguous. If you have node/edge data that changes over time, you can select along the ``time`` dimension directly via the ``at_times`` or the ``at_time_idx`` argument. This sets or overwrites the respective entry in the ``sel`` or ``isel`` dicts. The following example demonstrates the graph creation: Let us assume that we have a graph with static nodes and dynamic edges, each with dynamic properties. The dynamic data, stored as :py:class:`~dantro.groups.time_series.TimeSeriesGroup`, is given for two points in time. The resulting data tree looks as follows: .. literalinclude:: ../../../tests/test_doc_examples.py :language: python :start-after: ### Start -- groups_graphgroup_datatree :end-before: ### End ---- groups_graphgroup_datatree :dedent: 4 Let's now create a graph from the :py:class:`~dantro.groups.graph.GraphGroup`: .. literalinclude:: ../../../tests/test_doc_examples.py :language: python :start-after: ### Start -- groups_graphgroup_create_graph :end-before: ### End ---- groups_graphgroup_create_graph :dedent: 4 .. hint:: Graph creation might fail for graphs with a single node (edge) due to the node (edge) dimension (of size 1) being squeezed out. It is therefore strongly recommended to specify the node and edge dimension names in the ``_GG_attr_keep_dim`` group attribute. Alternatively, they can be specified via the ``keep_dim`` argument in :py:meth:`~dantro.groups.graph.GraphGroup.create_graph`, :py:meth:`~dantro.groups.graph.GraphGroup.set_node_property`, and :py:meth:`~dantro.groups.graph.GraphGroup.set_edge_property`. Setting Graph Properties ------------------------ If you already have a ``networkx`` graph object, you can set node or edge properties using the :py:meth:`~dantro.groups.graph.GraphGroup.set_node_property` or :py:meth:`~dantro.groups.graph.GraphGroup.set_edge_property` function. Properties can be added from: - data stored *inside* the :py:class:`~dantro.groups.graph.GraphGroup`: Here, the ``name`` argument specifies the name of the data container or group which stores the property. - *external* data: see :ref:`Loading External Data as Graph Property ` In both cases, ``name`` will be the name of the node or edge property in the ``networkx`` graph. Again, the data can be pre-selected using the ``sel``, ``isel``, ``at_time``, and ``at_time_idx`` arguments. In the example below, the ``other_edge_prop`` data stored inside the graph group is added as edge property. Note that time specification is required here, even though ``other_edge_prop`` is one-dimensional, because ``edge_container`` contains edge data for multiple times. .. literalinclude:: ../../../tests/test_doc_examples.py :language: python :start-after: ### Start -- groups_graphgroup_set_properties :end-before: ### End ---- groups_graphgroup_set_properties :dedent: 4 Node properties can be added analogously. .. note:: Node (edge) properties can only be added for those nodes (edges) that are available in the ``node_container`` (``edge_container``). By default, property data is assumed to be aligned with the :py:class:`~dantro.groups.graph.GraphGroup`\ s node (edge) data. However, it can be aligned with the latter via `xarray.align `_ by setting ``align`` to ``True`` in :py:meth:`~dantro.groups.graph.GraphGroup.set_node_property` (:py:meth:`~dantro.groups.graph.GraphGroup.set_edge_property`). The indexes of the ``node_container`` (``edge_container``) are used for the alignment in each dimension. If the class variable ``_GG_WARN_UPON_BAD_ALIGN`` is set to ``True`` (default: ``True``), warnings on possible pitfalls are given. .. _loading_ext_prop_data: Loading External Data as Graph Property ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you want to add a graph property from data that is *not* stored inside the :py:class:`~dantro.groups.graph.GraphGroup` (e.g., you have pre-processed some data), this can be realized in two different ways: - Making use of the :py:attr:`~dantro.groups.graph.GraphGroup.property_maps`. After registering external data with a key using :py:meth:`~dantro.groups.graph.GraphGroup.register_property_map`, it will be permanently available via the provided key, i.e., the key can be passed as ``name`` in the ``set_*_property`` functions. - Loading the external data *directly* by passing it via the ``data`` argument in the respective ``set_*_property`` function. The ``name`` argument then *sets* the name of the property. Have a look at a small example where some external data ``ext_data`` is added to the graph as a node property: .. literalinclude:: ../../../tests/test_doc_examples.py :language: python :start-after: ### Start -- groups_graphgroup_property_maps :end-before: ### End ---- groups_graphgroup_property_maps :dedent: 4