MatplotLibAPI 4.0.3__tar.gz → 4.0.4__tar.gz
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.
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/PKG-INFO +1 -1
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/pyproject.toml +1 -1
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/accessor.py +51 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/network/core.py +27 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/network/plot.py +2 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_network.py +60 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/.github/dependabot.yml +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/.github/workflows/ci.yml +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/.github/workflows/python-publish.yml +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/.gitignore +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/.pre-commit-config.yaml +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/AGENTS.md +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/LICENSE +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/README.md +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/SECURITY.md +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/SUGGESTIONS.md +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/examples/__init__.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/examples/bubble.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/examples/network.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/examples/sample_data.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/scripts/pre_commit.sh +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/__init__.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/area.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/bar.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/base_plot.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/box_violin.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/bubble.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/composite.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/heatmap.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/histogram.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/mcp/__init__.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/mcp/metadata.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/mcp/renderers.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/mcp_server.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/network/__init__.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/network/constants.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/network/scaling.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/pie.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/pivot.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/sankey.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/style_template.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/sunburst.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/table.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/timeserie.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/treemap.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/types.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/waffle.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/src/MatplotLibAPI/word_cloud.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/__init__.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/conftest.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_area.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_bar.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_box_violin.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_bubble.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_composite.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_dependencies.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_heatmap.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_histogram.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_mcp_server.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_network_preprocessing.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_pie.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_pivot.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_sankey.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_smoke.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_style_template.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_sunburst.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_table.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_timeseries.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_treemap.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_waffle.py +0 -0
- {matplotlibapi-4.0.3 → matplotlibapi-4.0.4}/tests/test_wordcloud.py +0 -0
|
@@ -1278,6 +1278,57 @@ class DataFrameAccessor:
|
|
|
1278
1278
|
**kwargs,
|
|
1279
1279
|
)
|
|
1280
1280
|
|
|
1281
|
+
def fplot_network(
|
|
1282
|
+
self,
|
|
1283
|
+
edge_source_col: str = "source",
|
|
1284
|
+
edge_target_col: str = "target",
|
|
1285
|
+
edge_weight_col: str = "weight",
|
|
1286
|
+
title: Optional[str] = None,
|
|
1287
|
+
style: Optional[StyleTemplate] = None,
|
|
1288
|
+
layout_seed: Optional[int] = None,
|
|
1289
|
+
figsize: Tuple[float, float] = FIG_SIZE,
|
|
1290
|
+
) -> Figure:
|
|
1291
|
+
"""Plot a network graph on a new figure.
|
|
1292
|
+
|
|
1293
|
+
Parameters
|
|
1294
|
+
----------
|
|
1295
|
+
edge_source_col : str, optional
|
|
1296
|
+
Column for source nodes. The default is "source".
|
|
1297
|
+
edge_target_col : str, optional
|
|
1298
|
+
Column for target nodes. The default is "target".
|
|
1299
|
+
edge_weight_col : str, optional
|
|
1300
|
+
Column for edge weights. The default is "weight".
|
|
1301
|
+
title : str, optional
|
|
1302
|
+
Chart title.
|
|
1303
|
+
style : StyleTemplate, optional
|
|
1304
|
+
Styling template. The default is `NETWORK_STYLE_TEMPLATE`.
|
|
1305
|
+
layout_seed : int, optional
|
|
1306
|
+
Seed forwarded to the spring layout. The default is ``_DEFAULT["SPRING_LAYOUT_SEED"]``.
|
|
1307
|
+
figsize : tuple[float, float], optional
|
|
1308
|
+
Figure size. The default is FIG_SIZE.
|
|
1309
|
+
|
|
1310
|
+
Returns
|
|
1311
|
+
-------
|
|
1312
|
+
Figure
|
|
1313
|
+
The new Matplotlib figure with the plot.
|
|
1314
|
+
"""
|
|
1315
|
+
kwargs: Dict[str, Any] = {}
|
|
1316
|
+
if layout_seed is not None:
|
|
1317
|
+
kwargs["layout_seed"] = layout_seed
|
|
1318
|
+
|
|
1319
|
+
from .network import NETWORK_STYLE_TEMPLATE, fplot_network
|
|
1320
|
+
|
|
1321
|
+
return fplot_network(
|
|
1322
|
+
pd_df=self._obj,
|
|
1323
|
+
edge_source_col=edge_source_col,
|
|
1324
|
+
edge_target_col=edge_target_col,
|
|
1325
|
+
edge_weight_col=edge_weight_col,
|
|
1326
|
+
title=title,
|
|
1327
|
+
style=style or NETWORK_STYLE_TEMPLATE,
|
|
1328
|
+
figsize=figsize,
|
|
1329
|
+
**kwargs,
|
|
1330
|
+
)
|
|
1331
|
+
|
|
1281
1332
|
def aplot_network_components(
|
|
1282
1333
|
self,
|
|
1283
1334
|
edge_source_col: str = "source",
|
|
@@ -453,6 +453,33 @@ class NetworkGraph(BasePlot):
|
|
|
453
453
|
"""Return the degree assortativity coefficient of the graph."""
|
|
454
454
|
return nx.degree_assortativity_coefficient(self._nx_graph)
|
|
455
455
|
|
|
456
|
+
@property
|
|
457
|
+
def degree_distribution(self) -> Dict[int, int]:
|
|
458
|
+
"""Return the count of nodes for each degree.
|
|
459
|
+
|
|
460
|
+
Returns
|
|
461
|
+
-------
|
|
462
|
+
dict[int, int]
|
|
463
|
+
Mapping from node degree to number of nodes with that degree.
|
|
464
|
+
"""
|
|
465
|
+
distribution: Dict[int, int] = defaultdict(int)
|
|
466
|
+
for _, degree in self._nx_graph.degree():
|
|
467
|
+
distribution[int(degree)] += 1
|
|
468
|
+
return dict(sorted(distribution.items()))
|
|
469
|
+
|
|
470
|
+
@property
|
|
471
|
+
def degree_sequence(self) -> List[int]:
|
|
472
|
+
"""Return node degrees sorted in descending order.
|
|
473
|
+
|
|
474
|
+
Returns
|
|
475
|
+
-------
|
|
476
|
+
list[int]
|
|
477
|
+
Degree for each node sorted from highest to lowest.
|
|
478
|
+
"""
|
|
479
|
+
return sorted(
|
|
480
|
+
(int(degree) for _, degree in self._nx_graph.degree()), reverse=True
|
|
481
|
+
)
|
|
482
|
+
|
|
456
483
|
def add_node(self, node: Any, **attributes: Any):
|
|
457
484
|
"""Add a node with optional attributes.
|
|
458
485
|
|
|
@@ -83,6 +83,44 @@ def test_accessor_fplot_network_components(load_sample_df):
|
|
|
83
83
|
assert isinstance(fig, Figure)
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def test_accessor_fplot_network(load_sample_df):
|
|
87
|
+
"""Render a network figure via the pandas accessor."""
|
|
88
|
+
|
|
89
|
+
df = load_sample_df("network.csv")
|
|
90
|
+
|
|
91
|
+
fig = df.mpl.fplot_network(
|
|
92
|
+
edge_source_col="city_a",
|
|
93
|
+
edge_target_col="city_b",
|
|
94
|
+
edge_weight_col="distance_km",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
assert isinstance(fig, Figure)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_accessor_fplot_network_forwards_layout_seed(monkeypatch):
|
|
101
|
+
"""Forward ``layout_seed`` from accessor calls to network plotting."""
|
|
102
|
+
|
|
103
|
+
df = pd.DataFrame(
|
|
104
|
+
{
|
|
105
|
+
"source": ["a", "b", "c"],
|
|
106
|
+
"target": ["b", "c", "a"],
|
|
107
|
+
"weight": [1, 2, 3],
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
captured_layout_seed = []
|
|
111
|
+
|
|
112
|
+
def fake_aplot(self, *args, **kwargs): # type: ignore[override]
|
|
113
|
+
captured_layout_seed.append(kwargs.get("layout_seed"))
|
|
114
|
+
return plt.gca()
|
|
115
|
+
|
|
116
|
+
monkeypatch.setattr(NetworkGraph, "aplot", fake_aplot)
|
|
117
|
+
|
|
118
|
+
fig = df.mpl.fplot_network(layout_seed=123)
|
|
119
|
+
|
|
120
|
+
assert isinstance(fig, Figure)
|
|
121
|
+
assert captured_layout_seed == [123]
|
|
122
|
+
|
|
123
|
+
|
|
86
124
|
def test_softmax_matches_expected_probabilities():
|
|
87
125
|
"""Return softmax probabilities consistent with NumPy operations."""
|
|
88
126
|
|
|
@@ -152,6 +190,28 @@ def test_scale_weights_respects_precomputed_deciles():
|
|
|
152
190
|
assert _scale_weights(weights, deciles=deciles) == expected
|
|
153
191
|
|
|
154
192
|
|
|
193
|
+
def test_degree_distribution_returns_node_counts_by_degree():
|
|
194
|
+
"""Return the count of nodes grouped by their degree."""
|
|
195
|
+
|
|
196
|
+
graph = NetworkGraph()
|
|
197
|
+
graph.add_edge("a", "b")
|
|
198
|
+
graph.add_edge("b", "c")
|
|
199
|
+
graph.add_node("isolated")
|
|
200
|
+
|
|
201
|
+
assert graph.degree_distribution == {0: 1, 1: 2, 2: 1}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_degree_sequence_returns_descending_node_degrees():
|
|
205
|
+
"""Return node degrees sorted from highest to lowest."""
|
|
206
|
+
|
|
207
|
+
graph = NetworkGraph()
|
|
208
|
+
graph.add_edge("a", "b")
|
|
209
|
+
graph.add_edge("b", "c")
|
|
210
|
+
graph.add_node("isolated")
|
|
211
|
+
|
|
212
|
+
assert graph.degree_sequence == [2, 1, 1, 0]
|
|
213
|
+
|
|
214
|
+
|
|
155
215
|
def test_network_layout_respects_precomputed_deciles() -> None:
|
|
156
216
|
"""Reuse provided deciles to produce stable layout scaling."""
|
|
157
217
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|