hiveplotlib 0.26.2__py3-none-any.whl → 0.28.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. hiveplotlib/__init__.py +4 -1
  2. hiveplotlib/axis.py +4 -1
  3. hiveplotlib/converters.py +141 -16
  4. hiveplotlib/datasets/international_trade.py +2 -2
  5. hiveplotlib/datasets/toy_hive_plots.py +395 -26
  6. hiveplotlib/datasets/toy_p2cps.py +2 -2
  7. hiveplotlib/datasets/trade_data_harvard_growth_lab/international_exports_metadata_2019_8112.json +1 -1
  8. hiveplotlib/edges.py +56 -23
  9. hiveplotlib/exceptions/hive_plot.py +11 -0
  10. hiveplotlib/graph_features/__init__.py +858 -0
  11. hiveplotlib/graph_features/networkx/__init__.py +103 -0
  12. hiveplotlib/graph_features/networkx/_helpers.py +177 -0
  13. hiveplotlib/graph_features/networkx/edge_metrics.py +259 -0
  14. hiveplotlib/graph_features/networkx/node_metrics.py +913 -0
  15. hiveplotlib/hiveplot.py +1086 -490
  16. hiveplotlib/hiveplot_matrix.py +2274 -0
  17. hiveplotlib/node.py +26 -13
  18. hiveplotlib/p2cp.py +20 -17
  19. hiveplotlib/utils.py +221 -1
  20. hiveplotlib/viz/base.py +16 -5
  21. hiveplotlib/viz/bokeh.py +15 -15
  22. hiveplotlib/viz/datashader.py +106 -49
  23. hiveplotlib/viz/hiveplot_matrix.py +657 -0
  24. hiveplotlib/viz/holoviews.py +12 -12
  25. hiveplotlib/viz/matplotlib.py +8 -7
  26. hiveplotlib/viz/plotly.py +37 -22
  27. {hiveplotlib-0.26.2.dist-info → hiveplotlib-0.28.0.dist-info}/METADATA +37 -25
  28. hiveplotlib-0.28.0.dist-info/RECORD +37 -0
  29. {hiveplotlib-0.26.2.dist-info → hiveplotlib-0.28.0.dist-info}/WHEEL +1 -1
  30. {hiveplotlib-0.26.2.dist-info → hiveplotlib-0.28.0.dist-info}/licenses/LICENSE +2 -2
  31. hiveplotlib-0.26.2.dist-info/RECORD +0 -30
  32. {hiveplotlib-0.26.2.dist-info → hiveplotlib-0.28.0.dist-info}/top_level.txt +0 -0
hiveplotlib/__init__.py CHANGED
@@ -10,7 +10,10 @@ from hiveplotlib.hiveplot import ( # noqa: F401
10
10
  SUPPORTED_VIZ_BACKENDS,
11
11
  BaseHivePlot,
12
12
  HivePlot,
13
- hive_plot_n_axes,
13
+ )
14
+ from hiveplotlib.hiveplot_matrix import ( # noqa: F401
15
+ HPM_SUPPORTED_VIZ_BACKENDS,
16
+ HivePlotMatrix,
14
17
  )
15
18
  from hiveplotlib.node import Node, NodeCollection # noqa: F401
16
19
  from hiveplotlib.p2cp import P2CP, p2cp_n_axes # noqa: F401
hiveplotlib/axis.py CHANGED
@@ -6,6 +6,7 @@ Definition of ``Axis`` instance and helper static methods for generating and wor
6
6
 
7
7
  from typing import Hashable, Optional
8
8
 
9
+ import numpy as np
9
10
  import pandas as pd
10
11
 
11
12
  from hiveplotlib.utils import polar2cartesian
@@ -93,7 +94,9 @@ class Axis:
93
94
  self.sorting_variable = None
94
95
 
95
96
  # hold all the cartesian coordinates, polar rho, corresponding labels, and node metadata in a pandas dataframe
96
- self.node_placements = pd.DataFrame(columns=["x", "y", "unique_id", "rho"])
97
+ self.node_placements = pd.DataFrame(
98
+ columns=np.array(["x", "y", "unique_id", "rho"])
99
+ )
97
100
 
98
101
  # hold the current vmin and vmax used to place nodes on an axis in a ``HivePlot`` instance.
99
102
  self.vmin = None
hiveplotlib/converters.py CHANGED
@@ -4,38 +4,163 @@
4
4
  Converters from various data structures to ``hiveplotlib``-ready structures.
5
5
  """
6
6
 
7
- from typing import Tuple
7
+ from typing import Optional
8
8
 
9
9
  import pandas as pd
10
10
 
11
- from hiveplotlib import Edges, Node, NodeCollection
12
- from hiveplotlib.node import node_collection_from_node_list
11
+ from hiveplotlib import Edges, NodeCollection
13
12
 
14
- # say the graph type instead of calling `nx.Graph` to keep networkx dep out of this file.
15
- NetworkXGraph = "networkx.classes.graph.Graph instance"
13
+ try:
14
+ import networkx as nx
15
+ except ImportError as ie: # pragma: no cover
16
+ msg = "`networkx` dependencies not installed, but can be installed by running `pip install hiveplotlib[networkx]`"
17
+ raise ImportError(msg) from ie
16
18
 
17
19
 
18
20
  def networkx_to_nodes_edges(
19
- graph: NetworkXGraph,
21
+ graph: nx.Graph,
20
22
  unique_id_name: str = "unique_id",
21
23
  check_uniqueness: bool = True,
22
- ) -> Tuple[NodeCollection, Edges]: # type: ignore
24
+ ) -> tuple[NodeCollection, Edges]:
23
25
  """
24
26
  Take a ``networkx`` graph and return ``hiveplotlib``-friendly data structures.
25
27
 
26
28
  Specifically, returns a ``NodeCollection`` instance of node data and an ``Edges`` instance of
27
29
  edge data. These outputs can be fed directly into :py:func:`~hiveplotlib.HivePlot`.
28
30
 
29
- :param graph: ``networkx`` graph.
31
+ :param graph: ``networkx`` graph (any of :py:class:`~networkx.Graph`, :py:class:`~networkx.DiGraph`,
32
+ :py:class:`~networkx.MultiGraph`, or :py:class:`~networkx.MultiDiGraph`).
30
33
  :param unique_id_name: name to use for unique IDs.
31
34
  :param check_uniqueness: whether or not to check that provided ``Node`` instances have unique IDs.
