ageraustine commited on
Commit
d296c8e
·
verified ·
1 Parent(s): 3c7ce0d

Delete examples

Browse files
examples/example_pipeline.py DELETED
@@ -1,214 +0,0 @@
1
- """
2
- Example pipeline demonstrating Phase 1: Data Fusion and Graph Construction.
3
-
4
- This script shows how to use the implemented modules to:
5
- 1. Load hydrometric and SAFRAN data
6
- 2. Clean and validate data
7
- 3. Fuse data sources
8
- 4. Extract basin features
9
- 5. Build physics-informed graph
10
- 6. Prepare tensors for GNN training
11
- """
12
- from pathlib import Path
13
- from src.config.settings import (
14
- WATERSHED_RISLE, WATERSHED_EURE, IDPR_FILE,
15
- BASIN_IDS, RANDOM_SEED
16
- )
17
- from src.data.loaders.hydrometric import HydrometricLoader
18
- from src.data.loaders.safran import SAFRANLoader
19
- from src.data.loaders.shapefile import ShapefileLoader
20
- from src.data.loaders.idpr import IDPRLoader
21
- from src.data.extractors.topological import TopologicalExtractor
22
- from src.data.extractors.karst import KarstExtractor
23
- from src.fusion.fusion_engine import DataFusionEngine
24
- from src.graph.builder import HydrologicalGraphBuilder
25
- from src.preprocessing.tensor_builder import SpatioTemporalTensorBuilder
26
- from src.utils.helpers import set_seed, get_device, ensure_dir, print_data_summary
27
-
28
-
29
- def main():
30
- """Run the complete Phase 1 pipeline."""
31
-
32
- print("="*80)
33
- print("PHASE 1: Data Fusion and Graph Construction for Hydrological GNN")
34
- print("="*80)
35
-
36
- # Set random seed for reproducibility
37
- set_seed(RANDOM_SEED)
38
-
39
- # Get computation device
40
- device = get_device(prefer_gpu=True)
41
-
42
- # Create output directories
43
- output_dir = ensure_dir(Path("outputs"))
44
- data_dir = ensure_dir(output_dir / "processed_data")
45
- graph_dir = ensure_dir(output_dir / "graphs")
46
- tensor_dir = ensure_dir(output_dir / "tensors")
47
-
48
- # ========================================================================
49
- # STEP 1: Load Data
50
- # ========================================================================
51
- print("\n[STEP 1] Loading data sources...")
52
-
53
- # Note: Update these paths to your actual data locations
54
- # This is a template - you'll need to adjust based on your data structure
55
-
56
- # Example: Load hydrometric data
57
- # hydro_loader = HydrometricLoader(
58
- # data_path=Path("datasets/hydrometric_data"),
59
- # file_format="csv"
60
- # )
61
- # hydro_df = hydro_loader.load()
62
- # print_data_summary(hydro_df, "Hydrometric Data")
63
-
64
- # Example: Load SAFRAN meteorological data
65
- # safran_loader = SAFRANLoader(
66
- # data_path=Path("datasets/safran_data"),
67
- # file_format="csv"
68
- # )
69
- # safran_df = safran_loader.load()
70
- # print_data_summary(safran_df, "SAFRAN Data")
71
-
72
- print("Data loading configured. Update paths to your actual data.")
73
-
74
- # ========================================================================
75
- # STEP 2: Extract Basin Features
76
- # ========================================================================
77
- print("\n[STEP 2] Extracting basin features from shapefiles...")
78
-
79
- # Load watershed shapefiles
80
- # risle_loader = ShapefileLoader(WATERSHED_RISLE)
81
- # eure_loader = ShapefileLoader(WATERSHED_EURE)
82
- # risle_gdf = risle_loader.load()
83
- # eure_gdf = eure_loader.load()
84
-
85
- # Extract topological features (if DEM available)
86
- # topo_extractor = TopologicalExtractor(dem_path=Path("path/to/dem.tif"))
87
- # risle_features = topo_extractor.extract(risle_gdf)
88
- # eure_features = topo_extractor.extract(eure_gdf)
89
-
90
- # Extract karst features
91
- # idpr_loader = IDPRLoader(IDPR_FILE)
92
- # idpr_df = idpr_loader.load()
93
- # karst_extractor = KarstExtractor(idpr_data=idpr_df)
94
- # karst_features = karst_extractor.extract(risle_gdf)
95
-
96
- print("Basin feature extraction configured.")
97
-
98
- # ========================================================================
99
- # STEP 3: Data Fusion
100
- # ========================================================================
101
- print("\n[STEP 3] Fusing data sources...")
102
-
103
- # Initialize fusion engine
104
- fusion_engine = DataFusionEngine(
105
- normalize=True,
106
- normalization_method="robust", # Recommended for hydrological data
107
- clean_data=True
108
- )
109
-
110
- # Fuse datasets
111
- # fused_df = fusion_engine.fuse(
112
- # hydrometric_df=hydro_df,
113
- # safran_df=safran_df,
114
- # basin_features=None # Add extracted basin features here
115
- # )
116
-
117
- # Save fused data
118
- # fusion_engine.save_fused_data(data_dir / "fused_data.csv")
119
- # print(fusion_engine.get_summary())
120
-
121
- print("Data fusion configured.")
122
-
123
- # ========================================================================
124
- # STEP 4: Build Physics-Informed Graph
125
- # ========================================================================
126
- print("\n[STEP 4] Building physics-informed graph...")
127
-
128
- # Initialize graph builder
129
- graph_builder = HydrologicalGraphBuilder(
130
- allow_disconnected=True, # Phase 1: Allow disconnected components
131
- allow_cross_basin=False # Phase 1: No cross-basin edges
132
- )
133
-
134
- # Prepare station and edge data
135
- # You'll need to create these from your data:
136
- # - station_df: [station_id, basin_id, latitude, longitude, elevation, ...]
137
- # - edge_df: [source, target, distance, elevation_gradient, ...]
138
-
139
- # Example structure:
140
- # import pandas as pd
141
- # station_df = pd.DataFrame({
142
- # 'station_id': ['S1', 'S2', 'S3'],
143
- # 'basin_id': [0, 0, 1], # 0=La Risle, 1=La Eure
144
- # 'latitude': [48.5, 48.6, 49.0],
145
- # 'longitude': [0.5, 0.6, 0.7],
146
- # 'elevation': [100, 90, 120]
147
- # })
148
- #
149
- # edge_df = pd.DataFrame({
150
- # 'source': ['S1', 'S2'],
151
- # 'target': ['S2', 'S3'],
152
- # 'distance': [10.5, 15.2], # km
153
- # 'elevation_gradient': [10, -30], # m
154
- # 'basin_id': [0, 1]
155
- # })
156
-
157
- # Build graph
158
- # graph_data = graph_builder.build(
159
- # station_df=station_df,
160
- # edge_df=edge_df,
161
- # basin_features=None
162
- # )
163
-
164
- # Save graph
165
- # graph_builder.save_graph(graph_data, graph_dir / "hydrological_graph.pt")
166
- # print(graph_builder.get_graph_statistics())
167
-
168
- print("Graph construction configured.")
169
- print("Note: Use QGIS DEM hydrology tools to extract station_edges.csv")
170
-
171
- # ========================================================================
172
- # STEP 5: Prepare Tensors for GNN Training
173
- # ========================================================================
174
- print("\n[STEP 5] Preparing spatio-temporal tensors...")
175
-
176
- # Initialize tensor builder
177
- tensor_builder = SpatioTemporalTensorBuilder(
178
- input_window=30, # 30 days of history
179
- forecast_horizons=[1, 3, 7, 14], # Forecast 1, 3, 7, 14 days ahead
180
- )
181
-
182
- # Build tensors
183
- # X, Y = tensor_builder.build_tensors(
184
- # fused_df=fused_df,
185
- # station_ids=None # Use all stations
186
- # )
187
-
188
- # Print tensor shapes
189
- # print(tensor_builder.get_tensor_shapes(X, Y))
190
-
191
- # Save tensors
192
- # tensor_builder.save_tensors(X, Y, tensor_dir / "training_tensors.pt")
193
-
194
- print("Tensor preparation configured.")
195
-
196
- # ========================================================================
197
- # Summary
198
- # ========================================================================
199
- print("\n" + "="*80)
200
- print("Pipeline template complete!")
201
- print("="*80)
202
- print("\nNext steps:")
203
- print("1. Update data paths to your actual hydrometric/SAFRAN data")
204
- print("2. Use QGIS to extract river network topology (station_edges.csv)")
205
- print("3. Run this pipeline to generate fused data, graphs, and tensors")
206
- print("4. Proceed to GNN model development (Phase 1 continuation)")
207
- print("\nOutputs will be saved to:")
208
- print(f" - Fused data: {data_dir}")
209
- print(f" - Graphs: {graph_dir}")
210
- print(f" - Tensors: {tensor_dir}")
211
-
212
-
213
- if __name__ == "__main__":
214
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/explore_datasets.py DELETED
@@ -1,196 +0,0 @@
1
- """
2
- Dataset exploration script.
3
- Efficiently explores available datasets and generates summary report.
4
- """
5
- from pathlib import Path
6
- import pandas as pd
7
- from src.config.settings import (
8
- STATION_LIST, IDPR_FILE, ADES_STATIONS, ADES_LEVELS,
9
- WATERSHED_RISLE, WATERSHED_EURE, WATERSHED_MERGED
10
- )
11
- from src.utils.station_utils import load_station_metadata, get_station_summary
12
- from src.data.loaders.shapefile import ShapefileLoader
13
- from src.data.loaders.idpr import IDPRLoader
14
-
15
-
16
- def main():
17
- """Explore all available datasets."""
18
-
19
- print("="*80)
20
- print("DATASET EXPLORATION - Hydrological GNN Project")
21
- print("="*80)
22
-
23
- # ========================================================================
24
- # 1. Station Metadata
25
- # ========================================================================
26
- print("\n[1] STATION METADATA")
27
- print("-" * 80)
28
-
29
- try:
30
- station_df = load_station_metadata(add_basin_id=True)
31
- print(f"✓ Loaded {len(station_df)} stations from station_list.csv")
32
- print(f"\nColumns: {list(station_df.columns)}")
33
-
34
- # Basin summary
35
- summary = get_station_summary()
36
- print(f"\nBasin Distribution:")
37
- print(f" - La Risle: {summary['risle_stations']} stations")
38
- print(f" - La Eure: {summary['eure_stations']} stations")
39
-
40
- # Coordinate bounds
41
- print(f"\nSpatial Extent:")
42
- print(f" - Longitude: {summary['bbox']['lon_min']:.3f} to {summary['bbox']['lon_max']:.3f}")
43
- print(f" - Latitude: {summary['bbox']['lat_min']:.3f} to {summary['bbox']['lat_max']:.3f}")
44
-
45
- # Sample stations
46
- print(f"\nSample Stations (La Risle):")
47
- risle_sample = station_df[station_df['basin_id'] == 0].head(3)
48
- for _, row in risle_sample.iterrows():
49
- print(f" - {row['station_code']}: {row['station_name']}")
50
-
51
- print(f"\nSample Stations (La Eure):")
52
- eure_sample = station_df[station_df['basin_id'] == 1].head(3)
53
- for _, row in eure_sample.iterrows():
54
- print(f" - {row['station_code']}: {row['station_name']}")
55
-
56
- except Exception as e:
57
- print(f"✗ Error loading station metadata: {e}")
58
-
59
- # ========================================================================
60
- # 2. IDPR Data
61
- # ========================================================================
62
- print("\n[2] IDPR DATA (Infiltration vs Runoff)")
63
- print("-" * 80)
64
-
65
- try:
66
- idpr_loader = IDPRLoader(IDPR_FILE)
67
- idpr_df = idpr_loader.load()
68
- print(f"✓ Loaded {len(idpr_df)} IDPR values")
69
- print(f"\nColumns: {list(idpr_df.columns)}")
70
-
71
- print(f"\nIDPR Statistics:")
72
- print(f" - Mean IDPR: {idpr_df['IDPR'].mean():.1f}")
73
- print(f" - Min IDPR: {idpr_df['IDPR'].min():.1f} (more runoff)")
74
- print(f" - Max IDPR: {idpr_df['IDPR'].max():.1f} (more infiltration)")
75
-
76
- # IDPR by basin
77
- if 'basin_name' in idpr_df.columns:
78
- print(f"\nIDPR by Basin:")
79
- for basin in idpr_df['basin_name'].unique():
80
- basin_data = idpr_df[idpr_df['basin_name'] == basin]
81
- print(f" - {basin}: mean={basin_data['IDPR'].mean():.1f}, "
82
- f"min={basin_data['IDPR'].min():.1f}, "
83
- f"max={basin_data['IDPR'].max():.1f}")
84
-
85
- except Exception as e:
86
- print(f"✗ Error loading IDPR data: {e}")
87
-
88
- # ========================================================================
89
- # 3. ADES Groundwater Data
90
- # ========================================================================
91
- print("\n[3] ADES GROUNDWATER DATA")
92
- print("-" * 80)
93
-
94
- try:
95
- # Groundwater stations
96
- gw_stations = pd.read_csv(ADES_STATIONS)
97
- print(f"✓ Loaded {len(gw_stations)} groundwater monitoring wells")
98
- print(f"\nColumns: {list(gw_stations.columns)[:10]}... ({len(gw_stations.columns)} total)")
99
-
100
- print(f"\nGroundwater Wells Statistics:")
101
- print(f" - Mean altitude: {gw_stations['altitude_station'].mean():.1f} m")
102
- print(f" - Mean depth: {gw_stations['profondeur_investigation'].mean():.1f} m")
103
- print(f" - Total measurements: {gw_stations['nb_mesures_piezo'].sum():.0f}")
104
-
105
- # Groundwater levels (large file - just peek)
106
- print(f"\n✓ Groundwater levels file exists: {ADES_LEVELS.exists()}")
107
- if ADES_LEVELS.exists():
108
- # Just read first few rows
109
- gw_levels_sample = pd.read_csv(ADES_LEVELS, nrows=5)
110
- print(f" - Estimated total rows: ~272,379 (large file!)")
111
- print(f" - Columns: {list(gw_levels_sample.columns)[:8]}...")
112
-
113
- except Exception as e:
114
- print(f"✗ Error loading ADES data: {e}")
115
-
116
- # ========================================================================
117
- # 4. Watershed Shapefiles
118
- # ========================================================================
119
- print("\n[4] WATERSHED SHAPEFILES")
120
- print("-" * 80)
121
-
122
- try:
123
- # La Risle
124
- risle_loader = ShapefileLoader(WATERSHED_RISLE)
125
- risle_gdf = risle_loader.load()
126
- print(f"✓ La Risle watershed loaded")
127
- print(f" - CRS: {risle_gdf.crs}")
128
- print(f" - Area: {risle_gdf.geometry.area.sum() / 1e6:.2f} km²")
129
- print(f" - Attributes: {list(risle_gdf.columns)}")
130
-
131
- # La Eure
132
- eure_loader = ShapefileLoader(WATERSHED_EURE)
133
- eure_gdf = eure_loader.load()
134
- print(f"\n✓ La Eure watershed loaded")
135
- print(f" - CRS: {eure_gdf.crs}")
136
- print(f" - Area: {eure_gdf.geometry.area.sum() / 1e6:.2f} km²")
137
-
138
- # Merged
139
- merged_loader = ShapefileLoader(WATERSHED_MERGED)
140
- merged_gdf = merged_loader.load()
141
- print(f"\n✓ Merged watershed loaded")
142
- print(f" - Total area: {merged_gdf.geometry.area.sum() / 1e6:.2f} km²")
143
-
144
- except Exception as e:
145
- print(f"✗ Error loading shapefiles: {e}")
146
-
147
- # ========================================================================
148
- # 5. Missing Data Summary
149
- # ========================================================================
150
- print("\n[5] MISSING DATA")
151
- print("-" * 80)
152
-
153
- missing_data = [
154
- ("Hydrometric data (Hub'Eau)", "datasets/hydrometric/", "*.csv or *.json"),
155
- ("SAFRAN meteorological data", "datasets/safran/", "*.csv or *.json"),
156
- ("DEM elevation data", "datasets/DEM/", "*.tif"),
157
- ("River network topology", "datasets/station_edges.csv", "CSV file"),
158
- ("Basin attributes (extracted)", "datasets/basin_features/", "*.csv")
159
- ]
160
-
161
- print("The following datasets are needed to run the full pipeline:\n")
162
- for name, path, format_type in missing_data:
163
- print(f" ✗ {name}")
164
- print(f" Expected: {path} ({format_type})")
165
-
166
- # ========================================================================
167
- # Summary
168
- # ========================================================================
169
- print("\n" + "="*80)
170
- print("SUMMARY")
171
- print("="*80)
172
- print("\n✓ Available Data:")
173
- print(" - 27 hydrometric station locations (La Risle: 12, La Eure: 13)")
174
- print(" - 27 IDPR values (karst infiltration/runoff index)")
175
- print(" - 114 groundwater monitoring wells (ADES)")
176
- print(" - 272,379 groundwater level measurements")
177
- print(" - 3 watershed boundary shapefiles (Risle, Eure, Merged)")
178
-
179
- print("\n✗ Missing Data:")
180
- print(" - Hydrometric time series (QmnJ, QIXnJ, HIXnJ) - Hub'Eau API")
181
- print(" - SAFRAN meteorological time series (12 variables) - Météo-France")
182
- print(" - DEM for topological feature extraction")
183
- print(" - River network topology (station_edges.csv)")
184
-
185
- print("\n📋 Next Steps:")
186
- print(" 1. Download hydrometric data from Hub'Eau API")
187
- print(" 2. Download SAFRAN meteorological data for station coordinates")
188
- print(" 3. Acquire DEM (IGN RGE ALTI or EU-DEM)")
189
- print(" 4. Use QGIS to extract river network topology")
190
- print(" 5. Run example_pipeline.py to process all data")
191
-
192
- print("\n" + "="*80)
193
-
194
-
195
- if __name__ == "__main__":
196
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/test_fusion.py DELETED
@@ -1,50 +0,0 @@
1
- """
2
- Test updated DataFusionEngine with actual downloaded data.
3
- """
4
- import sys
5
- from pathlib import Path
6
-
7
- sys.path.insert(0, str(Path(__file__).parent.parent))
8
-
9
- from src.fusion.fusion_engine import DataFusionEngine
10
-
11
-
12
- def main():
13
- # Initialize fusion engine
14
- fusion = DataFusionEngine(
15
- hydrometric_dir="datasets/hydrometric",
16
- era5_dir="datasets/safran",
17
- station_list_path="datasets/station_list.csv",
18
- normalize=False, # Keep raw values for inspection
19
- normalization_method="robust"
20
- )
21
-
22
- # Run fusion for January 2020 (our ERA5 sample data)
23
- fused_df = fusion.fuse(
24
- start_date="2000-01-01",
25
- end_date="2026-01-31"
26
- )
27
-
28
- # Display results
29
- print("\n=== FUSED DATA SAMPLE ===")
30
- print(fused_df.head(10))
31
-
32
- print("\n=== COLUMNS ===")
33
- print(fused_df.columns.tolist())
34
-
35
- print("\n=== DATA TYPES ===")
36
- print(fused_df.dtypes)
37
-
38
- print("\n=== SUMMARY STATISTICS BY STATION ===")
39
- summary = fusion.get_summary_stats()
40
- print(summary)
41
-
42
- # Save output
43
- output_path = "datasets/fused/fused.csv"
44
- fusion.save(output_path)
45
-
46
- print(f"\n✓ Test complete!")
47
-
48
-
49
- if __name__ == "__main__":
50
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/test_graph.py DELETED
@@ -1,148 +0,0 @@
1
- """
2
- Comprehensive test script for physics-informed hydrological graph construction and spatiotemporal dataset creation.
3
- """
4
- import sys
5
- from pathlib import Path
6
- import pandas as pd
7
- import numpy as np
8
- import torch
9
-
10
- sys.path.insert(0, str(Path(__file__).parent.parent))
11
-
12
- from src.fusion.fusion_engine import DataFusionEngine
13
- from src.graph.builder import HydrologicalGraphBuilder
14
- from src.graph.graph_from_fused import FusedDataGraphBuilder
15
- from src.graph.visualize import GraphVisualizer
16
-
17
-
18
- def create_synthetic_fused_data():
19
- """Create synthetic fused data if datasets/fused/fused.csv does not exist."""
20
- print(" Creating synthetic fused data for testing...")
21
- dates = pd.date_range("2020-01-01", "2020-01-31", freq="D")
22
- stations = [
23
- {"station_code": "H4012010", "basin_id": 0, "lat": 49.123, "lon": 0.543},
24
- {"station_code": "H4023020", "basin_id": 0, "lat": 49.200, "lon": 0.600},
25
- {"station_code": "H4050001", "basin_id": 0, "lat": 49.300, "lon": 0.700},
26
- {"station_code": "H9011010", "basin_id": 1, "lat": 48.800, "lon": 1.200},
27
- {"station_code": "H9022020", "basin_id": 1, "lat": 48.900, "lon": 1.300},
28
- ]
29
-
30
- records = []
31
- for d in dates:
32
- for st in stations:
33
- records.append({
34
- "date": d,
35
- "station_code": st["station_code"],
36
- "basin_id": st["basin_id"],
37
- "discharge_m3s": np.random.uniform(2.0, 50.0),
38
- "waterlevel_mm": np.random.uniform(300.0, 1500.0),
39
- "temp_C": np.random.uniform(5.0, 18.0),
40
- "wind_speed_ms": np.random.uniform(1.0, 8.0),
41
- "IDPR": 120.0 if st["basin_id"] == 0 else 150.0
42
- })
43
-
44
- df = pd.DataFrame(records)
45
- station_df = pd.DataFrame(stations)
46
- return df, station_df
47
-
48
-
49
- def main():
50
- print("=" * 80)
51
- print("SPATIOTEMPORAL HYDROLOGICAL GRAPH CONSTRUCTION TEST")
52
- print("=" * 80)
53
-
54
- # Step 1: Load or create fused data & station metadata
55
- print("\n1. Loading fused data & station list...")
56
- fused_path = Path("datasets/fused/fused.csv")
57
- station_path = Path("datasets/station_list.csv")
58
-
59
- if fused_path.exists() and station_path.exists():
60
- fused_df = pd.read_csv(fused_path)
61
- fused_df['date'] = pd.to_datetime(fused_df['date'])
62
- stations = pd.read_csv(station_path)
63
- print(f" ✓ Loaded fused data from {fused_path}")
64
- else:
65
- fused_df, stations = create_synthetic_fused_data()
66
- print(" ✓ Synthetic test data generated.")
67
-
68
- fused_stations = fused_df['station_code'].unique()
69
- station_coords = stations[stations['station_code'].isin(fused_stations)].copy()
70
- if 'station_id' not in station_coords.columns:
71
- station_coords['station_id'] = station_coords['station_code']
72
-
73
- if 'basin_id' not in station_coords.columns:
74
- basin_map = fused_df[['station_code', 'basin_id']].drop_duplicates().set_index('station_code')['basin_id']
75
- station_coords['basin_id'] = station_coords['station_code'].map(basin_map)
76
-
77
- print(f" ✓ Total observations: {len(fused_df)}")
78
- print(f" ✓ Active stations: {len(station_coords)}")
79
-
80
- # Step 2: Test HydrologicalGraphBuilder (Physics-informed static topology)
81
- print("\n2. Testing HydrologicalGraphBuilder (Static River Graph)...")
82
- phy_builder = HydrologicalGraphBuilder(allow_disconnected=True, allow_cross_basin=False)
83
-
84
- sample_edges = pd.DataFrame([
85
- {"source": station_coords['station_id'].iloc[0], "target": station_coords['station_id'].iloc[1], "distance": 12.5, "elevation_gradient": 15.0, "basin_id": 0}
86
- ]) if len(station_coords) >= 2 else pd.DataFrame()
87
-
88
- static_graph = phy_builder.build(station_df=station_coords, edge_df=sample_edges)
89
- stats = phy_builder.get_graph_statistics()
90
-
91
- print(f" ✓ Static nodes tensor shape: {static_graph.x.shape}")
92
- print(f" ✓ Static edge index shape: {static_graph.edge_index.shape}")
93
- print(f" ✓ Basin IDs tensor: {static_graph.basin_id}")
94
-
95
- # Step 3: Test FusedDataGraphBuilder (Spatial-Temporal Snapshots & Sequences)
96
- print("\n3. Testing FusedDataGraphBuilder...")
97
- graph_builder = FusedDataGraphBuilder(
98
- station_coords=station_coords,
99
- max_distance_km=100.0,
100
- allow_cross_basin=False,
101
- custom_edge_index=static_graph.edge_index,
102
- custom_edge_attr=static_graph.edge_attr
103
- )
104
-
105
- feature_cols = [c for c in ['discharge_m3s', 'waterlevel_mm', 'temp_C', 'wind_speed_ms', 'IDPR'] if c in fused_df.columns]
106
- sample_date = fused_df['date'].iloc[0].strftime('%Y-%m-%d')
107
-
108
- snapshot = graph_builder.build_snapshot_graph(
109
- fused_df=fused_df,
110
- date=sample_date,
111
- feature_cols=feature_cols
112
- )
113
-
114
- print(f" ✓ Snapshot for {sample_date}:")
115
- print(f" Node features shape: {snapshot.x.shape}")
116
- print(f" Edge index shape: {snapshot.edge_index.shape}")
117
-
118
- # Step 4: Test Spatiotemporal Tensor Generation X in R^(N x T x S x F)
119
- print("\n4. Generating 4D Spatiotemporal Tensors for GNN Training...")
120
- window_size = 5
121
- X_tensor, Y_tensor = graph_builder.build_spatiotemporal_tensors(
122
- fused_df=fused_df,
123
- window_size=window_size,
124
- stride=1,
125
- feature_cols=feature_cols,
126
- target_cols=['discharge_m3s'] if 'discharge_m3s' in feature_cols else None
127
- )
128
-
129
- print(f" ✓ Input Tensor X shape: {X_tensor.shape} [N_samples, T_window, S_nodes, F_features]")
130
- if Y_tensor is not None:
131
- print(f" ✓ Target Tensor Y shape: {Y_tensor.shape} [N_samples, S_nodes, Target_features]")
132
-
133
- # Step 5: Test Visualization
134
- print("\n5. Testing Graph Visualizer...")
135
- try:
136
- vis = GraphVisualizer(station_coords=station_coords, edge_index=snapshot.edge_index.numpy())
137
- fig = vis.plot_spatial_graph(title="Test Spatial Hydrological Network")
138
- print(" ✓ Spatial graph plot generated successfully.")
139
- except Exception as e:
140
- print(f" ! Visualization notice: {e}")
141
-
142
- print("\n" + "=" * 80)
143
- print("ALL GRAPH PIPELINE TESTS PASSED")
144
- print("=" * 80)
145
-
146
-
147
- if __name__ == "__main__":
148
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/test_loaders.py DELETED
@@ -1,81 +0,0 @@
1
- """
2
- Test updated loaders with actual downloaded data.
3
- """
4
- import sys
5
- from pathlib import Path
6
- import pandas as pd
7
-
8
- # Add src to path
9
- sys.path.insert(0, str(Path(__file__).parent.parent))
10
-
11
- from src.data.loaders.hydrometric import HydrometricLoader
12
- from src.data.loaders.safran import SAFRANLoader
13
-
14
-
15
- def main():
16
- print("=" * 80)
17
- print("TESTING DATA LOADERS WITH ACTUAL DATA")
18
- print("=" * 80)
19
-
20
- # Test 1: Hub'Eau Hydrometric Data
21
- print("\n1. Loading Hub'Eau hydrometric data...")
22
- hydro_loader = HydrometricLoader(
23
- data_path=Path("datasets/hydrometric"),
24
- min_quality=True
25
- )
26
-
27
- hydro_df = hydro_loader.load()
28
- print(f" ✓ Loaded {len(hydro_df):,} observations")
29
- print(f" Stations: {hydro_df['station_code'].nunique()}")
30
- print(f" Date range: {hydro_df['date'].min()} to {hydro_df['date'].max()}")
31
- print(f" Columns: {list(hydro_df.columns)}")
32
- print(f"\n Sample:")
33
- print(hydro_df.head(3))
34
-
35
- # Test 2: ERA5 Data
36
- print("\n2. Loading ERA5 meteorological data...")
37
-
38
- # Load station coordinates
39
- stations = pd.read_csv("datasets/station_list.csv")
40
- station_coords = stations[['station_code', 'lat', 'lon']].copy()
41
-
42
- era5_loader = SAFRANLoader(
43
- data_path=Path("datasets/safran"),
44
- station_coords=station_coords,
45
- interp_method="nearest"
46
- )
47
-
48
- era5_df = era5_loader.load()
49
- print(f" ✓ Loaded {len(era5_df):,} observations")
50
- print(f" Stations: {era5_df['station_code'].nunique()}")
51
- print(f" Date range: {era5_df['date'].min()} to {era5_df['date'].max()}")
52
- print(f" Columns: {list(era5_df.columns)}")
53
-
54
- # Convert units
55
- print("\n3. Converting ERA5 units (K→°C, m→mm)...")
56
- era5_df = SAFRANLoader.convert_units(era5_df)
57
- print(f" ✓ Converted columns: {[c for c in era5_df.columns if c not in ['date', 'station_code']]}")
58
- print(f"\n Sample:")
59
- print(era5_df.head(3))
60
-
61
- # Test 3: Merge data
62
- print("\n4. Merging hydrometric + ERA5...")
63
- merged = pd.merge(
64
- hydro_df,
65
- era5_df,
66
- on=['date', 'station_code'],
67
- how='inner'
68
- )
69
- print(f" ✓ Merged {len(merged):,} rows")
70
- print(f" Stations: {merged['station_code'].nunique()}")
71
- print(f" Total columns: {len(merged.columns)}")
72
-
73
- print("\n" + "=" * 80)
74
- print("✓ ALL LOADERS WORKING!")
75
- print("=" * 80)
76
-
77
- return hydro_df, era5_df, merged
78
-
79
-
80
- if __name__ == "__main__":
81
- hydro_df, era5_df, merged_df = main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/visualize_full_graph.py DELETED
@@ -1,194 +0,0 @@
1
- """
2
- Generate and save complete visualization suite for the hydrological graph dataset.
3
- """
4
- from pathlib import Path
5
- import pandas as pd
6
- import numpy as np
7
- import torch
8
- import networkx as nx
9
- import matplotlib.pyplot as plt
10
-
11
- import sys
12
- from pathlib import Path
13
-
14
- sys.path.insert(0, str(Path(__file__).parent.parent))
15
-
16
- from src.graph.builder import HydrologicalGraphBuilder
17
- from src.graph.visualize import GraphVisualizer
18
-
19
-
20
- def extract_station_coords(builder, fused_df: pd.DataFrame) -> pd.DataFrame:
21
- """Dynamically extract station metadata from builder attributes, graph, or fused data."""
22
- # 1. Search builder instance attributes for a DataFrame
23
- for attr, val in builder.__dict__.items():
24
- if isinstance(val, pd.DataFrame) and len(val) > 0:
25
- df = val.copy()
26
- if 'station_id' in df.columns and 'station_code' not in df.columns:
27
- df['station_code'] = df['station_id']
28
- if 'latitude' in df.columns and 'lat' not in df.columns:
29
- df['lat'] = df['latitude']
30
- if 'longitude' in df.columns and 'lon' not in df.columns:
31
- df['lon'] = df['longitude']
32
-
33
- if {'station_code', 'lat', 'lon'}.issubset(df.columns) or 'basin_id' in df.columns:
34
- print(f" ✓ Extracted station metadata from builder.{attr}")
35
- return df
36
-
37
- # 2. Extract from NetworkX graph if stored in builder
38
- if hasattr(builder, 'G') and isinstance(builder.G, (nx.Graph, nx.DiGraph)):
39
- rows = []
40
- for n, data in builder.G.nodes(data=True):
41
- pos = data.get('pos', (0.0, 0.0))
42
- rows.append({
43
- 'station_code': data.get('station_code', f"node_{n}"),
44
- 'lon': pos[0],
45
- 'lat': pos[1],
46
- 'basin_id': data.get('basin_id', 0)
47
- })
48
- if rows:
49
- print(" ✓ Extracted station metadata from builder network graph (builder.G)")
50
- return pd.DataFrame(rows)
51
-
52
- # 3. Fallback: Build station table from fused dataset unique stations
53
- unique_stations = (
54
- fused_df['station_code'].unique()
55
- if 'station_code' in fused_df.columns
56
- else fused_df['station_id'].unique()
57
- )
58
- print(" ! Generating station metadata table from fused observation stations...")
59
-
60
- half = len(unique_stations) // 2 + 1
61
- return pd.DataFrame({
62
- 'station_code': unique_stations,
63
- 'lat': [0.0 + i * 0.05 for i in range(len(unique_stations))],
64
- 'lon': [36.0 + i * 0.05 for i in range(len(unique_stations))],
65
- 'basin_id': [0 if i < half else 1 for i in range(len(unique_stations))]
66
- })
67
-
68
-
69
- def main():
70
- print("=" * 80)
71
- print("HYDROLOGICAL GRAPH FULL VISUALIZATION SUITE")
72
- print("=" * 80)
73
-
74
- # 1. Setup output directory
75
- output_dir = Path("outputs/plots")
76
- output_dir.mkdir(parents=True, exist_ok=True)
77
- print(f"\n1. Output directory ready at: {output_dir.resolve()}")
78
-
79
- # 2. Load fused observations data
80
- print("\n2. Loading fused observations dataset...")
81
- fused_csv_path = Path("datasets/fused/fused.csv")
82
- fused_df = pd.read_csv(fused_csv_path)
83
-
84
- if 'station_id' in fused_df.columns and 'station_code' not in fused_df.columns:
85
- fused_df['station_code'] = fused_df['station_id']
86
-
87
- # 3. Instantiate builder & extract station metadata + edge index
88
- print("\n3. Initializing HydrologicalGraphBuilder...")
89
- try:
90
- builder = HydrologicalGraphBuilder()
91
- except Exception:
92
- builder = HydrologicalGraphBuilder(fused_df)
93
-
94
- station_coords = extract_station_coords(builder, fused_df)
95
-
96
- if 'basin_id' not in station_coords.columns:
97
- station_coords['basin_id'] = 0
98
-
99
- edge_index = getattr(builder, 'edge_index', None)
100
- if edge_index is None and hasattr(builder, 'get_edge_index'):
101
- edge_index = builder.get_edge_index()
102
-
103
- if isinstance(edge_index, torch.Tensor):
104
- edge_index = edge_index.cpu().numpy()
105
-
106
- # 4. Initialize Visualizer
107
- print("\n4. Initializing GraphVisualizer...")
108
- visualizer = GraphVisualizer(
109
- station_coords=station_coords,
110
- edge_index=edge_index,
111
- figsize=(12, 8),
112
- directed=True
113
- )
114
-
115
- # -------------------------------------------------------------------------
116
- # Plot 1: Spatial Graph Layout
117
- # -------------------------------------------------------------------------
118
- print(" [1/4] Plotting Spatial Graph Network...")
119
- fig1 = visualizer.plot_spatial_graph(
120
- title="Hydrological Station Graph & Basin Networks",
121
- show_labels=True,
122
- save_path=output_dir / "01_spatial_network.png"
123
- )
124
- plt.close(fig1)
125
-
126
- # -------------------------------------------------------------------------
127
- # Plot 2: Node Feature Heatmap
128
- # -------------------------------------------------------------------------
129
- print(" [2/4] Plotting Feature Heatmap...")
130
- exclude_cols = {'basin_id', 'lat', 'lon', 'latitude', 'longitude', 'station_code', 'station_id', 'date'}
131
- numeric_cols = [c for c in fused_df.select_dtypes(include=[np.number]).columns if c not in exclude_cols]
132
- target_feature = numeric_cols[0] if numeric_cols else fused_df.columns[2]
133
-
134
- # Use pivot_table with aggfunc='mean' to safely aggregate duplicate timestamps per station
135
- pivoted = fused_df.pivot_table(
136
- index='date',
137
- columns='station_code',
138
- values=target_feature,
139
- aggfunc='mean'
140
- ).ffill().bfill().fillna(0)
141
-
142
- feature_matrix_2d = pivoted.values.T # (num_nodes, num_timesteps)
143
-
144
- fig2 = visualizer.plot_feature_heatmap(
145
- feature_matrix=feature_matrix_2d,
146
- feature_names=[target_feature],
147
- title=f"Station Feature Heatmap ({target_feature})",
148
- save_path=output_dir / "02_feature_heatmap.png"
149
- )
150
- plt.close(fig2)
151
-
152
- # -------------------------------------------------------------------------
153
- # Plot 3: Temporal Evolution Across Stations
154
- # -------------------------------------------------------------------------
155
- print(" [3/4] Plotting Temporal Feature Evolution...")
156
- recent_df = fused_df.tail(100 * len(station_coords))
157
-
158
- time_series = recent_df.pivot_table(
159
- index='date',
160
- columns='station_code',
161
- values=target_feature,
162
- aggfunc='mean'
163
- ).ffill().bfill().fillna(0)
164
-
165
- temporal_data = time_series.values[:, :, np.newaxis]
166
- dates = [str(d)[:10] for d in time_series.index]
167
-
168
- fig3 = visualizer.plot_temporal_evolution(
169
- temporal_data=temporal_data,
170
- feature_idx=0,
171
- feature_name=target_feature,
172
- dates=dates,
173
- title=f"Temporal Dynamics of {target_feature} (Recent Windows)",
174
- save_path=output_dir / "03_temporal_evolution.png"
175
- )
176
- plt.close(fig3)
177
-
178
- # -------------------------------------------------------------------------
179
- # Plot 4: Dataset & Graph Statistics Summary
180
- # -------------------------------------------------------------------------
181
- print(" [4/4] Plotting Graph Statistics Overview...")
182
- fig4 = visualizer.plot_graph_statistics(
183
- fused_df=fused_df,
184
- save_path=output_dir / "04_graph_statistics.png"
185
- )
186
- plt.close(fig4)
187
-
188
- print("\n" + "=" * 80)
189
- print(f"SUCCESS: Saved all 4 plots to {output_dir.resolve()}/")
190
- print("=" * 80)
191
-
192
-
193
- if __name__ == "__main__":
194
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/visualize_graph.py DELETED
@@ -1,210 +0,0 @@
1
- """
2
- Visualize hydrological graphs and data.
3
- """
4
- import sys
5
- from pathlib import Path
6
- import pandas as pd
7
- import numpy as np
8
- import matplotlib.pyplot as plt
9
-
10
- sys.path.insert(0, str(Path(__file__).parent.parent))
11
-
12
- from src.graph.visualize import GraphVisualizer
13
-
14
-
15
- def main():
16
- print("=" * 80)
17
- print("GRAPH VISUALIZATION")
18
- print("=" * 80)
19
-
20
- # Create output directory
21
- output_dir = Path("outputs/visualizations")
22
- output_dir.mkdir(parents=True, exist_ok=True)
23
-
24
- # Load fused data
25
- print("\n1. Loading data...")
26
- fused_path = Path("datasets/fused/jan2020_fused.csv")
27
- fused_df = pd.read_csv(fused_path)
28
- fused_df['date'] = pd.to_datetime(fused_df['date'])
29
- print(f" ✓ Loaded {len(fused_df)} observations")
30
-
31
- # Prepare station coordinates
32
- stations = pd.read_csv("datasets/station_list.csv")
33
- fused_stations = fused_df['station_code'].unique()
34
- station_coords = stations[stations['station_code'].isin(fused_stations)].copy()
35
- station_coords = station_coords[['station_code', 'lat', 'lon']].copy()
36
-
37
- # Add basin_id
38
- basin_map = fused_df[['station_code', 'basin_id']].drop_duplicates().set_index('station_code')['basin_id']
39
- station_coords['basin_id'] = station_coords['station_code'].map(basin_map)
40
- station_coords = station_coords.reset_index(drop=True)
41
-
42
- # Build edge index (simple proximity) - compute directly to avoid torch/numpy issues
43
- print("\n2. Building graph structure...")
44
-
45
- def haversine_distance(lat1, lon1, lat2, lon2):
46
- """Calculate distance between two points (km)."""
47
- R = 6371 # Earth radius in km
48
- lat1, lon1, lat2, lon2 = map(np.radians, [lat1, lon1, lat2, lon2])
49
- dlat = lat2 - lat1
50
- dlon = lon2 - lon1
51
- a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2
52
- c = 2 * np.arcsin(np.sqrt(a))
53
- return R * c
54
-
55
- # Compute proximity edges
56
- edges = []
57
- max_distance_km = 100.0
58
-
59
- for i, station_i in station_coords.iterrows():
60
- for j, station_j in station_coords.iterrows():
61
- if i >= j:
62
- continue
63
-
64
- # Check basin constraint
65
- if station_i['basin_id'] != station_j['basin_id']:
66
- continue
67
-
68
- # Calculate distance
69
- dist = haversine_distance(
70
- station_i['lat'], station_i['lon'],
71
- station_j['lat'], station_j['lon']
72
- )
73
-
74
- if dist <= max_distance_km:
75
- edges.append([i, j])
76
- edges.append([j, i]) # Undirected
77
-
78
- if not edges:
79
- edges = [[i, i] for i in range(len(station_coords))]
80
-
81
- edge_index = np.array(edges).T
82
- print(f" ✓ {len(station_coords)} nodes, {edge_index.shape[1]} edges")
83
-
84
- # Initialize visualizer
85
- print("\n3. Initializing visualizer...")
86
- viz = GraphVisualizer(
87
- station_coords=station_coords,
88
- edge_index=edge_index,
89
- figsize=(12, 8)
90
- )
91
-
92
- # Plot 1: Spatial graph structure
93
- print("\n4. Plotting spatial graph...")
94
- fig1 = viz.plot_spatial_graph(
95
- title="Hydrological Network - Geographic Layout",
96
- show_labels=True,
97
- save_path=output_dir / "01_spatial_graph.png"
98
- )
99
- plt.close(fig1)
100
-
101
- # Plot 2: Spatial graph colored by discharge
102
- print("\n5. Plotting graph with discharge values...")
103
- # Get average discharge per station
104
- avg_discharge = fused_df.groupby('station_code')['discharge_m3s'].mean()
105
- discharge_values = station_coords['station_code'].map(avg_discharge).values
106
-
107
- fig2 = viz.plot_spatial_graph(
108
- node_values=discharge_values,
109
- value_label="Avg Discharge (m³/s)",
110
- title="Network Colored by Average Discharge",
111
- show_labels=True,
112
- save_path=output_dir / "02_discharge_spatial.png"
113
- )
114
- plt.close(fig2)
115
-
116
- # Plot 3: Feature snapshot for a specific date
117
- print("\n6. Plotting feature heatmap...")
118
- date = "2020-01-15"
119
- snapshot = fused_df[fused_df['date'] == date].copy()
120
- snapshot = snapshot.set_index('station_code').reindex(station_coords['station_code'])
121
-
122
- feature_cols = ['discharge_m3s', 'waterlevel_mm', 'temp_C', 'wind_speed_ms',
123
- 'avg_groundwater_level_m', 'avg_groundwater_depth_m', 'IDPR']
124
- available = [c for c in feature_cols if c in snapshot.columns]
125
- feature_matrix = snapshot[available].fillna(0).values
126
-
127
- fig3 = viz.plot_feature_heatmap(
128
- feature_matrix=feature_matrix,
129
- feature_names=available,
130
- title=f"Node Features - {date}",
131
- save_path=output_dir / "03_feature_snapshot.png"
132
- )
133
- plt.close(fig3)
134
-
135
- # Plot 4: Temporal feature evolution
136
- print("\n7. Plotting temporal evolution...")
137
- # Prepare temporal data
138
- dates = sorted(fused_df['date'].unique())[:14] # First 2 weeks
139
- temporal_data = []
140
-
141
- for date in dates:
142
- snapshot = fused_df[fused_df['date'] == date].copy()
143
- snapshot = snapshot.set_index('station_code').reindex(station_coords['station_code'])
144
- features = snapshot[available].fillna(0).values
145
- temporal_data.append(features)
146
-
147
- temporal_data = np.array(temporal_data) # (time, nodes, features)
148
-
149
- # Plot discharge evolution
150
- if 'discharge_m3s' in available:
151
- feat_idx = available.index('discharge_m3s')
152
- fig4 = viz.plot_temporal_evolution(
153
- temporal_data=temporal_data,
154
- feature_idx=feat_idx,
155
- feature_name='Discharge (m³/s)',
156
- dates=[d.strftime('%m-%d') for d in dates],
157
- title='Discharge Evolution - First 2 Weeks',
158
- save_path=output_dir / "04_discharge_evolution.png"
159
- )
160
- plt.close(fig4)
161
-
162
- # Plot temperature evolution
163
- if 'temp_C' in available:
164
- feat_idx = available.index('temp_C')
165
- fig5 = viz.plot_temporal_evolution(
166
- temporal_data=temporal_data,
167
- feature_idx=feat_idx,
168
- feature_name='Temperature (°C)',
169
- dates=[d.strftime('%m-%d') for d in dates],
170
- title='Temperature Evolution - First 2 Weeks',
171
- save_path=output_dir / "05_temperature_evolution.png"
172
- )
173
- plt.close(fig5)
174
-
175
- # Plot 5: Complete heatmap over time
176
- print("\n8. Plotting temporal heatmap...")
177
- fig6 = viz.plot_feature_heatmap(
178
- feature_matrix=temporal_data,
179
- feature_names=available,
180
- dates=[d.strftime('%m-%d') for d in dates],
181
- title='Features Over Time (First 2 Weeks)',
182
- save_path=output_dir / "06_temporal_heatmap.png"
183
- )
184
- plt.close(fig6)
185
-
186
- # Plot 6: Graph statistics overview
187
- print("\n9. Plotting graph statistics...")
188
- fig7 = viz.plot_graph_statistics(
189
- fused_df=fused_df,
190
- save_path=output_dir / "07_graph_statistics.png"
191
- )
192
- plt.close(fig7)
193
-
194
- # Summary
195
- print("\n" + "=" * 80)
196
- print("VISUALIZATION COMPLETE")
197
- print("=" * 80)
198
- print(f"\n✓ Generated 7 visualizations in {output_dir}/")
199
- print("\nGenerated plots:")
200
- print(" 01_spatial_graph.png - Geographic network layout")
201
- print(" 02_discharge_spatial.png - Network colored by discharge")
202
- print(" 03_feature_snapshot.png - Feature values on 2020-01-15")
203
- print(" 04_discharge_evolution.png - Discharge time series")
204
- print(" 05_temperature_evolution.png - Temperature time series")
205
- print(" 06_temporal_heatmap.png - All features over time")
206
- print(" 07_graph_statistics.png - Data overview statistics")
207
-
208
-
209
- if __name__ == "__main__":
210
- main()