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)

Hide 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)
Hide 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()
_images/c65d297cfe8de682f39e4cf3c83ff5b411e71f64462e5245034d54036e01999f.png

EIA Monthly: Electric Power Sector

Hide 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()
_images/c9a60c0664a96e18f8cac72d935564fcf2842232824f4120b6f655702fbfbc3f.png
Hide 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()
_images/1bb16c197b7a4801836b8bc26df0c46eb15456f426ea0001977590e46f83b393.png
Hide 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()
_images/36c996847775050f991f693a30fc5d32737ff640dd54616da775b5095d7bbec4.png
Hide 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()
_images/60573b9599e691c9a40ce23bd4e6a21df42a3b2e3393fa2b9e88aed8072878d5.png
Hide 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()
_images/ad3077a7f12b7a898d8c662e86ffb315a7ddcbb3f7b663e3b6f4d2012e78855f.png
Hide 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
Hide 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)');
_images/96237a3f553a12e70bee476a32ad9b18a7ca2039bfb1847112470f8af20f60c6.png

This page was last regenerated on:

Hide code cell source
import datetime
datetime.date.today().strftime('%Y-%m-%d')
'2023-03-05'