L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Données et recherche · SKILL.md

graph-conversion

graph-conversion

Convert city2graph GeoDataFrame graph tables to and from NetworkX, rustworkx, and optional PyTorch Geometric representations, including heterogeneous relations, metadata, features, labels, directionality, multigraph keys, and metapaths.

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: "graph-conversion" description: "Convert city2graph GeoDataFrame graph tables to and from NetworkX, rustworkx, and optional PyTorch Geometric representations, including heterogeneous relations, metadata, features, labels, directionality, multigraph keys, and metapaths." metadata: disco-role: operating disable-model-invocation: true license: BSD 3-Clause

Graph conversion

Use this skill when the task is to move a city2graph graph between its GeoDataFrame-first representation and NetworkX, rustworkx, or PyTorch Geometric (PyG), or when constructing derived heterogeneous relations with metapaths. Keep the GeoDataFrame tables as the source of truth: graph objects use integer/positional IDs and attached metadata, while the GeoDataFrame indexes carry the original node and edge identifiers.

Fast route

  1. Classify the input. Use one GeoDataFrame for a homogeneous graph, or dictionaries for a heterogeneous graph. The heterogeneous node dictionary is keyed by node type strings; the edge dictionary is keyed by (source_type, relation_type, target_type) tuples.
  2. Validate the table contract before conversion. Node IDs are the node GeoDataFrame index values. Edge endpoints are the first two levels of an edge MultiIndex; a third level is an edge key. All non-empty tables in a graph must use a consistent CRS.
  3. Choose the target. Use gdf_to_nx/nx_to_gdf for graph algorithms and attribute-preserving spatial round trips; use gdf_to_pyg/pyg_to_gdf for tensor workflows; use nx_to_pyg or pyg_to_nx when bridging formats; use nx_to_rx/rx_to_nx for rustworkx algorithms.
  4. Make semantics explicit. Decide directed, multigraph, keep_geom, feature/label columns, and (for heterogeneous PyG) reverse edge types before converting. The default PyG conversion is undirected and therefore symmetrizes safe edges.
  5. Verify the result. Check graph class, node/edge counts, edge-type stores, tensor shapes, graph_metadata, CRS, index names, and whether a round trip is expected to preserve exact geometry or only centroid/straight-line geometry.

Detailed signatures and return-shape rules are in references/api-reference.md. Heterogeneous and PyG-specific semantics are in references/heterogeneous-and-pyg.md. Error diagnosis and minimal probes are in references/troubleshooting.md.

Canonical GeoDataFrame contract

Homogeneous tables

nodes: geopandas.GeoDataFrame | None
edges: geopandas.GeoDataFrame | None
  • A node table's index is the node identifier space. A named Index is preserved by the NetworkX/PyG metadata; a MultiIndex is also supported.
  • A non-empty edge table used by PyG must have a pandas.MultiIndex with at least two levels: (source_id, target_id). The first two values are looked up in nodes.index. A third level (source, target, key) identifies a multigraph edge and is preserved.
  • NetworkX conversion can infer endpoints from the first and last coordinates of LineString geometries. If edges have no geometry, use a two-level edge MultiIndex and provide the node table; endpoint IDs are then resolved from the node index. Unknown endpoint IDs are dropped with a warning in this path.
  • Node geometries may be Points, polygons, or other valid geometries; node positions are represented by centroids when a graph/tensor format requires coordinates. Edge geometries normally are LineString or MultiLineString. Homogeneous NetworkX conversion can accept geometryless edges when their first two index levels identify endpoints and a node frame is supplied; geometryless nodes cannot provide positions.
  • Empty tables are allowed by the conversion validators. In particular, an empty edge frame may lack a MultiIndex for PyG; a non-empty one may not.
  • CRS is metadata, not a node/edge attribute. Non-empty node and edge tables must have equal CRS values. A missing CRS is accepted, but distances, lengths, and geographic-coordinate computations need caller judgment.

Heterogeneous tables

nodes = {"building": building_nodes, "road": road_nodes}
edges = {
    ("building", "connects_to", "road"): building_road_edges,
    ("road", "links_to", "road"): road_road_edges,
}
  • Every node-type key is a string. Every edge-type key is a three-string tuple.
  • Each edge frame's first index level contains IDs from its source node-type frame; its second level contains IDs from its destination node-type frame.
  • Node IDs may overlap between types because type namespaces distinguish them.
  • Heterogeneous NetworkX nodes receive node_type; heterogeneous edges receive edge_type. Heterogeneous PyG uses HeteroData stores keyed by node and edge types.
  • Validate all table CRSs together. Do not silently combine frames in different CRSs merely because their index values happen to match.

NetworkX and rustworkx route

GeoDataFrame ↔ NetworkX

from city2graph import gdf_to_nx, nx_to_gdf

