Climate Report¶
Additional time series data about climate change come in all the time. Here are some plots that I like to track.
I think that what is essential for this problem is a global consciousness,
a view that transcends our exclusive identifications with the generational
and political groupings into which by accident we have been born.
The solution to these problems requires a perspective that embraces the
planet and the future, because we are all in this greenhouse together.
Carl Sagan, https://youtu.be/Wp-WiNXH6hI?t=985
Keeling Curve (Mauna Loa CO2)¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import rdtools
plt.rcParams['figure.dpi'] = 100
url = 'https://gml.noaa.gov/webdata/ccgg/trends/co2/co2_weekly_mlo.csv'
df = pd.read_csv(url, skiprows=47, na_values=-999.99)
df.index = pd.to_datetime(df[['year', 'month', 'day']])
ppm_weekly = df['average']
ppm_daily = ppm_weekly.resample('d').interpolate()
_, _, calc_info = rdtools.degradation_year_on_year(ppm_daily)
yoy_changes = calc_info['YoY_values']
moving_avg = ppm_daily.rolling(365, center=True).mean()
ppm_deseasonalized = ppm_daily - moving_avg
grouper = ppm_deseasonalized.groupby(ppm_deseasonalized.index.dayofyear)
median_seasonality = grouper.median()
upper_seasonality = grouper.quantile(0.95)
lower_seasonality = grouper.quantile(0.05)
/home/kevin/anaconda3/lib/python3.7/site-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
import pandas.util.testing as tm
fig, axes = plt.subplots(2, 1, sharex=True, figsize=(6, 6))
# plot this first because it has a shorter index
yoy_changes.plot(ax=axes[1])
yoy_changes.rolling(52, center=True).median().plot(ax=axes[1])
axes[1].set_xlabel(None)
axes[1].set_ylabel('Year-on-year change [%/yr]')
axes[1].grid()
moving_avg.plot(ax=axes[0])
ppm_daily.plot(ax=axes[0])
axes[0].set_ylabel('Dry CO$_2$ Fraction [ppm]')
axes[0].grid()
fig.tight_layout()

EIA Monthly: Electric Power Sector¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
url = 'https://www.eia.gov/totalenergy/data/browser/csv.php?tbl=T07.02B'
df = pd.read_csv(url)
df = df.loc[~df['Description'].str.contains('Generation Total')]
df = df.loc[~df['YYYYMM'].astype(str).str.endswith('13')]
df['source'] = df['Description'].str.split(",").str[0].str.replace('Electricity Net Generation From ', '')
df['date'] = pd.to_datetime(df['YYYYMM'], format='%Y%m')
df2 = df.pivot(index='date', columns='source', values='Value')
df2 = df2.rename(columns={'Conventional Hydroelectric Power': 'Hydroelectric',
'Nuclear Electric Power': 'Nuclear'})
df2 = df2.replace('Not Available', np.nan).replace('Not Meaningful', np.nan).astype(float)
df2 = df2.loc[:, df2.max() > 5000] # only keep the main players
fractions = 100 * df2.divide(df2.sum(axis=1), axis=0).clip(lower=0)
fig, ax = plt.subplots(figsize=(10, 6))
df2.rolling(12, center=True).mean().plot(ax=ax, colormap='tab10')
ax.legend()
ax.set_ylabel('Million kWh')
fig.tight_layout()
df2.plot(ax=ax, alpha=0.2, colormap='tab10', legend=False)
plt.show()

fig, axes = plt.subplots(3, 1, sharex=True, figsize=(6, 7))
fig.suptitle('Utility-Scale Solar Monthly Generation')
df2['Solar'].dropna().loc['2009':].plot(ax=axes[0])
axes[0].set_ylabel('Million kWh')
fractions['Solar'].loc['2009':].plot(ax=axes[1])
moving_average = fractions['Solar'].rolling(12).mean()
moving_average.loc['2009':].plot(ax=axes[1])
axes[1].set_ylabel('Fraction of total [%]')
yoy_change = moving_average - moving_average.shift(12)
yoy_change.loc['2009':].plot(ax=axes[2])
axes[2].set_ylabel('Year-on-year change [% of total]')
for ax in axes:
ax.axvline('2017-02-01', ls='--', c='k')
ax.grid(which='both')
fig.tight_layout()

fig, axes = plt.subplots(3, 1, sharex=True, figsize=(6, 7))
fig.suptitle('Utility-Scale Wind Monthly Generation')
df2['Wind'].dropna().loc['2000':].plot(ax=axes[0])
axes[0].set_ylabel('Million kWh')
fractions['Wind'].loc['2000':].plot(ax=axes[1])
moving_average = fractions['Wind'].rolling(12).mean()
moving_average.loc['2000':].plot(ax=axes[1])
axes[1].set_ylabel('Fraction of total [%]')
yoy_change = moving_average - moving_average.shift(12)
yoy_change.loc['2000':].plot(ax=axes[2])
axes[2].set_ylabel('Year-on-year change [% of total]')
for ax in axes:
ax.grid(which='both')
fig.tight_layout()

