Weekly municipal NetworkX graphs for geostatistical analysis in Mexico
Project description
Graph-ARI
Graph-ARI provides graphari, a Python package for building dynamic weekly municipal graphs for geostatistical analysis in Mexico.
The package transforms bundled municipality-level weekly covariates and topology into networkx.Graph objects where:
- nodes are municipalities (
CVEGEO) - edges represent municipal adjacency with distance-based weight
- node attributes combine dynamic exogenous variables, mortality rate, and static metadata
- municipality existence is dynamic over time (a municipality appears only on/after its
creation_week)
This README follows the complete workflow shown in notebooks/visaulize.ipynb.
Installation
pip install .
Requires Python 3.10+.
What Is Bundled
Main weekly feature tables in graphari/data:
2m_mean_dewpoint_temperature.csv2m_mean_temperature.csv2m_relative_humidity.csvtotal_precipitation.csv10m_wind_speed.csv
Topology and metadata tables:
edges.csv: municipality identifiers, names, centroid coordinates, population, creation weekadjacency_matrix.csv: municipal adjacency matrix with distance-based edge weightsmortality_rates.csv: weekly mortality rate per municipalityEpidemiological_Weeks.csv: epidemiological week helper table
Default data span:
- weeks:
2003/01through2024/52 - municipalities: 2,478
- weekly snapshots: 1,148
Public API
graphari exposes the following top-level functions:
available_feature_tables()available_epiweeks()load_feature_tables(...)build_graph(...)build_graphs(...)get_node_feature_matrix(...)get_edge_index(...)
Complete Usage Walkthrough
1. Discover available tables and epidemiological weeks
import graphari as ga
tables = ga.available_feature_tables()
weeks = ga.available_epiweeks()
print(tables)
print(len(weeks), weeks[:5], weeks[-5:])
2. Build one weekly graph and a time window of graphs
focus_week = "2024/34"
window_start = "2024/01"
window_end = "2024/52"
G = ga.build_graph(week=focus_week)
graphs_window = ga.build_graphs(start_week=window_start, end_week=window_end)
print(G.number_of_nodes(), G.number_of_edges())
print(len(graphs_window), list(graphs_window)[:3], list(graphs_window)[-3:])
3. Inspect graph-level metadata
summary = {
"epidemiological_week": G.graph["epidemiological_week"],
"feature_names": G.graph["feature_names"],
"exogenous_variables": G.graph["exogenous_variables"],
"endogenous_variables": G.graph["endogenous_variables"],
"static_attributes": G.graph["static_attributes"],
"node_attributes": G.graph["node_attributes"],
}
summary
Expected structure:
feature_names: exogenous variables used for model-readyXexogenous_variables:feature_names + ["exists"]endogenous_variables:["mortality_rate"]static_attributes:["population", "latitude", "longitude"]
4. Verify dynamic municipality creation
Municipality 24059 is absent before 2024/30 and present from 2024/30 onward.
for week in ["2024/29", "2024/30", "2024/31"]:
Gw = ga.build_graph(week=week)
print(week, "present" if "24059" in Gw else "absent", Gw.number_of_nodes())
5. Inspect node and edge attributes
node = "24059" if "24059" in G else "24028"
attrs = G.nodes[node]
neighbor = next(iter(G.neighbors(node)))
edge_attrs = G.edges[(node, neighbor)]
print(node)
print(attrs)
print(edge_attrs)
Typical node attributes include:
- climate variables (
2m_mean_temperature,total_precipitation, etc.) mortality_ratepopulation,latitude,longitude- municipality identifiers and names (
cvegeo,state,municipality, ...) exists=True
Each edge includes:
weight: centroid-distance-based weight fromadjacency_matrix.csv
6. Build model-ready tensors from a graph
X, node_order = ga.get_node_feature_matrix(G)
edge_index = ga.get_edge_index(G, node_order)
print("X shape:", X.shape) # (n_nodes, n_features)
print("edge_index shape:", edge_index.shape) # (2, 2 * n_edges)
print("first nodes:", node_order[:5])
X[i]corresponds to nodenode_order[i]edge_indexis COO format with both directions(u, v)and(v, u)
7. Load raw weekly tables directly (pandas workflow)
tables = ga.load_feature_tables(start_week="2024/01", end_week="2024/52")
print(list(tables.keys()))
temperature = tables["2m_mean_temperature"]
print(temperature.shape)
print(temperature.iloc[:3, :3])
This is useful when you want table-based preprocessing before graph construction.
8. Build graphs with a custom feature subset
subset_graph = ga.build_graph(
week="2024/34",
feature_files=["2m_mean_temperature", "total_precipitation"],
)
print(subset_graph.graph["feature_names"])
subset_X, subset_order = ga.get_node_feature_matrix(subset_graph)
print(subset_X.shape)
Use this to control feature dimensionality for experiments.
Detailed Example: Time Dynamics and Visualization
The notebook includes exploratory plots; below is a direct adaptation you can run in scripts or notebooks.
Dynamic graph size over a time window
import matplotlib.pyplot as plt
import pandas as pd
import graphari as ga
focus_node = "24059"
graphs = ga.build_graphs(start_week="2024/01", end_week="2024/52")
node_counts = pd.DataFrame(
{
"week": list(graphs.keys()),
"node_count": [g.number_of_nodes() for g in graphs.values()],
"focus_node_present": [focus_node in g for g in graphs.values()],
}
)
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(node_counts["week"], node_counts["node_count"], marker="o", linewidth=2)
ax.set_title("Dynamic number of municipalities per week")
ax.set_xlabel("Epidemiological week")
ax.set_ylabel("Number of nodes")
ax.tick_params(axis="x", rotation=90)
first_presence = node_counts[node_counts["focus_node_present"]].head(1)
for _, row in first_presence.iterrows():
ax.axvline(row["week"], color="crimson", linestyle="--", alpha=0.8)
ax.text(row["week"], row["node_count"] + 0.1, f"{focus_node} appears", color="crimson")
plt.tight_layout()
Municipality-level time series from graph node attributes
import matplotlib.pyplot as plt
import pandas as pd
import graphari as ga
stable_node = "24028"
graphs = ga.build_graphs(start_week="2024/01", end_week="2024/52")
rows = []
for week, g in graphs.items():
if stable_node not in g:
continue
n = g.nodes[stable_node]
rows.append(
{
"week": week,
"mortality_rate": n["mortality_rate"],
"temperature": n["2m_mean_temperature"],
"precipitation": n["total_precipitation"],
"humidity": n["2m_relative_humidity"],
"wind_speed": n["10m_wind_speed"],
}
)
df = pd.DataFrame(rows)
fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)
axes[0].plot(df["week"], df["mortality_rate"], marker="o", color="crimson")
axes[0].set_title(f"Mortality rate for municipality {stable_node}")
axes[0].set_ylabel("Mortality rate")
axes[1].plot(df["week"], df["temperature"], marker="o", label="Temperature")
axes[1].plot(df["week"], df["precipitation"], marker="o", label="Precipitation")
axes[1].plot(df["week"], df["humidity"], marker="o", label="Humidity")
axes[1].plot(df["week"], df["wind_speed"], marker="o", label="10m Wind Speed")
axes[1].set_title(f"Exogenous variables for municipality {stable_node}")
axes[1].set_xlabel("Epidemiological week")
axes[1].set_ylabel("Value")
axes[1].legend()
axes[1].tick_params(axis="x", rotation=90)
plt.tight_layout()
One-hop ego network map using municipality latitude/longitude
import matplotlib.pyplot as plt
import networkx as nx
import graphari as ga
G = ga.build_graph(week="2024/34")
center_node = "24028"
ego = nx.ego_graph(G, center_node, radius=1)
positions = {
node: (ego.nodes[node]["longitude"], ego.nodes[node]["latitude"])
for node in ego.nodes()
}
node_colors = [ego.nodes[node]["mortality_rate"] for node in ego.nodes()]
node_sizes = [max(80, ego.nodes[node]["population"] / 1000) for node in ego.nodes()]
fig, ax = plt.subplots(figsize=(8, 6))
nx.draw_networkx_edges(ego, positions, alpha=0.35, ax=ax)
nodes = nx.draw_networkx_nodes(
ego,
positions,
node_color=node_colors,
node_size=node_sizes,
cmap="rainbow",
edgecolors="black",
linewidths=0.5,
ax=ax,
)
nx.draw_networkx_labels(ego, positions, font_size=9, ax=ax)
ax.set_title(f"Ego network around municipality {center_node} in 2024/34")
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
ax.set_aspect("equal", adjustable="datalim")
cbar = plt.colorbar(nodes, ax=ax)
cbar.set_label("Mortality rate")
plt.tight_layout()
Notes and Validation
- Epidemiological week format is normalized as
YYYY/WW. build_graph(week=...)requires a valid week present in the bundled data.feature_filescan be passed with or without.csvextension.- All selected feature tables are aligned by week and municipality columns.
Notebook
For the full executable walkthrough, open:
notebooks/visaulize.ipynb
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file graphari-1.0.tar.gz.
File metadata
- Download URL: graphari-1.0.tar.gz
- Upload date:
- Size: 49.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4220552360e66c3d19e162d1e6067751a0e481b5c865ce370cef6749a7d21885
|
|
| MD5 |
8b5254bfc3dc47974d4aceb8b53ee521
|
|
| BLAKE2b-256 |
5a81483a8c04f04b2647bdaec4a000dad1eff6b03dd77d6b6de00b63eba6cd12
|
File details
Details for the file graphari-1.0-py3-none-any.whl.
File metadata
- Download URL: graphari-1.0-py3-none-any.whl
- Upload date:
- Size: 50.0 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f273e88d18e6e4e2557a44565968315b258eee10571c7d3d60799e9601100235
|
|
| MD5 |
46806b2b27d66ad204e7f2f0b3ee78a9
|
|
| BLAKE2b-256 |
b43a6a98cbb604d6387c00c24c5635bd5c77902330daf4bec4cf9f17670b6146
|