In this blog post we’ll explore visualizing the Yield Curve through a cool 3D Surface Plot, since we’ll be exploring 3 dimensions of data: the rate yields, the publication date, and the rate periods.
Don’t wait, download now and transform your career!Your FREE Guide to Become a Data Scientist
Grabbing Yield Curve for 2022¶
We can use the pandas library to directly read the HTML table from the treasury website:
import pandas as pd
year = 2022
url = f"https://home.treasury.gov/resource-center/data-chart-center/interest-rates/TextView?type=daily_treasury_yield_curve&field_tdr_date_value={year}"
table_list = pd.read_html(url)
df = table_list[0]
df.head()
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
Date | 20 YR | 30 YR | Extrapolation Factor | 8 WEEKS BANK DISCOUNT | COUPON EQUIVALENT | 52 WEEKS BANK DISCOUNT | COUPON EQUIVALENT.1 | 1 Mo | 2 Mo | 3 Mo | 6 Mo | 1 Yr | 2 Yr | 3 Yr | 5 Yr | 7 Yr | 10 Yr | 20 Yr | 30 Yr | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 01/03/2022 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.05 | 0.06 | 0.08 | 0.22 | 0.40 | 0.78 | 1.04 | 1.37 | 1.55 | 1.63 | 2.05 | 2.01 |
1 | 01/04/2022 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.06 | 0.05 | 0.08 | 0.22 | 0.38 | 0.77 | 1.02 | 1.37 | 1.57 | 1.66 | 2.10 | 2.07 |
2 | 01/05/2022 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.05 | 0.06 | 0.09 | 0.22 | 0.41 | 0.83 | 1.10 | 1.43 | 1.62 | 1.71 | 2.12 | 2.09 |
3 | 01/06/2022 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.04 | 0.05 | 0.10 | 0.23 | 0.45 | 0.88 | 1.15 | 1.47 | 1.66 | 1.73 | 2.12 | 2.09 |
4 | 01/07/2022 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.05 | 0.05 | 0.10 | 0.24 | 0.43 | 0.87 | 1.17 | 1.50 | 1.69 | 1.76 | 2.15 | 2.11 |
Cleaning the Data¶
We can see that there is a bit of unwanted data and columns in the dataframe above. Let’s clean it up and create a function to clean up future dataframes from the website:
df.columns
Index(['Date', '20 YR', '30 YR', 'Extrapolation Factor', '8 WEEKS BANK DISCOUNT', 'COUPON EQUIVALENT', '52 WEEKS BANK DISCOUNT', 'COUPON EQUIVALENT.1', '1 Mo', '2 Mo', '3 Mo', '6 Mo', '1 Yr', '2 Yr', '3 Yr', '5 Yr', '7 Yr', '10 Yr', '20 Yr', '30 Yr'], dtype='object')
df['Date'] = pd.to_datetime(df['Date'])
df = df.drop(['20 YR', '30 YR', 'Extrapolation Factor',
'8 WEEKS BANK DISCOUNT', 'COUPON EQUIVALENT', '52 WEEKS BANK DISCOUNT',
'COUPON EQUIVALENT.1'],axis=1).set_index('Date')
df
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
1 Mo | 2 Mo | 3 Mo | 6 Mo | 1 Yr | 2 Yr | 3 Yr | 5 Yr | 7 Yr | 10 Yr | 20 Yr | 30 Yr | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Date | ||||||||||||
2022-01-03 | 0.05 | 0.06 | 0.08 | 0.22 | 0.40 | 0.78 | 1.04 | 1.37 | 1.55 | 1.63 | 2.05 | 2.01 |
2022-01-04 | 0.06 | 0.05 | 0.08 | 0.22 | 0.38 | 0.77 | 1.02 | 1.37 | 1.57 | 1.66 | 2.10 | 2.07 |
2022-01-05 | 0.05 | 0.06 | 0.09 | 0.22 | 0.41 | 0.83 | 1.10 | 1.43 | 1.62 | 1.71 | 2.12 | 2.09 |
2022-01-06 | 0.04 | 0.05 | 0.10 | 0.23 | 0.45 | 0.88 | 1.15 | 1.47 | 1.66 | 1.73 | 2.12 | 2.09 |
2022-01-07 | 0.05 | 0.05 | 0.10 | 0.24 | 0.43 | 0.87 | 1.17 | 1.50 | 1.69 | 1.76 | 2.15 | 2.11 |
… | … | … | … | … | … | … | … | … | … | … | … | … |
2022-06-23 | 1.12 | 1.54 | 1.65 | 2.44 | 2.78 | 3.01 | 3.12 | 3.14 | 3.16 | 3.09 | 3.45 | 3.21 |
2022-06-24 | 1.19 | 1.60 | 1.73 | 2.51 | 2.83 | 3.04 | 3.13 | 3.18 | 3.19 | 3.13 | 3.51 | 3.26 |
2022-06-27 | 1.16 | 1.57 | 1.79 | 2.56 | 2.89 | 3.08 | 3.21 | 3.24 | 3.27 | 3.20 | 3.56 | 3.31 |
2022-06-28 | 1.12 | 1.59 | 1.79 | 2.55 | 2.88 | 3.10 | 3.21 | 3.25 | 3.27 | 3.20 | 3.55 | 3.30 |
2022-06-29 | 1.12 | 1.53 | 1.78 | 2.55 | 2.88 | 3.06 | 3.13 | 3.15 | 3.17 | 3.10 | 3.46 | 3.22 |
123 rows × 12 columns
Function for Cleaning¶
Now that we have the cleaning steps we can easily create a function for this as well as resetting the dataframe index to be the Date column:
def clean_df(df):
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index('Date')
df = df.drop(['20 YR', '30 YR', 'Extrapolation Factor',
'8 WEEKS BANK DISCOUNT', 'COUPON EQUIVALENT', '52 WEEKS BANK DISCOUNT',
'COUPON EQUIVALENT.1','2 Mo'],axis=1)
return df
df = clean_df(df)
df
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
1 Mo | 3 Mo | 6 Mo | 1 Yr | 2 Yr | 3 Yr | 5 Yr | 7 Yr | 10 Yr | 20 Yr | 30 Yr | |
---|---|---|---|---|---|---|---|---|---|---|---|
Date | |||||||||||
2022-01-03 | 0.05 | 0.08 | 0.22 | 0.40 | 0.78 | 1.04 | 1.37 | 1.55 | 1.63 | 2.05 | 2.01 |
2022-01-04 | 0.06 | 0.08 | 0.22 | 0.38 | 0.77 | 1.02 | 1.37 | 1.57 | 1.66 | 2.10 | 2.07 |
2022-01-05 | 0.05 | 0.09 | 0.22 | 0.41 | 0.83 | 1.10 | 1.43 | 1.62 | 1.71 | 2.12 | 2.09 |
2022-01-06 | 0.04 | 0.10 | 0.23 | 0.45 | 0.88 | 1.15 | 1.47 | 1.66 | 1.73 | 2.12 | 2.09 |
2022-01-07 | 0.05 | 0.10 | 0.24 | 0.43 | 0.87 | 1.17 | 1.50 | 1.69 | 1.76 | 2.15 | 2.11 |
… | … | … | … | … | … | … | … | … | … | … | … |
2022-06-23 | 1.12 | 1.65 | 2.44 | 2.78 | 3.01 | 3.12 | 3.14 | 3.16 | 3.09 | 3.45 | 3.21 |
2022-06-24 | 1.19 | 1.73 | 2.51 | 2.83 | 3.04 | 3.13 | 3.18 | 3.19 | 3.13 | 3.51 | 3.26 |
2022-06-27 | 1.16 | 1.79 | 2.56 | 2.89 | 3.08 | 3.21 | 3.24 | 3.27 | 3.20 | 3.56 | 3.31 |
2022-06-28 | 1.12 | 1.79 | 2.55 | 2.88 | 3.10 | 3.21 | 3.25 | 3.27 | 3.20 | 3.55 | 3.30 |
2022-06-29 | 1.12 | 1.78 | 2.55 | 2.88 | 3.06 | 3.13 | 3.15 | 3.17 | 3.10 | 3.46 | 3.22 |
123 rows × 11 columns
Visualizing a Heat Map of Data¶
For a single year, we can visualize this in the form of a heatmap, which is essentially a flattened surface plot, where the Z value in this case is the actual interest yield on the treasury bill, denoted by the color:
import seaborn as sns
sns.heatmap(df)
<AxesSubplot:ylabel='Date'>
Grabbing Data for Multiple Years¶
Our heatmap from above looks good. Let’s loop through a few years to grab and clean dataframes from the treasury website to get a lot of data to concatenate together:
for year in range(2010,2021):
url = f"https://home.treasury.gov/resource-center/data-chart-center/interest-rates/TextView?type=daily_treasury_yield_curve&field_tdr_date_value={year}"
table_list = pd.read_html(url)
df = pd.concat([df,clean_df(table_list[0])])
df
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
1 Mo | 2 Mo | 3 Mo | 6 Mo | 1 Yr | 2 Yr | 3 Yr | 5 Yr | 7 Yr | 10 Yr | 20 Yr | 30 Yr | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Date | ||||||||||||
2022-01-03 | 0.05 | 0.06 | 0.08 | 0.22 | 0.40 | 0.78 | 1.04 | 1.37 | 1.55 | 1.63 | 2.05 | 2.01 |
2022-01-04 | 0.06 | 0.05 | 0.08 | 0.22 | 0.38 | 0.77 | 1.02 | 1.37 | 1.57 | 1.66 | 2.10 | 2.07 |
2022-01-05 | 0.05 | 0.06 | 0.09 | 0.22 | 0.41 | 0.83 | 1.10 | 1.43 | 1.62 | 1.71 | 2.12 | 2.09 |
2022-01-06 | 0.04 | 0.05 | 0.10 | 0.23 | 0.45 | 0.88 | 1.15 | 1.47 | 1.66 | 1.73 | 2.12 | 2.09 |
2022-01-07 | 0.05 | 0.05 | 0.10 | 0.24 | 0.43 | 0.87 | 1.17 | 1.50 | 1.69 | 1.76 | 2.15 | 2.11 |
… | … | … | … | … | … | … | … | … | … | … | … | … |
2020-12-24 | 0.09 | NaN | 0.09 | 0.09 | 0.10 | 0.13 | 0.17 | 0.37 | 0.66 | 0.94 | 1.46 | 1.66 |
2020-12-28 | 0.09 | NaN | 0.11 | 0.11 | 0.11 | 0.13 | 0.17 | 0.38 | 0.65 | 0.94 | 1.46 | 1.67 |
2020-12-29 | 0.08 | NaN | 0.10 | 0.12 | 0.11 | 0.12 | 0.17 | 0.37 | 0.66 | 0.94 | 1.47 | 1.67 |
2020-12-30 | 0.06 | NaN | 0.08 | 0.09 | 0.12 | 0.12 | 0.17 | 0.37 | 0.66 | 0.93 | 1.46 | 1.66 |
2020-12-31 | 0.08 | NaN | 0.09 | 0.09 | 0.10 | 0.13 | 0.17 | 0.36 | 0.65 | 0.93 | 1.45 | 1.65 |
2876 rows × 12 columns
df = df.sort_index()
df
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
1 Mo | 2 Mo | 3 Mo | 6 Mo | 1 Yr | 2 Yr | 3 Yr | 5 Yr | 7 Yr | 10 Yr | 20 Yr | 30 Yr | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Date | ||||||||||||
2010-01-04 | 0.05 | NaN | 0.08 | 0.18 | 0.45 | 1.09 | 1.66 | 2.65 | 3.36 | 3.85 | 4.60 | 4.65 |
2010-01-05 | 0.03 | NaN | 0.07 | 0.17 | 0.41 | 1.01 | 1.57 | 2.56 | 3.28 | 3.77 | 4.54 | 4.59 |
2010-01-06 | 0.03 | NaN | 0.06 | 0.15 | 0.40 | 1.01 | 1.60 | 2.60 | 3.33 | 3.85 | 4.63 | 4.70 |
2010-01-07 | 0.02 | NaN | 0.05 | 0.16 | 0.40 | 1.03 | 1.62 | 2.62 | 3.33 | 3.85 | 4.62 | 4.69 |
2010-01-08 | 0.02 | NaN | 0.05 | 0.15 | 0.37 | 0.96 | 1.56 | 2.57 | 3.31 | 3.83 | 4.61 | 4.70 |
… | … | … | … | … | … | … | … | … | … | … | … | … |
2022-06-23 | 1.12 | 1.54 | 1.65 | 2.44 | 2.78 | 3.01 | 3.12 | 3.14 | 3.16 | 3.09 | 3.45 | 3.21 |
2022-06-24 | 1.19 | 1.60 | 1.73 | 2.51 | 2.83 | 3.04 | 3.13 | 3.18 | 3.19 | 3.13 | 3.51 | 3.26 |
2022-06-27 | 1.16 | 1.57 | 1.79 | 2.56 | 2.89 | 3.08 | 3.21 | 3.24 | 3.27 | 3.20 | 3.56 | 3.31 |
2022-06-28 | 1.12 | 1.59 | 1.79 | 2.55 | 2.88 | 3.10 | 3.21 | 3.25 | 3.27 | 3.20 | 3.55 | 3.30 |
2022-06-29 | 1.12 | 1.53 | 1.78 | 2.55 | 2.88 | 3.06 | 3.13 | 3.15 | 3.17 | 3.10 | 3.46 | 3.22 |
2876 rows × 12 columns
Looks like we still have the 2 month, which was not available in earlier years, let’s remove it:
df = df.drop('2 Mo',axis=1)
df.head()
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
1 Mo | 3 Mo | 6 Mo | 1 Yr | 2 Yr | 3 Yr | 5 Yr | 7 Yr | 10 Yr | 20 Yr | 30 Yr | |
---|---|---|---|---|---|---|---|---|---|---|---|
Date | |||||||||||
2010-01-04 | 0.05 | 0.08 | 0.18 | 0.45 | 1.09 | 1.66 | 2.65 | 3.36 | 3.85 | 4.60 | 4.65 |
2010-01-05 | 0.03 | 0.07 | 0.17 | 0.41 | 1.01 | 1.57 | 2.56 | 3.28 | 3.77 | 4.54 | 4.59 |
2010-01-06 | 0.03 | 0.06 | 0.15 | 0.40 | 1.01 | 1.60 | 2.60 | 3.33 | 3.85 | 4.63 | 4.70 |
2010-01-07 | 0.02 | 0.05 | 0.16 | 0.40 | 1.03 | 1.62 | 2.62 | 3.33 | 3.85 | 4.62 | 4.69 |
2010-01-08 | 0.02 | 0.05 | 0.15 | 0.37 | 0.96 | 1.56 | 2.57 | 3.31 | 3.83 | 4.61 | 4.70 |
Creating a 3D Surface Plot¶
Now we can use Plotly to easily create a 3D surface plot to visualize the timeline of the curves and how they have changed over time. Pay close attention to the short term yields dropping to zero at certain times!
import plotly.graph_objects as go
import pandas as pd
import numpy as np
x = df.columns
y = df.index
z = df.to_numpy()
fig = go.Figure(data=[go.Surface(z=z, x=x, y=y)])
fig.update_layout(title='Yield Curves',
scene = {"aspectratio": {"x": 1, "y": 1, "z": 0.4}})
fig.show()