# reorder for plotting purposes
col_order = ['Nuclear', 'Hydroelectric', 'Wind', 'Solar',
'Coal', 'Natural Gas', 'Petroleum']
fractions = fractions[col_order]
fractions.loc['2010-01':].plot.area(lw=0, figsize=(10, 4))
plt.ylabel('Share of total generation [%]')
handles, labels = plt.gca().get_legend_handles_labels()
plt.legend(reversed(handles), reversed(labels),
loc='center right', bbox_to_anchor=(1.2, 0.5))
plt.tight_layout()
plt.grid()

clean = ['Solar', 'Wind', 'Hydroelectric', 'Nuclear']
clean_fraction = 100 * df2[clean].sum(axis=1) / df2.sum(axis=1)
clean_fraction.plot()
clean_fraction_rolling = 100 * (
df2[clean].sum(axis=1).rolling(12, center=True).sum() /
df2.sum(axis=1).rolling(12, center=True).sum()
)
clean_fraction_rolling.plot()
plt.ylabel('Clean Fraction [%]')
plt.tight_layout()
plt.grid()

fractions.assign(clean_fraction=clean_fraction).tail(24)
source | Nuclear | Hydroelectric | Wind | Solar | Coal | Natural Gas | Petroleum | clean_fraction |
---|---|---|---|---|---|---|---|---|
date | ||||||||
2020-04-01 | 22.447127 | 8.750506 | 11.277171 | 2.981203 | 15.263376 | 38.840032 | 0.440585 | 45.456007 |
2020-05-01 | 22.312202 | 10.352296 | 9.833078 | 3.320763 | 15.983966 | 37.772547 | 0.425149 | 45.818339 |
2020-06-01 | 20.033423 | 8.318162 | 8.983890 | 2.854431 | 19.335395 | 40.016047 | 0.458653 | 40.189906 |
2020-07-01 | 17.689461 | 6.796157 | 5.809551 | 2.683980 | 22.752788 | 43.843092 | 0.424971 | 32.979149 |
2020-08-01 | 18.117107 | 6.094032 | 6.030627 | 2.428277 | 23.819808 | 43.091396 | 0.418754 | 32.670042 |
2020-09-01 | 20.738172 | 5.871980 | 7.289020 | 2.420934 | 21.431536 | 41.896269 | 0.352089 | 36.320106 |
2020-10-01 | 19.958934 | 6.301916 | 9.655205 | 2.365077 | 19.950981 | 41.385050 | 0.382838 | 38.281132 |
2020-11-01 | 21.682672 | 7.306161 | 11.589323 | 2.009788 | 21.327548 | 35.619953 | 0.464556 | 42.587944 |
2020-12-01 | 21.412255 | 6.560907 | 9.769556 | 1.550087 | 23.934337 | 36.282983 | 0.489874 | 39.292806 |
2021-01-01 | 21.568254 | 7.726720 | 9.123949 | 1.708809 | 24.358307 | 35.057843 | 0.456117 | 40.127733 |
2021-02-01 | 20.232108 | 6.918272 | 8.599784 | 2.047226 | 28.088203 | 33.377148 | 0.737259 | 37.797390 |
2021-03-01 | 21.524656 | 7.253476 | 13.464893 | 3.109543 | 20.804169 | 33.388248 | 0.455015 | 45.352568 |
2021-04-01 | 20.613114 | 6.896434 | 13.027281 | 3.881814 | 19.333900 | 35.859000 | 0.388457 | 44.418643 |
2021-05-01 | 20.953200 | 7.500017 | 11.065173 | 4.034643 | 20.960580 | 35.080056 | 0.406332 | 43.553032 |
2021-06-01 | 18.522126 | 6.721281 | 7.438509 | 3.297800 | 24.347450 | 39.326462 | 0.346372 | 35.979716 |
2021-07-01 | 17.781657 | 5.687010 | 5.549288 | 3.056849 | 26.115728 | 41.439960 | 0.369507 | 32.074804 |
2021-08-01 | 17.557164 | 5.270914 | 6.747998 | 2.965353 | 25.629573 | 41.366647 | 0.462351 | 32.541429 |
2021-09-01 | 19.442799 | 5.389966 | 8.625739 | 3.325565 | 23.629964 | 39.140676 | 0.445292 | 36.784069 |
2021-10-01 | 18.888144 | 5.939761 | 10.723468 | 3.043927 | 20.607307 | 40.797393 | NaN | 38.595299 |
2021-11-01 | 21.006165 | 6.816418 | 12.023371 | 2.615439 | 18.956266 | 38.065861 | 0.516480 | 42.461393 |
2021-12-01 | 21.970067 | 7.933967 | 12.594438 | 1.959319 | 18.445046 | 36.661973 | 0.435191 | 44.457791 |
2022-01-01 | 19.540750 | 7.449320 | 10.566333 | 2.201032 | 24.084032 | 35.139284 | 1.019249 | 39.757435 |
2022-02-01 | 19.826930 | 7.554435 | 12.221207 | 2.929947 | 22.529049 | 34.451338 | 0.487094 | 42.532520 |
2022-03-01 | 20.422092 | 8.416541 | 13.968529 | 3.819068 | 19.483063 | 33.452772 | 0.437936 | 46.626230 |
This page was last regenerated on:
import datetime
datetime.date.today().strftime('%Y-%m-%d')
'2022-07-04'