G = gdf_to_nx(nodes=nodes, edges=edges,
              keep_geom=True, multigraph=False, directed=False)
nodes_back, edges_back = nx_to_gdf(G)

gdf_to_nx chooses Graph, DiGraph, MultiGraph, or MultiDiGraph from directed and multigraph. It assigns internal integer node IDs when a node frame is supplied, stores the original index in _original_index, stores centroid coordinates in pos, and carries ordinary columns as attributes. Edge attributes include _original_edge_index; multigraph edges use their third index level as the NetworkX key, or generated row keys when needed. Heterogeneous node IDs are offset into one NetworkX namespace, but the original IDs and type metadata make reconstruction possible.

nx_to_gdf expects graph-level metadata and node positions or geometry. It returns a homogeneous GeoDataFrame (or a pair of node/edge GeoDataFrames) for homogeneous metadata, and typed dictionaries for heterogeneous metadata. With nodes=False or edges=False, homogeneous output can be a single frame; heterogeneous output remains a (nodes_dict, edges_dict) pair with an empty side. Requesting neither raises ValueError.

When an ordinary NetworkX graph lacks pos, nx_to_gdf can populate it from x and y (the default set_missing_pos_from=("x", "y")) or from one 2-element coordinate attribute. validate_nx is stricter: it rejects empty node/edge graphs, requires crs and is_hetero metadata, and requires every node to have pos or geometry; heterogeneous graphs additionally need type metadata and per-node/per-edge type attributes.

NetworkX ↔ rustworkx

from city2graph import nx_to_rx, rx_to_nx

rx_graph = nx_to_rx(G)
G_again = rx_to_nx(rx_graph)

nx_to_rx returns rustworkx.PyGraph or PyDiGraph, preserving directed and multigraph status and copying G.graph to rx_graph.attrs. It stores each original NetworkX node ID in the node payload key __nx_node_id__; for a multigraph it stores the NetworkX edge key as __nx_edge_key__ in the edge payload. rx_to_nx uses those sentinels to restore IDs and keys, and restores graph attributes. Raw rustworkx payloads without sentinels are retained under payload; this is a compatibility conversion, not a GeoDataFrame conversion.

PyG route

PyG is optional. Before invoking any PyG function, use city2graph.is_torch_available() (or city2graph.graph.is_torch_available()). If either PyTorch or torch_geometric is absent, the public PyG functions raise an ImportError with the message that PyTorch and PyTorch Geometric are required. Install/test a CPU-compatible PyTorch + PyG environment separately; do not make a graph conversion skill depend on CUDA. A minimal gate is:

python -c "import torch, torch_geometric; print(torch.__version__, torch_geometric.__version__)"

Then use:

from city2graph import gdf_to_pyg, pyg_to_gdf

data = gdf_to_pyg(
    nodes, edges,
    node_feature_cols=["population"],
    node_label_cols=["class_id"],
    edge_feature_cols=["length"],
    device="cpu",
    keep_geom=True,
    directed=True,
)
nodes_back, edges_back = pyg_to_gdf(data)

A homogeneous input produces torch_geometric.data.Data; a dictionary input produces HeteroData. PyG maps each node index value to a sequential integer position. Endpoint IDs are resolved against the node index; duplicate node IDs resolve to the last occurrence for connectivity. Edges whose endpoints are absent are omitted from the tensor. graph_metadata is mandatory for reconstruction and validation; call validate_pyg(data) after constructing or mutating a PyG object.

Features and labels are column-driven. Homogeneous specs must be lists; heterogeneous specs must be dictionaries keyed by node type or full edge-type tuple. Only existing numeric columns are included, in requested order; nonexistent and nonnumeric requested columns are ignored, so a zero-width x, y, or edge_attr is a valid result. Positions are centroid coordinates. device=None selects CUDA when available, otherwise CPU; explicitly selecting unavailable CUDA raises a clear ValueError. dtype controls floating tensor creation and position tensors.

Geometry and round-trip policy

  • keep_geom=True (the default for PyG) serializes original geometries as WKB in graph_metadata. A PyG → GeoDataFrame round trip can therefore preserve curved or non-straight edge geometries and null geometry values when metadata remains intact.
  • keep_geom=False, or a graph created without stored geometry metadata, falls back to positions. Nodes become Points and edges become straight LineStrings between source and destination positions. This is not an exact geometry round trip; an L-shaped or curved input edge will be simplified.
  • NetworkX conversion stores geometry attributes when keep_geom=True; when absent, reconstructed edge geometry is derived from endpoint pos.
  • Geographic CRS positions are computed through an estimated projected CRS for centroid calculation and transformed back to the source CRS. This improves centroid calculation but does not make geographic distances metrically valid.
  • Preserve graph_metadata/G.graph when serializing or passing objects between stages. A manually constructed bare NetworkX or PyG object may not contain enough index, CRS, type, or geometry information to reconstruct the original tables.

