Tabular-to-Graph ETL Guide
Real-world datasets often originate as tabular files (CSVs, Parquet, pandas/polars DataFrames, SQL databases) rather than pre-constructed graph topologies.
K3-Node provides an end-to-end Tabular-to-Graph ETL API under k3_node.etl to transform single tables or multi-table relational databases into graph Data or HeteroData objects ready for GNN training.
1. Single Table to Graph (TableToGraph)
TableToGraph (alias TabularToGraph) converts a DataFrame or CSV file into a k3_node.data.Data object. It automatically encodes numerical and categorical features, constructs graph edges according to your chosen strategy, maps IDs, and generates train/val/test splits.
Example: Customer Churn with k-NN Graph
import pandas as pd
from k3_node.etl import TableToGraph
from k3_node.tasks import NodeClassifier
# 1. Load raw tabular DataFrame
df = pd.DataFrame({
"customer_id": ["c1", "c2", "c3", "c4", "c5", "c6"],
"age": [25, 30, 45, 50, 22, 60],
"monthly_spend": [120.0, 150.0, 300.0, 320.0, 110.0, 400.0],
"plan_type": ["basic", "pro", "enterprise", "enterprise", "basic", "enterprise"],
"churn": [0, 0, 1, 1, 0, 1],
})
# 2. Convert table to graph in one line
etl = TableToGraph(
target_col="churn",
id_col="customer_id",
edge_strategy="knn",
edge_kwargs={"k": 2, "metric": "cosine"},
train_ratio=0.7,
test_ratio=0.3,
)
data = etl.fit_transform(df)
# 3. Directly train a NodeClassifier!
clf = NodeClassifier(backbone="gcn", hidden_channels=32, num_layers=2)
clf.fit(data, epochs=20, lr=0.01)
metrics = clf.evaluate(data, mask="test_mask")
print(f"Test Accuracy: {metrics['accuracy']:.4f}")
Loading Directly from CSV
from k3_node.etl import TableToGraph
data = TableToGraph.from_csv(
"data/transactions.csv",
target_col="is_fraud",
id_col="tx_id",
edge_strategy="knn",
edge_kwargs={"k": 5},
)
2. Graph Topology Strategies
K3-Node provides four built-in topology generation builders:
1. k-Nearest Neighbors ("knn")
Connects each row to its \(k\) closest rows based on feature space distance.
etl = TableToGraph(
edge_strategy="knn",
edge_kwargs={"k": 5, "metric": "cosine", "bidirectional": True},
)
"cosine", "euclidean", "manhattan".
2. Pairwise Similarity Threshold ("similarity")
Connects rows whose pairwise similarity exceeds a threshold \(\tau\).
etl = TableToGraph(
edge_strategy="similarity",
edge_kwargs={"threshold": 0.8, "metric": "cosine"},
)
"cosine", "rbf".
3. Shared Entity / Bipartite Projection ("shared_entity")
Connects rows that share identical values in one or more categorical identifier columns (e.g., users sharing the same IP address, device ID, or cluster).
etl = TableToGraph(
edge_strategy="shared_entity",
edge_kwargs={"entity_cols": ["ip_address", "device_fingerprint"], "max_degree": 50},
)
4. Sequential / Chronological ("sequential")
Connects rows sequentially in order of a timestamp or sequence index, optionally grouped by an entity (e.g. clickstreams per user session).
etl = TableToGraph(
edge_strategy="sequential",
edge_kwargs={"order_col": "timestamp", "group_by_col": "session_id", "window_size": 2},
)
3. Multi-Table Relational ETL (RelationalToGraph)
For relational databases with foreign keys, RelationalToGraph converts multiple tables into a k3_node.data.HeteroData graph. It handles entity ID mapping (e.g. string UUIDs \(\rightarrow\) contiguous integer indices), node feature encoding, edge attributes, and filters orphaned edges.
import pandas as pd
from k3_node.etl import RelationalToGraph
# 1. Node tables
users_df = pd.DataFrame({
"user_id": ["u1", "u2", "u3"],
"age": [25, 40, 32],
"segment": ["consumer", "enterprise", "consumer"],
})
items_df = pd.DataFrame({
"item_id": ["i1", "i2", "i3", "i4"],
"price": [15.0, 99.0, 45.0, 120.0],
"category": ["apparel", "electronics", "apparel", "electronics"],
})
# 2. Edge / Interaction table
ratings_df = pd.DataFrame({
"user_id": ["u1", "u1", "u2", "u3"],
"item_id": ["i1", "i2", "i3", "i4"],
"rating": [5.0, 4.0, 3.0, 5.0],
})
# 3. ETL conversion
etl = RelationalToGraph(
id_cols={"user": "user_id", "item": "item_id"},
edge_cols={("user", "rates", "item"): ("user_id", "item_id")},
edge_attr_cols={("user", "rates", "item"): ["rating"]},
)
hetero_data = etl.fit_transform(
nodes={"user": users_df, "item": items_df},
edges={("user", "rates", "item"): ratings_df},
)
print(hetero_data["user"].x.shape) # (3, 3)
print(hetero_data["item"].x.shape) # (4, 3)
print(hetero_data["user", "rates", "item"].edge_index) # (2, 4)
print(hetero_data["user", "rates", "item"].edge_attr) # (4, 1)
Accessing Original Entity IDs
The mapping from raw business identifiers to node indices is stored directly on the resulting HeteroData object:
# Convert raw user ID "u1" to graph node index
user_node_idx = hetero_data.id_maps["user"]["u1"]
# Convert graph node index 0 back to raw user ID
raw_user_id = hetero_data.inverse_id_maps["user"][0]
4. Standalone Encoders
If you need fine-grained control over column encoding:
- NumericalEncoder(strategy="standard"|"minmax"|"log1p", impute_strategy="mean"|"median"|"zero")
- CategoricalEncoder(strategy="onehot"|"ordinal"|"hash", handle_unknown="ignore")
- TabularEncoder(column_encoders={...})