@@ -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
208215def 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
15311623def 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 ([
0 commit comments