Historical time-series analysis of Many Glacier, Montana¶
This analysis looks at climate data from Many Glacier, MT using the NOAA/NCEI API.
In [1]:
# Import python libraries
import pandas as pd # working with datafames
import geopandas as gpd # working with vector data
import osmnx as ox # creating maps with open street maps
import holoviews as hv # creating interactive plots
import hvplot.pandas
import numpy as np # for adding trendline to plot
import matplotlib.pyplot as plt # for plotting
In [2]:
# Build endpoint request from NOAA/NCEI for Many Glacier, MT
many_glacier_url = (
"https://www.ncei.noaa.gov/access/services/data/v1"
"?dataset=daily-summaries"
"&stations=USS0013A27S"
"&startDate=1976-09-30"
"&endDate=2024-04-25"
"&dataTypes=TOBS,PRCP,WESD"
"&includeStationName=true"
"&includeStationLocation=1"
"&units=standard")
many_glacier_url
Out[2]:
'https://www.ncei.noaa.gov/access/services/data/v1?dataset=daily-summaries&stations=USS0013A27S&startDate=1976-09-30&endDate=2024-04-25&dataTypes=TOBS,PRCP,WESD&includeStationName=true&includeStationLocation=1&units=standard'
In [3]:
# Open up data using pandas
many_glacier_df = pd.read_csv(many_glacier_url,
parse_dates=True,
index_col='DATE',
na_values=['NaN'])
# Clean up data (drop columns that aren't varying with time)
many_glacier_df = many_glacier_df[['PRCP','TOBS','WESD']]
many_glacier_df
Out[3]:
PRCP | TOBS | WESD | |
---|---|---|---|
DATE | |||
1976-09-30 | NaN | NaN | 0.0 |
1976-10-01 | NaN | NaN | 0.0 |
1976-10-02 | NaN | NaN | 0.0 |
1976-10-03 | NaN | NaN | 0.0 |
1976-10-04 | NaN | NaN | 0.0 |
... | ... | ... | ... |
2024-04-21 | 0.0 | 37.0 | 4.2 |
2024-04-22 | 0.0 | 30.0 | 3.5 |
2024-04-23 | 0.0 | 43.0 | 2.5 |
2024-04-24 | 0.0 | 45.0 | 1.4 |
2024-04-25 | 0.0 | 36.0 | 1.0 |
17375 rows × 3 columns
In [4]:
# Plot WESD data using .plot()
many_glacier_df.plot(y='WESD')
Out[4]:
<Axes: xlabel='DATE'>
In [5]:
# Plot temperature data using .hvplot()
many_glacier_df.hvplot(y='TOBS')
Out[5]:
In [6]:
# Resample to look annual mean
many_glacier_annual_avg = many_glacier_df.resample('YE').mean()
many_glacier_annual_avg
Out[6]:
PRCP | TOBS | WESD | |
---|---|---|---|
DATE | |||
1976-12-31 | NaN | NaN | 1.236559 |
1977-12-31 | NaN | NaN | 3.251233 |
1978-12-31 | 0.073913 | 16.958333 | 6.226301 |
1979-12-31 | 0.100274 | 30.415473 | 5.050137 |
1980-12-31 | 0.157377 | 30.069364 | 5.042077 |
1981-12-31 | 0.110137 | 29.729008 | 3.637260 |
1982-12-31 | 0.150137 | 26.740741 | 6.632603 |
1983-12-31 | 0.108767 | 28.765258 | 4.653151 |
1984-12-31 | 0.138525 | 30.477912 | 3.904098 |
1985-12-31 | 0.132329 | 28.224658 | 5.284110 |
1986-12-31 | 0.150411 | 33.024658 | 2.811781 |
1987-12-31 | 0.106027 | 34.741047 | 3.114795 |
1988-12-31 | 0.125137 | 33.284153 | 2.660109 |
1989-12-31 | 0.171507 | 31.958217 | 4.486301 |
1990-12-31 | 0.186849 | 31.986301 | 4.917260 |
1991-12-31 | 0.140548 | 33.183562 | 7.052329 |
1992-12-31 | 0.117760 | 33.980822 | 2.566120 |
1993-12-31 | 0.106027 | 31.541547 | 3.400274 |
1994-12-31 | 0.127123 | 34.595989 | 4.601096 |
1995-12-31 | 0.184932 | 33.128767 | 2.691233 |
1996-12-31 | 0.156557 | 30.441096 | 4.346175 |
1997-12-31 | 0.133425 | 34.379121 | 7.852877 |
1998-12-31 | 0.127945 | 36.800000 | 3.651233 |
1999-12-31 | 0.138356 | 37.393939 | 5.363562 |
2000-12-31 | 0.100000 | 35.227397 | 3.880874 |
2001-12-31 | 0.098082 | 36.912329 | 3.098082 |
2002-12-31 | 0.152603 | 34.783562 | 5.455342 |
2003-12-31 | 0.110685 | 37.751381 | 2.910959 |
2004-12-31 | 0.129781 | 37.265027 | 2.619399 |
2005-12-31 | 0.122740 | 37.824658 | 1.066575 |
2006-12-31 | 0.142192 | 38.323288 | 2.756164 |
2007-12-31 | 0.113151 | 38.583562 | 2.555616 |
2008-12-31 | 0.118579 | 36.633880 | 4.435246 |
2009-12-31 | 0.116712 | 35.391781 | 3.484110 |
2010-12-31 | 0.120548 | 37.452055 | 2.923014 |
2011-12-31 | 0.149041 | 36.564384 | 6.660548 |
2012-12-31 | 0.163661 | 37.961749 | 4.326503 |
2013-12-31 | 0.119178 | 38.057534 | 3.069589 |
2014-12-31 | 0.144384 | 37.046575 | 4.480000 |
2015-12-31 | 0.118630 | 39.917808 | 1.161370 |
2016-12-31 | 0.134153 | 39.412568 | 1.610109 |
2017-12-31 | 0.129041 | 37.030137 | 3.955616 |
2018-12-31 | 0.100274 | 37.098630 | 5.406849 |
2019-12-31 | 0.112329 | 34.886427 | 2.983288 |
2020-12-31 | 0.125956 | 38.041436 | 4.150273 |
2021-12-31 | 0.116986 | 37.831025 | 3.002466 |
2022-12-31 | 0.120548 | 36.825485 | 4.346301 |
2023-12-31 | 0.103014 | 38.564384 | 3.028219 |
2024-12-31 | 0.132759 | 26.370690 | 4.487931 |
In [7]:
# Subset the data to look at 1980-2020
many_glacier_1980_2020 = many_glacier_annual_avg["1980":"2020"]
many_glacier_1980_2020
Out[7]:
PRCP | TOBS | WESD | |
---|---|---|---|
DATE | |||
1980-12-31 | 0.157377 | 30.069364 | 5.042077 |
1981-12-31 | 0.110137 | 29.729008 | 3.637260 |
1982-12-31 | 0.150137 | 26.740741 | 6.632603 |
1983-12-31 | 0.108767 | 28.765258 | 4.653151 |
1984-12-31 | 0.138525 | 30.477912 | 3.904098 |
1985-12-31 | 0.132329 | 28.224658 | 5.284110 |
1986-12-31 | 0.150411 | 33.024658 | 2.811781 |
1987-12-31 | 0.106027 | 34.741047 | 3.114795 |
1988-12-31 | 0.125137 | 33.284153 | 2.660109 |
1989-12-31 | 0.171507 | 31.958217 | 4.486301 |
1990-12-31 | 0.186849 | 31.986301 | 4.917260 |
1991-12-31 | 0.140548 | 33.183562 | 7.052329 |
1992-12-31 | 0.117760 | 33.980822 | 2.566120 |
1993-12-31 | 0.106027 | 31.541547 | 3.400274 |
1994-12-31 | 0.127123 | 34.595989 | 4.601096 |
1995-12-31 | 0.184932 | 33.128767 | 2.691233 |
1996-12-31 | 0.156557 | 30.441096 | 4.346175 |
1997-12-31 | 0.133425 | 34.379121 | 7.852877 |
1998-12-31 | 0.127945 | 36.800000 | 3.651233 |
1999-12-31 | 0.138356 | 37.393939 | 5.363562 |
2000-12-31 | 0.100000 | 35.227397 | 3.880874 |
2001-12-31 | 0.098082 | 36.912329 | 3.098082 |
2002-12-31 | 0.152603 | 34.783562 | 5.455342 |
2003-12-31 | 0.110685 | 37.751381 | 2.910959 |
2004-12-31 | 0.129781 | 37.265027 | 2.619399 |
2005-12-31 | 0.122740 | 37.824658 | 1.066575 |
2006-12-31 | 0.142192 | 38.323288 | 2.756164 |
2007-12-31 | 0.113151 | 38.583562 | 2.555616 |
2008-12-31 | 0.118579 | 36.633880 | 4.435246 |
2009-12-31 | 0.116712 | 35.391781 | 3.484110 |
2010-12-31 | 0.120548 | 37.452055 | 2.923014 |
2011-12-31 | 0.149041 | 36.564384 | 6.660548 |
2012-12-31 | 0.163661 | 37.961749 | 4.326503 |
2013-12-31 | 0.119178 | 38.057534 | 3.069589 |
2014-12-31 | 0.144384 | 37.046575 | 4.480000 |
2015-12-31 | 0.118630 | 39.917808 | 1.161370 |
2016-12-31 | 0.134153 | 39.412568 | 1.610109 |
2017-12-31 | 0.129041 | 37.030137 | 3.955616 |
2018-12-31 | 0.100274 | 37.098630 | 5.406849 |
2019-12-31 | 0.112329 | 34.886427 | 2.983288 |
2020-12-31 | 0.125956 | 38.041436 | 4.150273 |
In [8]:
# Resetting the index
many_glacier_1980_2020 = many_glacier_1980_2020.reset_index()
many_glacier_1980_2020
Out[8]:
DATE | PRCP | TOBS | WESD | |
---|---|---|---|---|
0 | 1980-12-31 | 0.157377 | 30.069364 | 5.042077 |
1 | 1981-12-31 | 0.110137 | 29.729008 | 3.637260 |
2 | 1982-12-31 | 0.150137 | 26.740741 | 6.632603 |
3 | 1983-12-31 | 0.108767 | 28.765258 | 4.653151 |
4 | 1984-12-31 | 0.138525 | 30.477912 | 3.904098 |
5 | 1985-12-31 | 0.132329 | 28.224658 | 5.284110 |
6 | 1986-12-31 | 0.150411 | 33.024658 | 2.811781 |
7 | 1987-12-31 | 0.106027 | 34.741047 | 3.114795 |
8 | 1988-12-31 | 0.125137 | 33.284153 | 2.660109 |
9 | 1989-12-31 | 0.171507 | 31.958217 | 4.486301 |
10 | 1990-12-31 | 0.186849 | 31.986301 | 4.917260 |
11 | 1991-12-31 | 0.140548 | 33.183562 | 7.052329 |
12 | 1992-12-31 | 0.117760 | 33.980822 | 2.566120 |
13 | 1993-12-31 | 0.106027 | 31.541547 | 3.400274 |
14 | 1994-12-31 | 0.127123 | 34.595989 | 4.601096 |
15 | 1995-12-31 | 0.184932 | 33.128767 | 2.691233 |
16 | 1996-12-31 | 0.156557 | 30.441096 | 4.346175 |
17 | 1997-12-31 | 0.133425 | 34.379121 | 7.852877 |
18 | 1998-12-31 | 0.127945 | 36.800000 | 3.651233 |
19 | 1999-12-31 | 0.138356 | 37.393939 | 5.363562 |
20 | 2000-12-31 | 0.100000 | 35.227397 | 3.880874 |
21 | 2001-12-31 | 0.098082 | 36.912329 | 3.098082 |
22 | 2002-12-31 | 0.152603 | 34.783562 | 5.455342 |
23 | 2003-12-31 | 0.110685 | 37.751381 | 2.910959 |
24 | 2004-12-31 | 0.129781 | 37.265027 | 2.619399 |
25 | 2005-12-31 | 0.122740 | 37.824658 | 1.066575 |
26 | 2006-12-31 | 0.142192 | 38.323288 | 2.756164 |
27 | 2007-12-31 | 0.113151 | 38.583562 | 2.555616 |
28 | 2008-12-31 | 0.118579 | 36.633880 | 4.435246 |
29 | 2009-12-31 | 0.116712 | 35.391781 | 3.484110 |
30 | 2010-12-31 | 0.120548 | 37.452055 | 2.923014 |
31 | 2011-12-31 | 0.149041 | 36.564384 | 6.660548 |
32 | 2012-12-31 | 0.163661 | 37.961749 | 4.326503 |
33 | 2013-12-31 | 0.119178 | 38.057534 | 3.069589 |
34 | 2014-12-31 | 0.144384 | 37.046575 | 4.480000 |
35 | 2015-12-31 | 0.118630 | 39.917808 | 1.161370 |
36 | 2016-12-31 | 0.134153 | 39.412568 | 1.610109 |
37 | 2017-12-31 | 0.129041 | 37.030137 | 3.955616 |
38 | 2018-12-31 | 0.100274 | 37.098630 | 5.406849 |
39 | 2019-12-31 | 0.112329 | 34.886427 | 2.983288 |
40 | 2020-12-31 | 0.125956 | 38.041436 | 4.150273 |
In [9]:
# Remove year from DATE column and add as new variable
many_glacier_1980_2020['YEAR'] = many_glacier_1980_2020['DATE'].dt.year
many_glacier_1980_2020
Out[9]:
DATE | PRCP | TOBS | WESD | YEAR | |
---|---|---|---|---|---|
0 | 1980-12-31 | 0.157377 | 30.069364 | 5.042077 | 1980 |
1 | 1981-12-31 | 0.110137 | 29.729008 | 3.637260 | 1981 |
2 | 1982-12-31 | 0.150137 | 26.740741 | 6.632603 | 1982 |
3 | 1983-12-31 | 0.108767 | 28.765258 | 4.653151 | 1983 |
4 | 1984-12-31 | 0.138525 | 30.477912 | 3.904098 | 1984 |
5 | 1985-12-31 | 0.132329 | 28.224658 | 5.284110 | 1985 |
6 | 1986-12-31 | 0.150411 | 33.024658 | 2.811781 | 1986 |
7 | 1987-12-31 | 0.106027 | 34.741047 | 3.114795 | 1987 |
8 | 1988-12-31 | 0.125137 | 33.284153 | 2.660109 | 1988 |
9 | 1989-12-31 | 0.171507 | 31.958217 | 4.486301 | 1989 |
10 | 1990-12-31 | 0.186849 | 31.986301 | 4.917260 | 1990 |
11 | 1991-12-31 | 0.140548 | 33.183562 | 7.052329 | 1991 |
12 | 1992-12-31 | 0.117760 | 33.980822 | 2.566120 | 1992 |
13 | 1993-12-31 | 0.106027 | 31.541547 | 3.400274 | 1993 |
14 | 1994-12-31 | 0.127123 | 34.595989 | 4.601096 | 1994 |
15 | 1995-12-31 | 0.184932 | 33.128767 | 2.691233 | 1995 |
16 | 1996-12-31 | 0.156557 | 30.441096 | 4.346175 | 1996 |
17 | 1997-12-31 | 0.133425 | 34.379121 | 7.852877 | 1997 |
18 | 1998-12-31 | 0.127945 | 36.800000 | 3.651233 | 1998 |
19 | 1999-12-31 | 0.138356 | 37.393939 | 5.363562 | 1999 |
20 | 2000-12-31 | 0.100000 | 35.227397 | 3.880874 | 2000 |
21 | 2001-12-31 | 0.098082 | 36.912329 | 3.098082 | 2001 |
22 | 2002-12-31 | 0.152603 | 34.783562 | 5.455342 | 2002 |
23 | 2003-12-31 | 0.110685 | 37.751381 | 2.910959 | 2003 |
24 | 2004-12-31 | 0.129781 | 37.265027 | 2.619399 | 2004 |
25 | 2005-12-31 | 0.122740 | 37.824658 | 1.066575 | 2005 |
26 | 2006-12-31 | 0.142192 | 38.323288 | 2.756164 | 2006 |
27 | 2007-12-31 | 0.113151 | 38.583562 | 2.555616 | 2007 |
28 | 2008-12-31 | 0.118579 | 36.633880 | 4.435246 | 2008 |
29 | 2009-12-31 | 0.116712 | 35.391781 | 3.484110 | 2009 |
30 | 2010-12-31 | 0.120548 | 37.452055 | 2.923014 | 2010 |
31 | 2011-12-31 | 0.149041 | 36.564384 | 6.660548 | 2011 |
32 | 2012-12-31 | 0.163661 | 37.961749 | 4.326503 | 2012 |
33 | 2013-12-31 | 0.119178 | 38.057534 | 3.069589 | 2013 |
34 | 2014-12-31 | 0.144384 | 37.046575 | 4.480000 | 2014 |
35 | 2015-12-31 | 0.118630 | 39.917808 | 1.161370 | 2015 |
36 | 2016-12-31 | 0.134153 | 39.412568 | 1.610109 | 2016 |
37 | 2017-12-31 | 0.129041 | 37.030137 | 3.955616 | 2017 |
38 | 2018-12-31 | 0.100274 | 37.098630 | 5.406849 | 2018 |
39 | 2019-12-31 | 0.112329 | 34.886427 | 2.983288 | 2019 |
40 | 2020-12-31 | 0.125956 | 38.041436 | 4.150273 | 2020 |
In [10]:
# Plot TOBS using .plot()
many_glacier_1980_2020.plot(y='TOBS',
x='YEAR')
Out[10]:
<Axes: xlabel='YEAR'>
In [38]:
# Plot PRCP using .hvplot()
many_glacier_1980_2020.hvplot(y='PRCP',
x='YEAR',
title='Change in Precipitation at Many Glacier, MT - 1980-2020')
Out[38]:
In [39]:
# Define our figure and axis objects
fig, ax = plt.subplots(figsize=(6,4))
# Plot TOBS vs. YEAR as scatter plot
# Plot scatter set
ax.scatter(many_glacier_1980_2020['YEAR'],
many_glacier_1980_2020['TOBS'],
color='white',
edgecolor='black')
# Plot line plot
ax.plot(many_glacier_1980_2020['YEAR'],
many_glacier_1980_2020['TOBS'],
color='grey')
# Add title and axis label
ax.set(title="Mean Annual Temperature\nMany Glacier, MT (1980-2020)",
ylabel="Temperature (F)")
Out[39]:
[Text(0.5, 1.0, 'Mean Annual Temperature\nMany Glacier, MT (1980-2020)'), Text(0, 0.5, 'Temperature (F)')]
In [40]:
# From ChatGPT
# Define our figure and axis objects
fig, ax = plt.subplots(figsize=(6,4))
# Compute linear regression
x = many_glacier_1980_2020['YEAR']
y = many_glacier_1980_2020['TOBS']
# Compute the slope (m) and intercept (b) of the line y = mx + b
m, b = np.polyfit(x, y, 1)
# Plot TOBS vs. YEAR as scatter plot
ax.scatter(x, y, color='white', edgecolor='black')
# Plot trend line
ax.plot(x, m*x + b, color='blue', label=f'Trend Line (R-squared = {np.corrcoef(x, y)[0,1]**2:.2f})')
# Add legend
ax.legend()
# Add title and axis label
ax.set(title="Mean Annual Temperature\nMany Glacier, MT (1980-2020)",
ylabel="Temperature (F)")
Out[40]:
[Text(0.5, 1.0, 'Mean Annual Temperature\nMany Glacier, MT (1980-2020)'), Text(0, 0.5, 'Temperature (F)')]
In [41]:
# Define our figure and axis objects
fig, ax = plt.subplots(figsize=(6,4))
# Compute linear regression
x = many_glacier_1980_2020['YEAR']
y = many_glacier_1980_2020['TOBS']
# Compute the slope (m) and intercept (b) of the line y = mx + b
m, b = np.polyfit(x, y, 1)
# Plot TOBS vs. YEAR as scatter plot
ax.scatter(x, y, color='white', edgecolor='black')
# Plot trend line
ax.plot(x, m*x + b, color='blue', label=f'Trend Line (Slope = {m:.2f})')
# Add legend
ax.legend()
# Add title and axis label
ax.set(title="Mean Annual Temperature\nMany Glacier, MT (1980-2020)",
ylabel="Temperature (F)")
Out[41]:
[Text(0.5, 1.0, 'Mean Annual Temperature\nMany Glacier, MT (1980-2020)'), Text(0, 0.5, 'Temperature (F)')]
In [42]:
# Define our figure and axis objects
fig, ax = plt.subplots(figsize=(6,4))
# Compute linear regression
x = many_glacier_1980_2020['YEAR']
y = many_glacier_1980_2020['TOBS']
# Compute the slope (m) and intercept (b) of the line y = mx + b
m, b = np.polyfit(x, y, 1)
# Define degree symbol
degree_symbol = '\u00B0'
# Plot TOBS vs. YEAR as scatter plot
ax.scatter(x, y, color='white', edgecolor='black')
# Plot line graph
ax.plot(many_glacier_1980_2020['YEAR'],
many_glacier_1980_2020['TOBS'],
color='grey')
# Plot trend line
ax.plot(x, m*x + b, color='tomato', label=f'Trend Line (Slope = {m:.2f} {degree_symbol}F/yr)')
# Add legend
ax.legend()
# Add title and axis label
ax.set(title="Mean Annual Temperature\nMany Glacier, MT (1980-2020)",
ylabel="Temperature (\u00B0F)")
plt.show()
In [11]:
# Define our figure and axis objects
fig, ax = plt.subplots(figsize=(6,4))
# Compute linear regression
x = many_glacier_1980_2020['YEAR']
y = many_glacier_1980_2020['WESD']
# Compute the slope (m) and intercept (b) of the line y = mx + b
m, b = np.polyfit(x, y, 1)
# Define degree symbol
degree_symbol = '\u00B0'
# Plot TOBS vs. YEAR as scatter plot
ax.scatter(x, y, color='white', edgecolor='black')
# Plot line graph
ax.plot(many_glacier_1980_2020['YEAR'],
many_glacier_1980_2020['WESD'],
color='grey')
# Plot trend line
ax.plot(x, m*x + b, color='tomato', label=f'Trend Line (Slope = {m:.2f} {degree_symbol}F/yr)')
# Add legend
ax.legend()
# Add title and axis label
ax.set(title="Mean Annual Snow Water Equivalence\nMany Glacier, MT (1980-2020)",
ylabel="Water Equivalence (in.)")
plt.show()
Temperatures at Many Glacier are increasing.¶
Over the last 40 years (1980-2020) the average annual temperature has increased at a rate of 0.23 deg Fahrenheit per year.
Adding a map of Glacier National Park using Open Street Maps and ChatGPT¶
In [43]:
# Define the place name (in this case, Glacier National Park)
place_name = "Glacier National Park"
# Download the boundary shapefile
gla_boundary = ox.geocode_to_gdf(place_name)
gla_boundary
Out[43]:
geometry | bbox_north | bbox_south | bbox_east | bbox_west | place_id | osm_type | osm_id | lat | lon | class | type | place_rank | importance | addresstype | name | display_name | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | POLYGON ((-114.47550 49.00109, -114.47548 49.0... | 49.001109 | 48.233699 | -113.241617 | -114.475501 | 31377934 | relation | 1242641 | 48.617486 | -113.760885 | boundary | national_park | 25 | 0.514759 | national_park | Glacier National Park | Glacier National Park, Montana, 59916, United ... |
In [44]:
# Map using .explore()
gla_boundary.explore()
Out[44]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [45]:
# Plot Glacier National Park boundary
gla_map = gla_boundary.hvplot(
# Give the map a descriptive title
title="Glacier National Park, Montana, USA",
# Add a basemap
geo=True, tiles='EsriImagery',
# Change the colors
fill_color='white', fill_alpha=0.2,
line_color='skyblue', line_width=5,
# Change the image size
frame_width=400, frame_height=400)
# Save the map as a file to put on the web
hv.save(gla_map, 'glacier-national-park.html')
# Display the map
gla_map
WARNING:bokeh.core.validation.check:W-1005 (FIXED_SIZING_MODE): 'fixed' sizing mode requires width and height to be set: figure(id='p1494', ...)
Out[45]:
Create shareable HTML of the notebook¶
In [46]:
%%capture
%%bash
jupyter nbconvert many-glacier.ipynb --to html