← Back to home

netviz_tools

A Python package that takes a raw edge table to an interactive network chart in three steps: clean it, with a report of every change; build a NetworkX graph; plot it with Plotly. Every step uses the same argument names. Version 1.0 is a ground-up rewrite of the 0.x package I published in 2025, with FAOSTAT agricultural trade data as the worked example of messy input.

At a Glance

Problem / Context

Network data rarely arrives in a shape NetworkX can use. Column names differ between sources, the same country is spelled several ways, trade statistics list each flow twice (once by each country) with different numbers, and units get mixed. Once the data is clean, you still have to pick among plotting packages that each want their own input format and have their own quirks.

My own 0.x release from 2025 did not solve that. It bundled about 736 MB of FAOSTAT CSV files inside the wheel, required Python 3.13, and was built around stateful manager classes that printed, logged and plotted as side effects. I rewrote it from the ground up for 1.0; the 0.x API is not compatible.

Approach

Version 1.0 is one pipeline. You can enter at any step: a NetworkX graph you already have goes straight to the plots.

raw table ──clean()──▶ flow table ──build_graph()──▶ NetworkX graph ──plot.<kind>()──▶ Plotly figure

Results

Two examples of what the charts make visible, both from the bundled sample (FAOSTAT quantities in tonnes). The charts show the numbers; they do not say why the numbers changed.

Key Decisions / What I Learned

I used one generic column vocabulary instead of trade words: source, target, time, category, weight, unit. For trade data the source is the exporter; for migration it is the origin. The same words are the arguments of clean, build_graph and every plot. Display labels travel with the data in flows.attrs["labels"] and G.graph["labels"], so FAOSTAT charts still say Exporter and Importer without the plotting code knowing anything about trade.

Mirror flows are handled in clean, not left to the user. When both countries report the same flow, summing the reports double-counts it and picking one silently hides how far apart they are. clean keeps one report per flow (prefer= chooses which) and records the disagreement in the report.

A graph holds exactly one slice. Building a graph from several periods or categories raises MixedSliceError unless you pass aggregate="sum" or "mean", and mixed units raise MixedUnitError. Live-animal items in FAOSTAT, for example, have quantities in both head and tonnes, and adding those together would give a number that means nothing.

I kept a single rendering backend. Every chart is a Plotly figure with the same colours and hover style, returned to the caller. No function calls show() or writes a file, which is the opposite of 0.x.

A chart that cannot use an argument does not accept it, so passing one raises TypeError instead of being ignored. A silently ignored size_by produces a chart that looks fine and is not the one you asked for. plot.auto adds the chart it chose and why to the error message.

The full-dataset loader defaults to importer-reported flows. The Russian Federation reports no wheat exports after 2021, so exporter-reported data drop it from the 2022 wheat network entirely, while importers report 22.4 million tonnes of Russian wheat in 2022 and 34.0 million in 2023. More countries report imports, too: for wheat in 2021, 155 importing countries report against 99 exporting countries. reporter="exporter" and "combined" are still available.

Limitations

Tech Stack

Links

← Back to home