Directionality, undirected canonicalization, and multigraphs

PyG directionality

For a homogeneous PyG conversion, directed=False is the default. Each non-self-loop edge (u, v) is stored as (u, v) and (v, u); self-loops are not duplicated. Edge attributes and serialized edge geometries are duplicated in the same order. Reconstruction uses metadata to deduplicate the generated reverse rows back to the original edge table.

For directed=False, a non-empty edge frame must not already contain both (u, v) and (v, u) for the same undirected identity, and it must not contain parallel rows with the same unordered identity unless it has an explicit key level or multigraph=True. Such inputs raise ValueError rather than silently losing attributes or geometry. If a directed source exported reciprocal rows, choose one of:

from city2graph import canonicalize_edges
edges_one_per_pair = canonicalize_edges(edges, duplicates="first")
data = gdf_to_pyg(nodes, edges_one_per_pair, directed=False)

or use directed=True to preserve both orientations. canonicalize_edges changes index orientation only and does not reverse geometries. For a keyed multigraph, use duplicates="key" or provide a three-level index, and pass multigraph=True when two-level parallel rows need generated keys.

Heterogeneous directionality

For HeteroData, directed may be one bool or a complete dictionary with exactly one bool per supplied edge-type key. Missing or extra keys raise ValueError.

  • Same-type undirected relation (type, relation, type): symmetrized in its store; reconstruction deduplicates it using metadata.
  • Cross-type undirected relation (src, relation, dst): cannot be symmetrized in place because source and destination stores differ. city2graph creates a generated reverse store, by default (dst, "rev_<relation>", src), with a flipped edge_index and cloned edge attributes. Generated stores are PyG message-passing artifacts and are skipped by pyg_to_gdf.
  • reverse_edge_types=None is strict and raises for any non-empty undirected cross-type relation. A mapping supplies explicit reverse types; endpoints must be exactly reversed, and the result must not collide with a user edge type.
  • graph_metadata.original_edge_types, reverse_edge_types, and generated_reverse_edge_types record this provenance. Do not treat every data.edge_types entry as an original input table.

nx_to_pyg follows NetworkX class directionality by default: Graph/MultiGraph become undirected PyG (and are symmetrized), while DiGraph/MultiDiGraph remain directed. Its directed argument is an explicit override. pyg_to_nx must collapse per-edge-type directionality into one NetworkX graph class; mixed heterogeneous directionality emits a warning and uses an undirected graph.

Metapaths

Metapaths operate on heterogeneous tables or a city2graph-compatible heterogeneous NetworkX graph. The primary form is:

from city2graph import add_metapaths

nodes_out, edges_out = add_metapaths(
    (nodes_dict, edges_dict),
    sequence=[
        ("building", "connects_to", "road"),
        ("road", "links_to", "road"),
    ],
    edge_attr="travel_time",
    edge_attr_agg="sum",       # "sum", "mean", or a callable
    directed=False,
)

The sequence must contain at least two edge-type tuples. Consecutive hop endpoints must join. If directed=False and an exact hop type is absent, the reverse endpoint type with the same relation name is considered. A missing hop then raises KeyError. The result key is (start_type, "metapath_0", end_type), or uses new_relation_name. weight counts joined paths; requested numeric edge attributes are reduced along each path and then across terminal pairs. The result gets a straight terminal-to-terminal geometry and normalized index names. Undirected path materialization canonicalizes path orientation and edge identity, so mirrored traversals are not counted twice; distinct keyed parallel edges remain distinct. trace_path=True is retained for compatibility but is currently a no-op apart from debug logging. as_nx=True returns NetworkX and records generated metapath metadata under G.graph["metapath_dict"].

For threshold-based connectivity, use add_metapaths_by_weight. It requires weight, threshold, and an endpoint_type; it uses weighted Dijkstra over all or selected edge types and emits a same-type relation named connected_within_<min>_<max> unless customized. The threshold band is inclusive, self-connections are excluded, and undirected output canonicalizes terminal pairs. Missing/invalid endpoint or filtered edge data can return the original graph unchanged; validate the result key before assuming a new table was created.

Completion checklist

  • Input shape is homogeneous or heterogeneous consistently.
  • Node indexes are the intended stable IDs; edge first two index levels reference them exactly.
  • All non-empty frames have the same CRS.
  • directed, reciprocal rows, self-loops, and multigraph keys agree with the intended semantics.
  • Feature/label specs match homogeneous list versus heterogeneous dict rules and requested columns are numeric.
  • PyG availability/device was probed before a PyG conversion.
  • keep_geom was chosen with the exact-versus-straight geometry trade-off in mind.
  • Result class, counts, metadata, edge types, tensor shapes, and a round-trip sample were checked.
Skills enregistrées