library(tidyverse)
library(tidyfinance)
library(wesanderson)
start_date <- ymd("1962-01-01")
end_date <- ymd("2021-12-31")
factors_ff3_daily <- download_data(
domain = "Fama-French",
dataset = "Fama/French 3 Factors [Daily]",
start_date = start_date,
end_date = end_date
)
data_plot <- factors_ff3_daily |>
select(date, mkt_excess) |>
group_by(year = floor_date(date, "year")) |>
mutate(group_id = cur_group_id())
data_plot <- data_plot |>
group_by(group_id) |>
mutate(
day = 2 * pi * (1:n()) / 252,
ymin = pmin(1 + mkt_excess, 1),
ymax = pmax(1 + mkt_excess, 1),
vola = sd(mkt_excess)
) |>
filter(year >= "1962-01-01" & year <= "2021-12-31")
levels <- data_plot |>
distinct(group_id, vola) |>
arrange(vola) |>
pull(vola)
cp <- coord_polar(
direction = -1,
clip = "on"
)
cp$is_free <- function() TRUE
colors <- wes_palette("Zissou1", n_groups(data_plot), type = "continuous")
cover <- data_plot |>
mutate(vola = factor(vola, levels = levels)) |>
ggplot(aes(
x = day,
y = mkt_excess,
group = group_id,
fill = vola
)) +
cp +
geom_ribbon(
aes(
ymin = ymin,
ymax = ymax,
fill = vola
),
alpha = 0.90
) +
theme_void() +
facet_wrap(~group_id, ncol = 10, scales = "free") +
theme(
strip.text.x = element_blank(),
legend.position = "None",
panel.spacing = unit(-5, "lines")
) +
scale_fill_manual(values = colors)
ggsave(
plot = cover,
width = 10,
height = 6,
filename = "images/cover_r.png",
bg = "white"
)Cover and Logo Design
The covers of Tidy Finance are inspired by the fast growing generative art community in R. Generative art refers to art that in whole or in part has been created with the use of an autonomous system. Instead of creating random dynamics, we rely on what is core to the book: the evolution of financial markets. The two versions share this idea but draw on different data: the R cover renders one ring per year from daily market returns, while the Python cover renders one ring per industry from the 12 industry portfolios. The few lines of code below replicate each figure in full.
Each circle in the cover figure corresponds to the daily market return within one year of our sample. Deviations from the circle line indicate positive or negative returns, and the colors are determined by the standard deviation of market returns during that year. We use the Wes Anderson color palette (also used throughout the book), provided by the wesanderson package (Ram and Wickham 2018).
Each panel in the cover figure corresponds to one of the 12 industry portfolios. The bars show the average daily return per year, scaled within each industry, and the colors are determined by the standard deviation of returns during that year. We recreate the Wes Anderson color palette (also used throughout the book) with a custom color map in matplotlib.
import numpy as np
import polars as pl
import tidyfinance as tf
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
tf.set_backend("polars")
main_colors = ["#3B9AB2", "#78B7C5", "#EBCC2A", "#E1AF00", "#F21A00"]
colormap = LinearSegmentedColormap.from_list("Zissou1", main_colors)
industries_ff_daily = tf.download_data(
domain="Fama-French",
dataset="12 Industry Portfolios [Daily]",
start_date="1927-01-01",
end_date="2022-12-31",
)
industries_long = industries_ff_daily.unpivot(
index="date",
variable_name="name",
value_name="value",
)
industries_order = sorted(industries_long["name"].unique().to_list())
data_figure = (
industries_long.with_columns(pl.col("date").dt.year().alias("year"))
.group_by("year", "name")
.agg(
total=pl.col("value").mean(),
vola=pl.col("value").std(),
)
.with_columns(vola_ntile=pl.col("vola").rank())
)
dpi = 300
num_cols = 4
num_rows = len(industries_order) // num_cols
fig, axs = plt.subplots(
num_rows,
num_cols,
subplot_kw={"projection": "polar"},
figsize=(2400 / dpi, 1800 / dpi),
dpi=dpi,
)
axs = axs.flatten()
for i, industry in enumerate(industries_order):
panel = data_figure.filter(pl.col("name") == industry).sort("year")
total = panel["total"].to_numpy()
vola_ntile = panel["vola_ntile"].to_numpy()
std_value = total.std()
total = 2 * (total - total.min()) / (total.max() - total.min()) - 1
angles = np.linspace(0, 2 * np.pi, len(panel), endpoint=False)
bar_width = 2 * np.pi / len(panel)
ax = axs[i]
ax.set_theta_offset(np.pi / 2)
ax.set_ylim(-std_value * 1400, 1)
ax.set_frame_on(False)
ax.xaxis.grid(False)
ax.yaxis.grid(False)
ax.set_xticks([])
ax.set_yticks([])
normalize = plt.Normalize(vola_ntile.min(), vola_ntile.max())
colors = colormap(normalize(vola_ntile))
ax.bar(
angles,
total,
width=bar_width,
color=colors,
edgecolor="white",
linewidth=0.2,
)
plt.tight_layout()
plt.subplots_adjust(wspace=-0.2, hspace=-0.1)
fig.savefig("images/cover_python.png", dpi=dpi, pad_inches=0, transparent=False)
plt.close(fig)To generate our logo, we focus on year 2021 - the end of the sample period at the time we published tidy-finance.org for the first time.
logo <- data_plot |>
ungroup() |>
filter(year == "2021-01-01") |>
mutate(vola = factor(vola, levels = levels)) |>
ggplot(aes(
x = day,
y = mkt_excess,
fill = vola
)) +
cp +
geom_ribbon(
aes(
ymin = ymin,
ymax = ymax,
fill = vola
),
alpha = 0.90
) +
theme_void() +
theme(
strip.text.x = element_blank(),
legend.position = "None",
plot.margin = unit(c(-0.15, -0.15, -0.15, -0.15), "null")
) +
scale_fill_manual(values = "white")
ggsave(
plot = logo,
width = 840,
height = 840,
units = "px",
filename = "images/logo-website-white.png",
)
ggsave(
plot = logo +
scale_fill_manual(values = wes_palette("Zissou1")[1]),
width = 840,
height = 840,
units = "px",
filename = "images/logo-website.png",
)