32
- :return: ``list`` of ``Node`` instances, ``(n, 2)`` ``np.ndarray`` of edges.
35
+ :return: a ``(NodeCollection, Edges)`` pair of ``hiveplotlib`` data structures.
33
36
  """
34
- nodes = [Node(unique_id=i, data=data) for i, data in list(graph.nodes.data())]
35
- edge_keys = pd.DataFrame(list(graph.edges.keys()), columns=["from", "to"])
36
- edge_values = pd.DataFrame(list(graph.edges.values()))
37
- return node_collection_from_node_list(
38
- node_list=nodes,
39
- unique_id_name=unique_id_name,
37
+ node_data = []
38
+ for node, attributes in graph.nodes(data=True):
39
+ node_data.append({unique_id_name: node, **attributes})
40
+
41
+ node_df = pd.DataFrame(node_data)
42
+ edge_df = nx.to_pandas_edgelist(graph, source="from", target="to")
43
+ return NodeCollection(
44
+ data=node_df,
45
+ unique_id_column=unique_id_name,
40
46
  check_uniqueness=check_uniqueness,
41
- ), Edges(data=edge_keys.join(edge_values))
47
+ ), Edges(data=edge_df)
48
+
49
+
50
+ def nodes_edges_to_networkx(
51
+ nodes: NodeCollection,
52
+ edges: Edges,
53
+ *,
54
+ directed: bool = True,
55
+ multigraph: bool = True,
56
+ tag_attribute_name: str = "tag",
57
+ source_attribute_name: Optional[str] = None,
58
+ ) -> nx.Graph:
59
+ """
60
+ Convert a (``NodeCollection``, ``Edges``) pair to a ``networkx`` graph.
61
+
62
+ The concrete return type is determined by ``directed`` and ``multigraph``:
63
+
64
+ - ``directed=True``, ``multigraph=True`` → :py:class:`networkx.MultiDiGraph` (default).
65
+ - ``directed=True``, ``multigraph=False`` → :py:class:`networkx.DiGraph`.
66
+ - ``directed=False``, ``multigraph=True`` → :py:class:`networkx.MultiGraph`.
67
+ - ``directed=False``, ``multigraph=False`` → :py:class:`networkx.Graph`.
68
+
69
+ The defaults preserve duplicate edges faithfully and assume directed edges.
70
+
71
+ All non-(from / to) ``Edges`` DataFrame columns become edge attributes on the resulting graph. All
72
+ non-``unique_id_column`` node DataFrame columns become node attributes. The visualization-only attributes
73
+ ``node_viz_kwargs`` / ``edge_viz_kwargs`` are *not* included.
74
+
75
+ Multi-tag :py:class:`hiveplotlib.Edges`: when more than one tag is present, the original tag is written as an edge
76
+ attribute under ``tag_attribute_name`` (default ``"tag"``). If that name collides with an existing column on any
77
+ tag's edge DataFrame, a :class:`ValueError` is raised. Single-tag input does not write a tag attribute.
78
+
79
+ Optional source-row annotation: pass a string for ``source_attribute_name`` to tag each edge with a
80
+ ``(tag, row_index)`` tuple pointing back to its source row in the input :py:class:`hiveplotlib.Edges`. This is
81
+ useful for joining graph-level computations (e.g. edge metrics) back onto the original edge DataFrame (including
82
+ when there are multiple parallel edges in a multigraph). Default ``None`` skips this annotation.
83
+
84
+ .. note::
85
+ When ``multigraph=False`` but duplicate ``(from, to)`` pairs exist, ``networkx``'s :py:class:`networkx.Graph` /
86
+ :py:class:`networkx.DiGraph` will silently merge duplicates (last write wins). For an undirected
87
+ :py:class:`networkx.Graph`, reciprocal ``(a, b)`` / ``(b, a)`` rows likewise merge into a single undirected edge
88
+ (last write wins), so the merged edge carries only the surviving row's attributes (e.g. a ``weight``). Build
89
+ with ``multigraph=True`` to retain both edges as parallel edges, or pre-aggregate the weights into a single edge
90
+ beforehand.
91
+
92
+ :param nodes: :py:class:`hiveplotlib.NodeCollection` of node data.
93
+ :param edges: :py:class:`hiveplotlib.Edges` of edge data (single-tag or multi-tag).
94
+ :param directed: whether to return a :py:class:`networkx.DiGraph`-family graph. Default ``True``.
95
+ :param multigraph: whether to return a ``Multi*``-family graph. Default ``True``.
96
+ :param tag_attribute_name: edge-attribute name to use when storing the original tag on multi-tag inputs. Default
97
+ ``"tag"``.
98
+ :param source_attribute_name: optional edge-attribute name under which to store a ``(tag, row_index)`` source-row
99
+ identifier for every edge, useful for joining graph-level computations back onto the original edge DataFrame.
100
+ Default ``None`` skips annotation.
101
+ :return: ``networkx`` graph (one of :py:class:`networkx.Graph`, :py:class:`networkx.DiGraph`,
102
+ :py:class:`networkx.MultiGraph`, or :py:class:`networkx.MultiDiGraph`).
103
+ :raises ValueError: if ``tag_attribute_name`` collides with an existing edge column on a multi-tag
104
+ :py:class:`hiveplotlib.Edges` instance.
105
+ :raises ValueError: if ``source_attribute_name`` is a string and collides with an existing edge column on any tag.
106
+ """
107
+ if directed and multigraph:
108
+ graph_class = nx.MultiDiGraph
109
+ elif directed:
110
+ graph_class = nx.DiGraph
111
+ elif multigraph:
112
+ graph_class = nx.MultiGraph
113
+ else:
114
+ graph_class = nx.Graph
115
+ g = graph_class()
116
+
117
+ unique_id_column = nodes.unique_id_column
118
+ node_attr_cols = [c for c in nodes.data.columns if c != unique_id_column]
119
+ if node_attr_cols:
120
+ attr_records = nodes.data[node_attr_cols].to_dict(orient="records")
121
+ g.add_nodes_from(zip(nodes.data[unique_id_column], attr_records, strict=True))
122
+ else:
123
+ g.add_nodes_from(nodes.data[unique_id_column])
124
+
125
+ from_col = edges.from_column_name
126
+ to_col = edges.to_column_name
127
+ multi_tag = len(edges._data) > 1
128
+
129
+ if multi_tag:
130
+ for tag, df in edges._data.items():
131
+ if tag_attribute_name in df.columns:
132
+ msg = (
133
+ f"Cannot write tag attribute under name '{tag_attribute_name}' "
134
+ f"because tag '{tag}' already has a column with that name. "
135
+ "Pass `tag_attribute_name='<new_name>'` to choose a different "
136
+ "attribute name."
137
+ )
138
+ raise ValueError(msg)
139
+
140
+ if source_attribute_name is not None:
141
+ for tag, df in edges._data.items():
142
+ if source_attribute_name in df.columns:
143
+ msg = (
144
+ f"Cannot write source attribute under name '{source_attribute_name}' "
145
+ f"because tag '{tag}' already has a column with that name. "
146
+ "Pass `source_attribute_name='<new_name>'` to choose a different "
147
+ "attribute name."
148
+ )
149
+ raise ValueError(msg)
150
+
151
+ for tag, df in edges._data.items():
152
+ edge_attr_cols = [c for c in df.columns if c not in (from_col, to_col)]
153
+ attr_records = (
154
+ df[edge_attr_cols].to_dict(orient="records")
155
+ if edge_attr_cols
156
+ else [{} for _ in range(len(df))]
157
+ )
158
+ if multi_tag:
159
+ for attrs in attr_records:
160
+ attrs[tag_attribute_name] = tag
161
+ if source_attribute_name is not None:
162
+ for attrs, row_index in zip(attr_records, df.index, strict=True):
163
+ attrs[source_attribute_name] = (tag, row_index)
164
+ g.add_edges_from(zip(df[from_col], df[to_col], attr_records, strict=True))
165
+
166
+ return g
@@ -21,7 +21,7 @@ def international_trade_data(
21
21
  roughly 300mb. However, the raw data are available at the
22
22
  `Harvard Growth Lab's website <https://doi.org/10.7910/DVN/T4CHWJ>`_, and the runner to produce the necessary
23
23
  files to use this reader function is available in the
24
- `repository <https://gitlab.com/geomdata/hiveplotlib/-/blob/master/runners/make_trade_network_dataset.py>`_
24
+ `repository <https://gitlab.com/hiveplotlib/hiveplotlib/-/blob/master/runners/make_trade_network_dataset.py>`_
25
25
  (``make_trade_network_dataset.py``).
26
26
 
27
27
  If you are using the runner to make your own trade datasets that you will read in locally with this
@@ -52,7 +52,7 @@ def international_trade_data(
52
52
  csv_years = [i.split("_")[2] for i in csv_files]
53
53
  csv_hs92 = [i.split("_")[-1] for i in csv_files]
54
54
  hiveplotlib_supported_values = pd.DataFrame(
55
- np.c_[csv_years, csv_hs92], columns=["Year", "Trade Code"]
55
+ np.c_[csv_years, csv_hs92], columns=np.array(["Year", "Trade Code"])
56
56
  )
57
57
 
58
58
  # check that our implied data and metadata files exist