Skip to content

Commit 047a6e6

Browse files
committed
Added excel export for climate indices notebook
1 parent 1cff50d commit 047a6e6

File tree

2 files changed

+180
-12
lines changed

2 files changed

+180
-12
lines changed

tools/code/gui_ci_timeseries_utils.py

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ def create_country_selector_widget(country_options):
204204
indent=False
205205
)
206206

207+
export_excel_chk = widgets.Checkbox(
208+
value=False,
209+
description='Export to Excel',
210+
disabled=False,
211+
indent=False
212+
)
213+
207214
# Custom ADM
208215
def select_file(b):
209216
root = tk.Tk()
@@ -1526,7 +1533,92 @@ def export_boundaries_to_gpkg(gdf, country, adm_level, index, year, output_dir,
15261533
import traceback
15271534
traceback.print_exc()
15281535
return None, all_gdf
1529-
1536+
1537+
# Function to export statistics to Excel
1538+
def export_to_excel(all_gdf, country, adm_level, index, output_dir, mode='baseline', ssp=None):
1539+
"""
1540+
Export zonal statistics to an Excel file.
1541+
1542+
Args:
1543+
all_gdf: Combined GeoDataFrame with all years
1544+
country: Country ISO code
1545+
adm_level: Administrative level
1546+
index: Climate index (may include season suffix)
1547+
output_dir: Output directory
1548+
mode: Data mode ('baseline' or 'projections')
1549+
ssp: SSP scenario (only for projections mode)
1550+
1551+
Returns:
1552+
Path to the saved Excel file or None if failed
1553+
"""
1554+
if all_gdf is None:
1555+
print("No data to export")
1556+
return None
1557+
1558+
if not os.path.exists(output_dir):
1559+
os.makedirs(output_dir)
1560+
1561+
# Create filename
1562+
if mode.lower() == 'projections' and ssp:
1563+
output_path = os.path.join(output_dir, f"{country.lower()}_{index}_{mode.lower()}_{ssp}_ADM{adm_level}_statistics.xlsx")
1564+
else:
1565+
output_path = os.path.join(output_dir, f"{country.lower()}_{index}_{mode.lower()}_ADM{adm_level}_statistics.xlsx")
1566+
1567+
try:
1568+
# Create a copy without geometry for Excel export
1569+
df_export = all_gdf.drop(columns=['geometry'], errors='ignore')
1570+
1571+
# Export to Excel with multiple sheets
1572+
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
1573+
# Write full data
1574+
df_export.to_excel(writer, sheet_name='Zonal Statistics', index=False)
1575+
1576+
# Extract year columns for timeseries
1577+
year_cols = [col for col in df_export.columns if col.startswith('Y') and '_mean' in col]
1578+
1579+
if year_cols:
1580+
# Create a transposed timeseries view
1581+
id_cols = [col for col in df_export.columns
1582+
if any(x in col.upper() for x in ['NAME', 'ID', 'CODE'])
1583+
and col not in year_cols]
1584+
1585+
# If we have identifier columns, create a more readable timeseries
1586+
if id_cols:
1587+
# Select first name column for labeling
1588+
name_col = next((col for col in id_cols if 'NAME' in col.upper()), id_cols[0])
1589+
1590+
# Create timeseries data
1591+
timeseries_data = []
1592+
for idx, row in df_export.iterrows():
1593+
location_name = row[name_col]
1594+
for year_col in sorted(year_cols):
1595+
# Extract year from column name (e.g., "Y2020_mean" -> 2020)
1596+
year = year_col.split('_')[0][1:] # Remove 'Y' prefix
1597+
timeseries_data.append({
1598+
'Location': location_name,
1599+
'Year': year,
1600+
'Value': row[year_col]
1601+
})
1602+
1603+
timeseries_df = pd.DataFrame(timeseries_data)
1604+
timeseries_df.to_excel(writer, sheet_name='Timeseries', index=False)
1605+
1606+
# Create metadata sheet
1607+
metadata = pd.DataFrame({
1608+
'Parameter': ['Country', 'Climate Index', 'Administrative Level', 'Mode', 'SSP Scenario', 'Export Date'],
1609+
'Value': [country, index, f'ADM{adm_level}', mode, ssp if ssp else 'N/A',
1610+
pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')]
1611+
})
1612+
metadata.to_excel(writer, sheet_name='Metadata', index=False)
1613+
1614+
print(f"Saved statistics to {output_path}")
1615+
return output_path
1616+
except Exception as e:
1617+
print(f"Error exporting to Excel: {e}")
1618+
import traceback
1619+
traceback.print_exc()
1620+
return None
1621+
15301622
# Define a function to handle changes in the country combobox
15311623
def on_country_change(change):
15321624
with output:
@@ -1841,7 +1933,24 @@ def run_analysis(b):
18411933
print(f"Exported {len(all_grid_figs)} grid maps and {len(all_zonal_figs)} zonal maps")
18421934
if export_boundaries_chk.value:
18431935
print(f"Exported data for {len(all_zonal_data)} time periods to a combined GeoPackage")
1844-
1936+
1937+
# Export to Excel if checkbox is checked
1938+
if export_excel_chk.value and combined_gdf is not None:
1939+
print("Exporting statistics to Excel...")
1940+
excel_path = export_to_excel(
1941+
combined_gdf,
1942+
iso_a3,
1943+
selected_adm_level,
1944+
selected_index,
1945+
output_dir,
1946+
mode=selected_mode,
1947+
ssp=selected_ssp if selected_mode == 'Projections' else None
1948+
)
1949+
if excel_path:
1950+
print(f"Statistics exported to: {excel_path}")
1951+
else:
1952+
print("Failed to export to Excel")
1953+
18451954
elapsed_time = time.time() - start_time
18461955
print(f"Analysis completed in {elapsed_time:.2f} seconds")
18471956

@@ -1909,7 +2018,8 @@ def create_layout():
19092018
preview_container = widgets.VBox([
19102019
preview_chk,
19112020
export_charts_chk,
1912-
export_boundaries_chk
2021+
export_boundaries_chk,
2022+
export_excel_chk
19132023
], layout=widgets.Layout(width='100%', margin='10px 0'))
19142024

19152025
footer = widgets.VBox([

tools/code/gui_ci_utils.py

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ def create_country_selector_widget(country_options):
188188
indent=False
189189
)
190190

191+
export_excel_chk = widgets.Checkbox(
192+
value=False,
193+
description='Export to Excel',
194+
disabled=False,
195+
indent=False
196+
)
197+
191198
# Custom ADM Functions
192199
def select_file(b):
193200
root = tk.Tk()
@@ -1139,18 +1146,18 @@ def export_boundaries_to_gpkg(gdf, country, index, projection, time_period, outp
11391146
if gdf is None:
11401147
print("No boundary data to export")
11411148
return None
1142-
1149+
11431150
if not os.path.exists(output_dir):
11441151
os.makedirs(output_dir)
1145-
1152+
11461153
# Create filename
11471154
output_path = os.path.join(output_dir, f"{country.lower()}_{index}_{projection}_{time_period}_boundaries.gpkg")
1148-
1155+
11491156
try:
11501157
# Ensure the GeoDataFrame has a proper CRS
11511158
if gdf.crs is None:
11521159
gdf = gdf.set_crs("EPSG:4326")
1153-
1160+
11541161
# Export to GeoPackage
11551162
layer_name = f"{index}_{projection}_{time_period}"
11561163
gdf.to_file(output_path, layer=layer_name, driver="GPKG")
@@ -1159,6 +1166,40 @@ def export_boundaries_to_gpkg(gdf, country, index, projection, time_period, outp
11591166
except Exception as e:
11601167
print(f"Error exporting boundaries to GeoPackage: {e}")
11611168
return None
1169+
1170+
# Add a function to export statistics to Excel
1171+
def export_to_excel(gdf, country, index, projection, time_period, output_dir):
1172+
"""Export zonal statistics to an Excel file."""
1173+
if gdf is None:
1174+
print("No data to export")
1175+
return None
1176+
1177+
if not os.path.exists(output_dir):
1178+
os.makedirs(output_dir)
1179+
1180+
# Create filename
1181+
output_path = os.path.join(output_dir, f"{country.lower()}_{index}_{projection}_{time_period}_statistics.xlsx")
1182+
1183+
try:
1184+
# Create a copy without geometry for Excel export
1185+
df_export = gdf.drop(columns=['geometry'], errors='ignore')
1186+
1187+
# Export to Excel
1188+
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
1189+
df_export.to_excel(writer, sheet_name='Zonal Statistics', index=False)
1190+
1191+
# Create a summary sheet with metadata
1192+
metadata = pd.DataFrame({
1193+
'Parameter': ['Country', 'Climate Index', 'Projection', 'Time Period', 'Export Date'],
1194+
'Value': [country, index, projection, time_period, pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')]
1195+
})
1196+
metadata.to_excel(writer, sheet_name='Metadata', index=False)
1197+
1198+
print(f"Saved statistics to {output_path}")
1199+
return output_path
1200+
except Exception as e:
1201+
print(f"Error exporting to Excel: {e}")
1202+
return None
11621203

11631204
# Define a function to handle changes in the ADM level dropdown
11641205
def on_adm_level_change(change):
@@ -1375,17 +1416,33 @@ def run_analysis(b):
13751416
if export_boundaries_chk.value and gdf_change is not None:
13761417
print("Exporting boundary data to GeoPackage...")
13771418
export_path = export_boundaries_to_gpkg(
1378-
gdf_change,
1379-
iso_a3,
1380-
selected_index,
1419+
gdf_change,
1420+
iso_a3,
1421+
selected_index,
13811422
selected_projection,
1382-
selected_time_period,
1423+
selected_time_period,
13831424
output_dir
13841425
)
13851426
if export_path:
13861427
print(f"Boundary values exported to: {export_path}")
13871428
else:
13881429
print("Failed to export boundary values")
1430+
1431+
# Export to Excel if the checkbox is checked
1432+
if export_excel_chk.value and gdf_change is not None:
1433+
print("Exporting statistics to Excel...")
1434+
excel_path = export_to_excel(
1435+
gdf_change,
1436+
iso_a3,
1437+
selected_index,
1438+
selected_projection,
1439+
selected_time_period,
1440+
output_dir
1441+
)
1442+
if excel_path:
1443+
print(f"Statistics exported to: {excel_path}")
1444+
else:
1445+
print("Failed to export to Excel")
13891446

13901447
# Display figures
13911448
with chart_output:
@@ -1453,7 +1510,8 @@ def create_layout():
14531510
preview_container = widgets.VBox([
14541511
preview_chk,
14551512
export_charts_chk,
1456-
export_boundaries_chk
1513+
export_boundaries_chk,
1514+
export_excel_chk
14571515
], layout=widgets.Layout(width='100%', margin='10px 0'))
14581516

14591517
footer = widgets.VBox([

0 commit comments

Comments
 (0)