Bank Lending Survey Statistics - BLS

Data - ECB

Structure

Release Calendar

Link: https://www.ecb.europa.eu/press/calendars/statscal/mfm/html/stpbls.en.html

Données

Données PCE

Code
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")

Consumer credit, Loans for house purchase

Code
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")

Graphiques façon BCE : facteurs contributifs

Code
# 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)
}

Changes in credit standards for loans to enterprises, and contributing factors

Code
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")

Changes in demand for loans to households for house purchase, and contributing factors

Code
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")