Last observation: Q4 2026 (N = 15222)
First observation: Q1 2003 (N = 3500)
Last data update: 02 nov. 2026, 22:21
Last compile: 03 nov. 2026, 01:19
Link: https://www.ecb.europa.eu/press/calendars/statscal/mfm/html/stpbls.en.html
BLS |>
filter(KEY %in% c("BLS.Q.U2.ALL.Z.H.H.B3.ZZ.D.WFNET",
"BLS.Q.U2.ALL.Z.H.C.B3.ZZ.D.WFNET")) |>
quarter_to_date() %>%
select_if(~ n_distinct(.) > 1) |>
ggplot() + geom_line(aes(x = date, y = OBS_VALUE/100, color = Bls_count_detail)) +
xlab("") + ylab("") + theme_minimal() +
scale_x_date(breaks = as.Date(paste0(seq(1940, 2100, 2), "-01-01")),
labels = date_format("%Y")) +
scale_color_manual(values = c("blue", "red")) +
theme(legend.position = c(0.65, 0.25),
legend.title = element_blank()) +
scale_y_continuous(breaks = 0.01*seq(-100, 90, 10),
labels = scales::percent_format(accuracy = 1)) +
geom_hline(yintercept = 0, linetype = "dashed")
BLS |>
filter(KEY %in% c("BLS.Q.U2.ALL.Z.H.H.B3.ZZ.D.WFNET",
"BLS.Q.U2.ALL.Z.H.C.B3.ZZ.D.WFNET")) |>
quarter_to_date() %>%
select_if(~ n_distinct(.) > 1) |>
ggplot() + geom_line(aes(x = date, y = OBS_VALUE/100, color = Bls_count_detail)) +
xlab("") + ylab("") + theme_minimal() +
scale_x_date(breaks = as.Date(paste0(seq(1940, 2100, 2), "-01-01")),
labels = date_format("%Y")) +
theme(legend.position = c(0.65, 0.25),
legend.title = element_blank()) +
scale_y_continuous(breaks = 0.01*seq(-100, 90, 10),
labels = scales::percent_format(accuracy = 1)) +
geom_hline(yintercept = 0, linetype = "dashed")
# Les séries B3 de la vague t portent sur le trimestre t-1 (date de référence dans les graphiques BCE),
# les séries F3 de la vague t portent sur le trimestre t.
bls_ref <- function(TIME_PERIOD, TIME_HORIZON) {
q <- as.numeric(substr(TIME_PERIOD, 1, 4)) * 4 + as.numeric(substr(TIME_PERIOD, 7, 7)) - 1 -
(TIME_HORIZON == "B3")
paste0("Q", q %% 4 + 1, " ", q %/% 4)
}
bls_theme <- theme_minimal() +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
panel.grid.minor = element_blank(),
panel.grid.major.x = element_blank(),
legend.title = element_blank())
# factors : tribble BLS_ITEM -> factor (moyenne dans chaque groupe, somme pour "Other factors")
# overall : BLS_ITEM de la série d'ensemble ; series : BLS_COUNT, BLS_COUNT_DETAIL, EFFECT_DOMAIN, MARKET_ROLE
# reverse_stack = TRUE : empile les facteurs dans l'ordre de la légende en partant de zéro
bls_factor_chart <- function(factors, colors, overall, series, line_labels,
ea_breaks = 5, country_breaks = 10, reverse_stack = FALSE,
subtitle = NULL) {
factors <- factors |> mutate(factor = factor(factor, levels = unique(factor)))
standards <- BLS |>
filter(BANK_SELECTION == "ALL", BLS_COUNT == series[1], BLS_COUNT_DETAIL == series[2],
EFFECT_DOMAIN == series[3], MARKET_ROLE == series[4],
REF_AREA %in% c("U2", "DE", "ES", "FR", "IT"),
BLS_AGG_METHOD == ifelse(REF_AREA == "U2", "WFNET", "FNET"),
BLS_ITEM %in% c(overall, factors$BLS_ITEM),
(TIME_HORIZON == "B3") | (BLS_ITEM == overall & TIME_HORIZON == "F3")) |>
select(REF_AREA, Ref_area, BLS_ITEM, TIME_HORIZON, TIME_PERIOD, OBS_VALUE) |>
mutate(Ref_area = factor(Ref_area, levels = unique(Ref_area[order(match(REF_AREA, c("U2", "DE", "ES", "FR", "IT")))])),
ref = bls_ref(TIME_PERIOD, TIME_HORIZON),
ref_n = as.numeric(substr(ref, 4, 7)) * 4 + as.numeric(substr(ref, 2, 2)))
bars <- standards |>
inner_join(factors, by = "BLS_ITEM") |>
group_by(REF_AREA, Ref_area, ref, ref_n, factor) |>
summarise(OBS_VALUE = if_else(first(factor) == "Other factors",
sum(OBS_VALUE, na.rm = T), mean(OBS_VALUE, na.rm = T)),
.groups = "drop")
lines <- standards |>
filter(BLS_ITEM == overall) |>
mutate(line = ifelse(TIME_HORIZON == "B3", line_labels[1], line_labels[2]))
last_n <- max(lines$ref_n[lines$TIME_HORIZON == "B3"])
ea_window <- (last_n - 7):(last_n + 1)
ea_levels <- lines |> filter(ref_n %in% ea_window) |> distinct(ref, ref_n) |> arrange(ref_n) |> pull(ref)
p_ea <- ggplot(mapping = aes(x = factor(ref, levels = ea_levels))) +
geom_col(data = bars |> filter(REF_AREA == "U2", ref_n %in% ea_window),
aes(y = OBS_VALUE, fill = factor), width = 0.6,
position = position_stack(reverse = reverse_stack)) +
geom_line(data = lines |> filter(REF_AREA == "U2", ref_n %in% ea_window),
aes(y = OBS_VALUE, group = line, linewidth = line), color = "#003299") +
scale_fill_manual(values = colors, guide = "none") +
scale_linewidth_manual(values = c(1.5, 0.6)) +
scale_y_continuous(breaks = seq(-200, 200, ea_breaks)) +
geom_hline(yintercept = 0, color = "grey50") +
xlab("") + ylab("") + bls_theme +
theme(legend.position = "top", legend.direction = "vertical")
country_window <- (last_n - 3):last_n
country_levels <- lines |> filter(ref_n %in% country_window) |> distinct(ref, ref_n) |> arrange(ref_n) |> pull(ref)
countries <- c("DE", "ES", "FR", "IT")
p_countries <- ggplot(mapping = aes(x = factor(ref, levels = country_levels))) +
geom_col(data = bars |> filter(REF_AREA %in% countries, ref_n %in% country_window),
aes(y = OBS_VALUE, fill = factor), width = 0.6,
position = position_stack(reverse = reverse_stack)) +
geom_line(data = lines |> filter(REF_AREA %in% countries, ref_n %in% country_window,
TIME_HORIZON == "B3"),
aes(y = OBS_VALUE, group = 1), color = "#003299", linewidth = 1.5) +
facet_wrap(~ Ref_area, nrow = 1, strip.position = "bottom") +
scale_fill_manual(values = colors) +
scale_y_continuous(breaks = seq(-200, 200, country_breaks)) +
geom_hline(yintercept = 0, color = "grey50") +
xlab("") + ylab("") + bls_theme +
theme(legend.position = "top", legend.direction = "vertical",
legend.justification = "left", strip.placement = "outside")
patchwork::wrap_plots(p_ea, p_countries, widths = c(1, 2)) +
patchwork::plot_annotation(subtitle = subtitle)
}bls_factor_chart(
factors = tribble(
~BLS_ITEM, ~factor,
"RTO", "Banks' risk tolerance",
"GEA", "Risk perceptions", "IFO", "Risk perceptions", "RCD", "Risk perceptions",
"BC", "Competition", "NBC", "Competition", "MFC", "Competition",
"CP", "Cost of funds and balance sheet constraints",
"LP", "Cost of funds and balance sheet constraints",
"MF", "Cost of funds and balance sheet constraints",
"OF1", "Other factors", "OF2", "Other factors", "OF3", "Other factors"),
colors = c("Banks' risk tolerance" = "#FF4B00", "Risk perceptions" = "#FFB400",
"Competition" = "#00B1EA", "Cost of funds and balance sheet constraints" = "#65B800",
"Other factors" = "#8139C6"),
overall = "O", series = c("E", "Z", "ST", "S"),
line_labels = c("Credit standards - actual", "Credit standards - expected"),
subtitle = "Net percentages of banks reporting a tightening of credit standards, and contributing factors")
bls_factor_chart(
factors = tribble(
~BLS_ITEM, ~factor,
"IFS", "Use of alternative finance", "LOB", "Use of alternative finance",
"OSF", "Use of alternative finance",
"DR", "Other financing needs", "RFR", "Other financing needs",
"GLI", "General level of interest rates",
"CCF", "Consumer confidence",
"HMP", "Housing market prospects",
"OF1", "Other factors", "OF2", "Other factors", "OF3", "Other factors"),
colors = c("Use of alternative finance" = "#007816", "Other financing needs" = "#FF4B00",
"General level of interest rates" = "#FFB400", "Consumer confidence" = "#00B1EA",
"Housing market prospects" = "#65B800", "Other factors" = "#8139C6"),
overall = "Z", series = c("H", "H", "ZZ", "D"),
line_labels = c("Demand - actual", "Demand - expected"),
ea_breaks = 20, country_breaks = 20, reverse_stack = TRUE,
subtitle = "Net percentages of banks reporting an increase in demand, and contributing factors")