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)¶
Show code cell source
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=51, 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)
Show code cell source
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¶
Show code cell source
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()

Show code cell source
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()

Show code cell source
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()

Show code cell source
# reorder for plotting purposes
col_order = ['Nuclear', 'Hydroelectric', 'Wind', 'Solar',
'Coal', 'Natural Gas', 'Petroleum']
fractions = fractions[col_order]
fractions.iloc[-12*10:].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()

Show code cell source
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()

Show code cell source
fractions.assign(clean_fraction=clean_fraction).tail(24)
source | Nuclear | Hydroelectric | Wind | Solar | Coal | Natural Gas | Petroleum | clean_fraction |
---|---|---|---|---|---|---|---|---|
date | ||||||||
2020-12-01 | 21.400579 | 6.557329 | 9.764229 | 1.549242 | 23.921286 | 36.317728 | 0.489607 | 39.271379 |
2021-01-01 | 21.652945 | 7.380077 | 9.067195 | 1.667184 | 24.379383 | 35.385158 | 0.468057 | 39.767401 |
2021-02-01 | 20.372133 | 6.489166 | 8.637934 | 2.036516 | 28.162106 | 33.607846 | 0.694300 | 37.535749 |
2021-03-01 | 21.575713 | 7.144087 | 13.266523 | 3.126851 | 20.809850 | 33.617503 | 0.459473 | 45.113174 |
2021-04-01 | 20.547507 | 6.938258 | 13.003650 | 3.893373 | 19.268645 | 35.932516 | 0.416051 | 44.382788 |
2021-05-01 | 20.845940 | 7.629358 | 11.102723 | 4.069981 | 20.853299 | 35.073657 | 0.425043 | 43.648002 |
2021-06-01 | 18.512837 | 6.548268 | 7.467721 | 3.395785 | 24.317522 | 39.386703 | 0.371163 | 35.924611 |
2021-07-01 | 17.748344 | 5.672360 | 5.595788 | 3.123535 | 26.057947 | 41.415359 | 0.386667 | 32.140028 |
2021-08-01 | 17.581077 | 5.121396 | 6.846633 | 3.009114 | 25.656907 | 41.309265 | 0.475608 | 32.558220 |
2021-09-01 | 19.473236 | 5.109244 | 8.745258 | 3.363585 | 23.658764 | 39.187692 | 0.462221 | 36.691323 |
2021-10-01 | 18.836057 | 5.636242 | 10.648276 | 3.046742 | 20.549359 | 40.793280 | 0.490045 | 38.167316 |
2021-11-01 | 21.078726 | 6.473890 | 12.000150 | 2.602087 | 19.089695 | 38.217652 | 0.537800 | 42.154853 |
2021-12-01 | 22.120823 | 7.341041 | 12.455574 | 1.893748 | 18.631823 | 37.090194 | 0.466797 | 43.811186 |
2022-01-01 | 19.627165 | 7.279458 | 10.583065 | 2.252881 | 24.190437 | 35.107484 | 0.959510 | 39.742569 |
2022-02-01 | 19.877272 | 7.350111 | 12.200232 | 2.972134 | 22.589292 | 34.507037 | 0.503922 | 42.399749 |
2022-03-01 | 20.580642 | 8.261454 | 14.007771 | 3.841555 | 19.631201 | 33.677377 | NaN | 46.691421 |
2022-04-01 | 19.211334 | 6.762649 | 15.948068 | 4.638046 | 18.967643 | 34.061900 | 0.410360 | 46.560097 |
2022-05-01 | 19.456704 | 7.056149 | 12.775585 | 4.624140 | 18.947615 | 36.693213 | 0.446595 | 43.912577 |
2022-06-01 | 18.048510 | 7.354000 | 9.188879 | 4.352820 | 19.997726 | 40.643408 | 0.414657 | 38.944209 |
2022-07-01 | 16.956261 | 5.912921 | 7.203022 | 3.837776 | 21.114304 | 44.627601 | 0.348115 | 33.909980 |
2022-08-01 | 17.442244 | 5.447476 | 6.159429 | 3.615273 | 21.386342 | 45.565218 | 0.384018 | 32.664422 |
2022-09-01 | 19.028847 | 4.999918 | 8.063690 | 3.974746 | 19.215507 | 44.254758 | 0.462534 | 36.067202 |
2022-10-01 | 19.768851 | 4.884765 | 11.001220 | 4.058524 | 17.982895 | 41.801571 | 0.502174 | 39.713361 |
2022-11-01 | 20.232302 | 6.093371 | 13.677540 | 2.740820 | 18.181965 | 38.587374 | 0.486628 | 42.744033 |
Show code cell source
rolling = df2.rolling(12).sum()
rolling['Renewables'] = rolling[['Solar', 'Wind', 'Hydroelectric']].sum(axis=1)
rolling.loc['2015':, ['Coal', 'Nuclear', 'Renewables']].plot()
plt.ylabel('Million kWh (12-month moving)');

This page was last regenerated on:
Show code cell source
import datetime
datetime.date.today().strftime('%Y-%m-%d')
'2023-03-05'