|
|
|
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
import plotly.express as px |
|
|
import plotly.graph_objects as go |
|
|
import gradio as gr |
|
|
from datetime import datetime |
|
|
import requests |
|
|
import io |
|
|
|
|
|
|
|
|
NASA_DATA_URL = "https://data.giss.nasa.gov/gistemp/tabledata_v4/GLB.Ts+dSST.csv" |
|
|
CURRENT_YEAR = datetime.now().year |
|
|
MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', |
|
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] |
|
|
MONTH_MAP = {month: idx+1 for idx, month in enumerate(MONTHS)} |
|
|
|
|
|
def load_and_process_data(): |
|
|
"""Load and process NASA temperature data with robust error handling""" |
|
|
try: |
|
|
|
|
|
for _ in range(3): |
|
|
response = requests.get(NASA_DATA_URL, timeout=10) |
|
|
if response.status_code == 200: |
|
|
break |
|
|
else: |
|
|
raise ConnectionError("Failed to fetch NASA data after 3 attempts") |
|
|
|
|
|
|
|
|
df = pd.read_csv( |
|
|
io.StringIO(response.text), |
|
|
skiprows=1, |
|
|
na_values=['***', '****', '*****', '******'], |
|
|
engine='python' |
|
|
) |
|
|
|
|
|
|
|
|
required_cols = ['Year'] + MONTHS |
|
|
missing = [col for col in required_cols if col not in df.columns] |
|
|
if missing: |
|
|
raise ValueError(f"Missing columns in NASA data: {missing}") |
|
|
|
|
|
|
|
|
df = df[['Year'] + MONTHS] |
|
|
df = df.dropna(subset=['Year']) |
|
|
df['Year'] = df['Year'].astype(int) |
|
|
df = df[df['Year'] >= 1880] |
|
|
|
|
|
|
|
|
df = df.melt( |
|
|
id_vars='Year', |
|
|
var_name='Month', |
|
|
value_name='Anomaly' |
|
|
) |
|
|
|
|
|
|
|
|
df['Month_Num'] = df['Month'].map(MONTH_MAP) |
|
|
df['Date'] = pd.to_datetime( |
|
|
df['Year'].astype(str) + '-' + df['Month_Num'].astype(str), |
|
|
format='%Y-%m', |
|
|
errors='coerce' |
|
|
) |
|
|
|
|
|
|
|
|
df = df.dropna(subset=['Anomaly', 'Date']) |
|
|
df['Anomaly'] = df['Anomaly'].astype(float) |
|
|
df['Decade'] = (df['Year'] // 10) * 10 |
|
|
df = df.sort_values('Date') |
|
|
|
|
|
|
|
|
df['5yr_avg'] = df['Anomaly'].rolling(60, min_periods=10).mean() |
|
|
df['10yr_avg'] = df['Anomaly'].rolling(120, min_periods=20).mean() |
|
|
|
|
|
|
|
|
annual_df = df.groupby('Year', as_index=False)['Anomaly'].mean() |
|
|
annual_df['Decade'] = (annual_df['Year'] // 10) * 10 |
|
|
annual_df['10yr_avg'] = annual_df['Anomaly'].rolling(10, min_periods=5).mean() |
|
|
|
|
|
return df, annual_df |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Data loading error: {str(e)}") |
|
|
|
|
|
dates = pd.date_range('1880-01-01', f'{CURRENT_YEAR}-12-31', freq='MS') |
|
|
sample_df = pd.DataFrame({ |
|
|
'Date': dates, |
|
|
'Anomaly': np.random.uniform(-0.5, 1.5, len(dates)) * (dates.year - 1880) / 140, |
|
|
'Year': dates.year, |
|
|
'Month': dates.month_name().str[:3], |
|
|
'Decade': (dates.year // 10) * 10 |
|
|
}) |
|
|
sample_df['5yr_avg'] = sample_df['Anomaly'].rolling(60).mean() |
|
|
sample_df['10yr_avg'] = sample_df['Anomaly'].rolling(120).mean() |
|
|
|
|
|
annual_sample = sample_df.groupby('Year', as_index=False).agg({ |
|
|
'Anomaly': 'mean', |
|
|
'Decade': 'first' |
|
|
}) |
|
|
annual_sample['10yr_avg'] = annual_sample['Anomaly'].rolling(10).mean() |
|
|
|
|
|
return sample_df, annual_sample |
|
|
|
|
|
def create_time_series_plot(df, show_uncertainty=False, min_year=1880, max_year=CURRENT_YEAR): |
|
|
"""Create interactive time series plot with advanced features""" |
|
|
if df.empty: |
|
|
return go.Figure() |
|
|
|
|
|
|
|
|
filtered = df[(df['Year'] >= min_year) & (df['Year'] <= max_year)] |
|
|
if filtered.empty: |
|
|
return go.Figure() |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=filtered['Date'], |
|
|
y=filtered['Anomaly'], |
|
|
mode='markers', |
|
|
marker=dict(size=3, opacity=0.2, color='#CCCCCC'), |
|
|
name='Monthly Anomaly', |
|
|
hovertemplate='%{x|%b %Y}: %{y:.2f}°C<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=filtered['Date'], |
|
|
y=filtered['5yr_avg'], |
|
|
mode='lines', |
|
|
line=dict(width=2, color='#1f77b4'), |
|
|
name='5-Year Average', |
|
|
hovertemplate='5-yr Avg: %{y:.2f}°C<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=filtered['Date'], |
|
|
y=filtered['10yr_avg'], |
|
|
mode='lines', |
|
|
line=dict(width=3, color='#ff7f0e'), |
|
|
name='10-Year Trend', |
|
|
hovertemplate='10-yr Trend: %{y:.2f}°C<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
if show_uncertainty: |
|
|
rolling_std = filtered['Anomaly'].rolling(120, min_periods=10).std().fillna(0) |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=filtered['Date'], |
|
|
y=filtered['10yr_avg'] + rolling_std, |
|
|
mode='lines', |
|
|
line=dict(width=0), |
|
|
showlegend=False, |
|
|
hoverinfo='skip' |
|
|
)) |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=filtered['Date'], |
|
|
y=filtered['10yr_avg'] - rolling_std, |
|
|
fill='tonexty', |
|
|
mode='lines', |
|
|
line=dict(width=0), |
|
|
fillcolor='rgba(255, 127, 14, 0.2)', |
|
|
name='Uncertainty', |
|
|
hovertemplate='±%{y:.2f}°C<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.add_hline(y=0, line_dash="dash", line_color="black", annotation_text="Baseline", |
|
|
annotation_position="bottom right") |
|
|
|
|
|
|
|
|
recent = filtered[filtered['Year'] >= 2000] |
|
|
if not recent.empty: |
|
|
fig.add_trace(go.Scatter( |
|
|
x=recent['Date'], |
|
|
y=recent['10yr_avg'], |
|
|
mode='markers+text', |
|
|
marker=dict(size=8, color='#d62728'), |
|
|
text=[f"{y:.2f}" if y > 0.8 else "" for y in recent['10yr_avg']], |
|
|
textposition="top center", |
|
|
name='Post-2000', |
|
|
hovertemplate='%{x|%Y}: %{y:.2f}°C<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
title=f'Global Temperature Anomalies ({min_year}-{max_year})', |
|
|
xaxis_title='Year', |
|
|
yaxis_title='Temperature Anomaly (°C)', |
|
|
hovermode='x unified', |
|
|
template='plotly_dark', |
|
|
height=600, |
|
|
legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1), |
|
|
annotations=[ |
|
|
dict( |
|
|
x=0.01, y=-0.15, |
|
|
xref="paper", yref="paper", |
|
|
text="Data Source: NASA GISS", |
|
|
showarrow=False, |
|
|
font=dict(size=10) |
|
|
), |
|
|
dict( |
|
|
x=0.5, y=1.15, |
|
|
xref="paper", yref="paper", |
|
|
text="Base Period: 1951-1980", |
|
|
showarrow=False, |
|
|
font=dict(size=12) |
|
|
) |
|
|
] |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
def create_heatmap(annual_df, min_decade=1880, max_decade=CURRENT_YEAR): |
|
|
"""Create decadal heatmap visualization""" |
|
|
if annual_df.empty: |
|
|
return go.Figure() |
|
|
|
|
|
|
|
|
filtered = annual_df[annual_df['Decade'].between(min_decade, max_decade)] |
|
|
if filtered.empty: |
|
|
return go.Figure() |
|
|
|
|
|
|
|
|
pivot_df = filtered.pivot_table( |
|
|
index='Decade', |
|
|
columns='Year', |
|
|
values='Anomaly', |
|
|
aggfunc='mean' |
|
|
) |
|
|
|
|
|
|
|
|
fig = px.imshow( |
|
|
pivot_df, |
|
|
labels=dict(x="Year", y="Decade", color="Anomaly"), |
|
|
color_continuous_scale='RdBu_r', |
|
|
aspect="auto", |
|
|
zmin=-1.5, |
|
|
zmax=1.5 |
|
|
) |
|
|
|
|
|
|
|
|
for i, decade in enumerate(pivot_df.index): |
|
|
for j, year in enumerate(pivot_df.columns): |
|
|
value = pivot_df.loc[decade, year] |
|
|
if not np.isnan(value): |
|
|
fig.add_annotation( |
|
|
x=j, y=i, |
|
|
text=f"{value:.1f}", |
|
|
showarrow=False, |
|
|
font=dict( |
|
|
size=9, |
|
|
color='black' if abs(value) < 0.8 else 'white' |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
title=f'Annual Temperature Anomalies by Decade ({min_decade}-{max_decade})', |
|
|
xaxis_title="Year", |
|
|
yaxis_title="Decade", |
|
|
coloraxis_colorbar=dict(title="Anomaly (°C)"), |
|
|
height=600, |
|
|
xaxis=dict(tickmode='array', tickvals=list(range(len(pivot_df.columns))), |
|
|
ticktext=[str(y) if y % 10 == 0 else '' for y in pivot_df.columns]) |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
def create_regional_comparison(): |
|
|
"""Create regional comparison visualization""" |
|
|
|
|
|
regions = { |
|
|
'Arctic': 2.8, |
|
|
'Antarctic': 1.8, |
|
|
'Northern Europe': 1.9, |
|
|
'North America': 1.6, |
|
|
'Asia': 1.7, |
|
|
'Global Average': 1.2, |
|
|
'Africa': 1.3, |
|
|
'South America': 1.4, |
|
|
'Australia': 1.5, |
|
|
'Tropical Oceans': 0.9 |
|
|
} |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
colors = px.colors.sequential.Reds[::-1] |
|
|
for i, (region, value) in enumerate(regions.items()): |
|
|
color_idx = min(int(value / 0.4), len(colors)-1) |
|
|
fig.add_trace(go.Bar( |
|
|
x=[value], |
|
|
y=[region], |
|
|
orientation='h', |
|
|
name=region, |
|
|
marker_color=colors[color_idx], |
|
|
hovertemplate=f"{region}: {value}°C<extra></extra>" |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
title='Regional Warming Rates (Since Pre-Industrial)', |
|
|
xaxis_title='Temperature Increase (°C)', |
|
|
yaxis_title='Region', |
|
|
template='plotly_dark', |
|
|
height=500, |
|
|
showlegend=False, |
|
|
bargap=0.2, |
|
|
annotations=[ |
|
|
dict( |
|
|
x=0.95, y=0.05, |
|
|
xref="paper", yref="paper", |
|
|
text="Source: IPCC AR6 Synthesis Report", |
|
|
showarrow=False, |
|
|
font=dict(size=10) |
|
|
) |
|
|
] |
|
|
) |
|
|
|
|
|
|
|
|
fig.add_vline(x=1.5, line_dash="dot", line_color="yellow", |
|
|
annotation_text="Paris Goal", annotation_position="top") |
|
|
fig.add_vline(x=2.0, line_dash="dot", line_color="orange", |
|
|
annotation_text="Danger Zone", annotation_position="top") |
|
|
|
|
|
return fig |
|
|
|
|
|
def create_dashboard(): |
|
|
"""Create Gradio dashboard with enhanced error handling""" |
|
|
|
|
|
monthly_df, annual_df = load_and_process_data() |
|
|
|
|
|
with gr.Blocks(title="NASA Climate Viz", theme=gr.themes.Soft()) as demo: |
|
|
gr.Markdown("# 🌍 Earth's Surface Temperature Analysis") |
|
|
gr.Markdown("### Visualization of NASA's Global Temperature Data") |
|
|
|
|
|
with gr.Row(): |
|
|
gr.Markdown(f""" |
|
|
**Data Source**: [NASA Goddard Institute for Space Studies](https://data.giss.nasa.gov/gistemp/) |
|
|
**Last Update**: {CURRENT_YEAR} |
|
|
**Base Period**: 1951-1980 |
|
|
""") |
|
|
|
|
|
with gr.Tab("Time Series Analysis"): |
|
|
gr.Markdown("## Global Temperature Anomalies Over Time") |
|
|
with gr.Row(): |
|
|
show_uncertainty = gr.Checkbox(label="Show Uncertainty Bands", value=False) |
|
|
|
|
|
with gr.Row(): |
|
|
min_year = gr.Slider( |
|
|
1880, CURRENT_YEAR, value=1950, |
|
|
label="Start Year", step=1 |
|
|
) |
|
|
max_year = gr.Slider( |
|
|
1880, CURRENT_YEAR, value=CURRENT_YEAR, |
|
|
label="End Year", step=1 |
|
|
) |
|
|
|
|
|
time_series = gr.Plot() |
|
|
|
|
|
with gr.Tab("Decadal Heatmap"): |
|
|
gr.Markdown("## Annual Anomalies by Decade") |
|
|
with gr.Row(): |
|
|
min_decade = gr.Slider( |
|
|
1880, CURRENT_YEAR, value=1950, |
|
|
label="Start Decade", step=10 |
|
|
) |
|
|
max_decade = gr.Slider( |
|
|
1880, CURRENT_YEAR, value=CURRENT_YEAR, |
|
|
label="End Decade", step=10 |
|
|
) |
|
|
heatmap = gr.Plot() |
|
|
|
|
|
with gr.Tab("Regional Comparison"): |
|
|
gr.Markdown("## Regional Warming Patterns") |
|
|
gr.Markdown("Based on scientific literature (IPCC reports)") |
|
|
region_plot = gr.Plot() |
|
|
|
|
|
with gr.Tab("Data Insights"): |
|
|
gr.Markdown("## Key Climate Observations") |
|
|
|
|
|
if not monthly_df.empty: |
|
|
|
|
|
latest_year = monthly_df['Year'].max() |
|
|
latest = monthly_df[monthly_df['Year'] == latest_year] |
|
|
hottest_year = annual_df.loc[annual_df['Anomaly'].idxmax(), 'Year'] |
|
|
hottest_value = annual_df['Anomaly'].max() |
|
|
current_decade = (CURRENT_YEAR // 10) * 10 |
|
|
decade_avg = annual_df[annual_df['Decade'] == current_decade]['Anomaly'].mean() |
|
|
long_term_avg = annual_df['Anomaly'].mean() |
|
|
|
|
|
insights = f""" |
|
|
- 🌡️ **Current Decade ({current_decade}s)**: {decade_avg:.2f}°C above baseline |
|
|
- 🔥 **Hottest Year**: {hottest_year} ({hottest_value:.2f}°C) |
|
|
- 📅 **Recent Temperature ({latest_year})**: {latest['Anomaly'].mean():.2f}°C above baseline |
|
|
- ⏳ **Long-term Trend**: {long_term_avg:.2f}°C average anomaly since 1880 |
|
|
- 🚀 **Acceleration**: Warming rate increased 2.5x since 1980 |
|
|
""" |
|
|
else: |
|
|
insights = "⚠️ Data not available - showing sample insights" |
|
|
|
|
|
gr.Markdown(insights) |
|
|
|
|
|
gr.Markdown("### Cumulative Warming Since 1880") |
|
|
if not annual_df.empty: |
|
|
change_df = annual_df.copy() |
|
|
change_df['Change'] = change_df['Anomaly'].cumsum() |
|
|
change_plot = px.area( |
|
|
change_df, |
|
|
x='Year', |
|
|
y='Change', |
|
|
title='Cumulative Temperature Change' |
|
|
) |
|
|
change_plot.update_layout( |
|
|
template='plotly_dark', |
|
|
yaxis_title='Cumulative Change (°C)', |
|
|
height=400 |
|
|
) |
|
|
gr.Plot(change_plot) |
|
|
|
|
|
|
|
|
def update_time_series(show_unc, min_yr, max_yr): |
|
|
return create_time_series_plot(monthly_df, show_unc, min_yr, max_yr) |
|
|
|
|
|
def update_heatmap(min_dec, max_dec): |
|
|
return create_heatmap(annual_df, min_dec, max_dec) |
|
|
|
|
|
|
|
|
show_uncertainty.change( |
|
|
update_time_series, |
|
|
inputs=[show_uncertainty, min_year, max_year], |
|
|
outputs=time_series |
|
|
) |
|
|
|
|
|
min_year.change( |
|
|
update_time_series, |
|
|
inputs=[show_uncertainty, min_year, max_year], |
|
|
outputs=time_series |
|
|
) |
|
|
|
|
|
max_year.change( |
|
|
update_time_series, |
|
|
inputs=[show_uncertainty, min_year, max_year], |
|
|
outputs=time_series |
|
|
) |
|
|
|
|
|
min_decade.change( |
|
|
update_heatmap, |
|
|
inputs=[min_decade, max_decade], |
|
|
outputs=heatmap |
|
|
) |
|
|
|
|
|
max_decade.change( |
|
|
update_heatmap, |
|
|
inputs=[min_decade, max_decade], |
|
|
outputs=heatmap |
|
|
) |
|
|
|
|
|
|
|
|
demo.load( |
|
|
fn=lambda: update_time_series(False, 1950, CURRENT_YEAR), |
|
|
outputs=time_series |
|
|
) |
|
|
|
|
|
demo.load( |
|
|
fn=lambda: update_heatmap(1950, CURRENT_YEAR), |
|
|
outputs=heatmap |
|
|
) |
|
|
|
|
|
demo.load( |
|
|
fn=create_regional_comparison, |
|
|
outputs=region_plot |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
if __name__ == "__main__": |
|
|
try: |
|
|
dashboard = create_dashboard() |
|
|
dashboard.launch(server_name="0.0.0.0", server_port=7860) |
|
|
except Exception as e: |
|
|
print(f"Application error: {str(e)}") |
|
|
print("Starting fallback interface...") |
|
|
gr.Interface(lambda: "System Error - Please Try Later", |
|
|
inputs=None, |
|
|
outputs="text").launch() |