Skip to content

Instantly share code, notes, and snippets.

@matthewwiese
Created September 28, 2025 17:37
Show Gist options
  • Select an option

  • Save matthewwiese/84b53d28c942ebd067b177ddc5571acb to your computer and use it in GitHub Desktop.

Select an option

Save matthewwiese/84b53d28c942ebd067b177ddc5571acb to your computer and use it in GitHub Desktop.
Total Annual Precipitation at Cornell University Using NRCC CLIMOD 2 Data
Station 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
BROOKTONDALE 1.3 NW M M
BROOKTONDALE 2.7 SE M M M
CAROLINE CENTER 0.1 SE M M M M M M M M M M M M M M
DANBY 2.5 NE M
EAST ITHACA 2.6 ENE M M M
EAST ITHACA 3.6 SE M
EAST ITHACA 4.7 E M M M M M M M M M M M M M M M 46.58 M
ENFIELD 0.8 ENE M M M M M M M
FREEVILLE 1 NE 31.69 39.5 29.51 37.27 41.41 32.48 38.01 M M 36.95 M M 37.96 M 43.44 34.66 47.57 M M 31.42 37.96 31.79 38.32 43.06 M 39.67 M M 40.71 35.41 39.02 47.56 33.32 41.98 36.85 37.71 32.99 M 44.01 41.71 34.26 47.25 M 35.48 36.74
FREEVILLE 1.5 NE M M M M M M M M M M M M M M M M M M M
FREEVILLE 2.6 N M M M M M M
FREEVILLE 3.7 W M M M 49.75 37.01 43.53 41.79 38.96 37.27 M M 43.95 M M 35.96 43.49 41.38
FREEVILLE 4.2 S M
GROTON 0.5 NW M 44.91 M 41.60 50.77 41.06 44.37 42.46 42.67 38.49 49.98 50.10 45.81 36.82 53.55 41.30 M 45.49
GROTON 0.6 ESE M M M M M M M
GROTON 3.2 SSW M M M M 38.78 42.69 M M M M 36.45 49.97 M M M
ITHACA 0.2 N M
ITHACA 0.5 N M M
ITHACA 1.1 SE M M M M M M M M M
ITHACA 1.2 WNW M
ITHACA 1.8 WSW M M M M
ITHACA 2.0 ESE M M M M M
ITHACA 2.2 NW M M M M M M M M M M
ITHACA 2.5 SW M
ITHACA 2.6 NW M M M M M M M M M M M M M M M M M
ITHACA 3.0 N M M
ITHACA 3.0 NNW M M
ITHACA 3.1 SSE M
ITHACA 3.4 SE M 36.59
ITHACA 4.3 S M M
ITHACA 5.4 SSW M M M M M
ITHACA 6.3 E M
ITHACA 6.4 E M M M M M M M
ITHACA 6.9 WNW M
ITHACA 8.5 W M M
ITHACA CORNELL UNIV 31.35 39.05 M 35.64 39.84 31.53 37.53 32.80 M 37.07 43.74 30.86 38.12 M 42.01 29.09 47.76 30.16 34.13 29.77 38.12 34.65 39.32 44.4 45.26 40.18 41.00 38.19 37.86 33.40 38.59 51.22 32.83 39.01 38.49 33.79 31.81 42.54 41.66 41.48 36.81 47.90 35.37 42.47 36.45
ITHACA TOMPKINS CNTY M
LANSING 4.3 NW M
LANSING 6.6 NW M M M M M M M M M M M
LANSING 8.9 N M M M M M M M
LANSING 9.1 NW M 40.06
NEWFIELD 2.5 S M M M M M M M M M M M M M
NEWFIELD HAMLET 0.9 E M
NEWFIELD HAMLET 1.7 NW M
NEWFIELD HAMLET 4.3 S M M M M M
SLATERVILLE SPRINGS 0.5 ESE M M
SLATERVILLE SPRINGS 1.0 S M M M M
SOUTH HILL 0.4 ESE M M M M M M M
SPENCER 9.5 N M
TRUMANSBURG 0.3 NNE M M M
TRUMANSBURG 0.3 SSE M
TRUMANSBURG 0.4 WNW M M M M M M M M M M M M M M M M
TRUMANSBURG 2.4 SSE M
TRUMANSBURG 4.3 NE M
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Read the data
df = pd.read_csv('combined_precipitation.csv')
# Filter for Ithaca Cornell University
ithaca_row = df[df['Station'] == 'ITHACA CORNELL UNIV'].iloc[0]
# Extract years and precipitation values
years = list(range(1980, 2025))
precip_values = []
for year in years:
value = ithaca_row[str(year)]
if value == 'M' or pd.isna(value):
precip_values.append(np.nan)
else:
precip_values.append(float(value))
# Create DataFrame for easier handling
data = pd.DataFrame({
'Year': years,
'Precipitation': precip_values
})
# Identify valid (non-missing) data points
valid_mask = ~np.isnan(data['Precipitation'])
valid_years = data['Year'][valid_mask].values
valid_precip = data['Precipitation'][valid_mask].values
# Create moving average trend line
# Use 5-year moving average to show long-term trends
window_size = 5
if len(valid_years) >= window_size:
# Calculate moving average for valid data
moving_avg = pd.Series(data['Precipitation']).rolling(window=window_size, center=True).mean()
# Get non-null moving average values and corresponding years
valid_ma_mask = ~moving_avg.isna()
ma_years = data['Year'][valid_ma_mask].values
ma_values = moving_avg[valid_ma_mask].values
# Create the plot
fig, ax = plt.subplots(figsize=(14, 8))
# Plot actual data points
ax.scatter(valid_years, valid_precip, color='#2E86AB', s=50, alpha=0.8,
label='Observed Annual Precipitation', zorder=3)
# Plot moving average trend line
if len(valid_years) >= window_size:
ax.plot(ma_years, ma_values, color='#A23B72', linewidth=2.5,
alpha=0.7, label=f'{window_size}-Year Moving Average', zorder=2)
# Connect consecutive valid points with light lines
ax.plot(valid_years, valid_precip, color='#2E86AB', linewidth=1,
alpha=0.4, zorder=1)
# Mark missing data years
missing_years = data['Year'][~valid_mask].values
if len(missing_years) > 0:
ax.scatter(missing_years, [ax.get_ylim()[0] + 1] * len(missing_years),
marker='x', color='red', s=60, alpha=0.6,
label='Missing Data', zorder=3)
# Calculate and add statistics
mean_precip = np.nanmean(data['Precipitation'])
ax.axhline(y=mean_precip, color='#F18F01', linestyle='--', alpha=0.7,
linewidth=2, label=f'Long-term Average ({mean_precip:.1f} inches)')
# Styling
ax.set_xlabel('Year', fontsize=12, fontweight='bold')
ax.set_ylabel('Annual Precipitation (inches)', fontsize=12, fontweight='bold')
ax.set_title('Annual Precipitation Trends at Ithaca Cornell University\n1980-2024',
fontsize=14, fontweight='bold', pad=20)
ax.grid(True, alpha=0.3, linestyle='-', linewidth=0.5)
ax.set_xlim(1979, 2025)
# Add some styling
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_linewidth(0.5)
ax.spines['bottom'].set_linewidth(0.5)
# Legend
ax.legend(loc='upper left', frameon=True, fancybox=True, shadow=True)
# Add summary statistics as text box
stats_text = f"""Data Summary:
• Valid observations: {len(valid_years)}/{len(years)} years
• Missing data: {len(missing_years)} years
• Average precipitation: {mean_precip:.1f} inches
• Min: {np.nanmin(data['Precipitation']):.1f} inches ({data.loc[data['Precipitation'].idxmin(), 'Year']})
• Max: {np.nanmax(data['Precipitation']):.1f} inches ({data.loc[data['Precipitation'].idxmax(), 'Year']})"""
ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, fontsize=9,
verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
plt.tight_layout()
plt.savefig('ithaca_precipitation_1980_2024.png', dpi=300, bbox_inches='tight')
plt.show()
# Print missing years for reference
print("Missing data years:", missing_years.tolist() if len(missing_years) > 0 else "None")
print(f"Data coverage: {len(valid_years)}/{len(years)} years ({len(valid_years)/len(years)*100:.1f}%)")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment