Skip to contents

This vignette implements PrjThaiHFID-#32.

It runs the investment–loan–bridge gateway (ffp_hfid_invest_loan_linked_abc_investloan_char_gateway), optionally writes eight regenerated tstm_* objects to data/ or data-temp/, and produces summary tables from gateway outputs.

Unit of observation: investment-loan-bridge linkages — investment-level investloan_type_* categories keyed on household hhid_Num x asset ivars x investment counter hh_inv_asset_ctr.

Outputs at a glance

This vignette regenerates eight gateway datasets, written to data/ when bl_replace_data_output <- TRUE, otherwise to the gitignored data-temp/ for safe validation. Use the links below to jump to each rendered data documentation page:

Object Data page Role
tstm_loans_pn_nd reference Non-duplicate monthly loan panel (Set A loans)
tstm_loans_hooks reference Hooked loan pairs (Set A + B)
tstm_loans_bridges_type reference Loan bridges with formal/informal type
tstm_invdates_uniq reference Unique investment dates
tstm_invest reference Investment spells by asset ivars
tstm_roster_invest_loan_linked reference Investment-to-loan roster
tstm_roster_invest2loan2bridge reference Investment-to-loan-to-bridge roster
tstm_invest2loan2bridge_chars reference Investment-level linkage characteristics

These objects are documented in R/data-res.R. The two inputs tstm_loans_panel and tstm_asset_loan are documented in R/data.R and R/data-res.R.

Tables and figures (not saved as data): an investment-type-by-asset wide table with paper-style row labels, investment-level counts and shares for investloan_type_m4 / investloan_type_brg_m5 / investloan_type_m8, and roster diagnostic tallies. Each table is gated by its own ls_save_res[["<name>"]] switch (all FALSE by default); flip one on to write its LaTeX/CSV (including example_table) to res/res_bridge_type/ via ffp_save_res_table().

What ships vs. what is generated locally

  • Shipped with the installed package: the packaged datasets in data/ (lazy-loaded inputs tstm_loans_panel, tstm_asset_loan).
  • Generated locally when this vignette runs (neither pushed to GitHub nor shipped in the package): the eight gateway .rda written to data/ (or data-temp/); their inst/extdata/*.csv / *.dta exports; and any tables written to res/res_bridge_type/ (.tex/.csv, only when the matching ls_save_res control is TRUE). The inst/extdata/ and res/ artifacts are git-ignored, saved only for local convenience.

Required packaged inputs

The gateway loads only two objects from the package data/ folder (must exist locally when knitting):

Object Role
tstm_loans_panel Monthly loan panel (Group A)
tstm_asset_loan Household-month asset and loan aggregates (Group B)

All other pipeline objects are computed in this run.

Pipeline structure

flowchart TD
  subgraph inputs [Packaged data inputs]
    loansPanel[tstm_loans_panel]
    assetLoan[tstm_asset_loan]
  end
  subgraph gateway [Gateway run]
    gw[ffp_hfid_invest_loan_linked_abc_investloan_char_gateway]
  end
  loansPanel --> gw
  assetLoan --> gw
  subgraph outputs [Eight regenerated tstm objects]
    o1[tstm_loans_pn_nd]
    o2[tstm_loans_hooks]
    o3[tstm_loans_bridges_type]
    o4[tstm_invdates_uniq]
    o5[tstm_invest]
    o6[tstm_roster_invest_loan_linked]
    o7[tstm_roster_invest2loan2bridge]
    o8[tstm_invest2loan2bridge_chars]
  end
  gw --> o1
  gw --> o2
  gw --> o3
  gw --> o4
  gw --> o5
  gw --> o6
  gw --> o7
  gw --> o8
  subgraph deliver [Save and tabulate]
    saveRda[save to data or data-temp]
    tabs[investloan_type m4 brg_m5 m8 tables]
    tex[res_bridge_type latex tables]
  end
  o1 --> saveRda
  o8 --> saveRda
  o8 --> tabs
  o7 --> tabs
  o8 --> tex

Control parameters

library(PrjThaiHFID)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(tidyr)
library(glue)
library(kableExtra)
#> 
#> Attaching package: 'kableExtra'
#> The following object is masked from 'package:dplyr':
#> 
#>     group_rows

bs_style <- c("striped", "hover", "condensed", "responsive")
options(kable_styling_bootstrap_options = bs_style)

verbose <- TRUE
it_file_code <- 494318

# TRUE: overwrite canonical data/*.rda; FALSE: write to data-temp/ (gitignored)
bl_replace_data_output <- FALSE

st_kableformat <- "html"

# MBF timing and size cuts (same as R-script/ffs_bridge_count/ffs_bridge_count_mbf.R)
it_mth_inv_start_min_cut <- 14
it_mth_inv_start_max_cut <- 144
fl_min_invest_size_cut <- 10000

# Three paper asset variables (Table 4)
ar_st_vars_to_keep <- c("agg_BS_1021", "agg_BS_1012", "agg_BS_1011")
ar_ivars_paper <- ar_st_vars_to_keep

# here::here() anchors on the temporary _quarto.yml pkgdown writes under
# vignettes/ during a quarto render; find the package root explicitly so paths
# resolve under both Quarto and knitr.
spn_pkg_root <- rprojroot::find_root(rprojroot::has_file("DESCRIPTION"))
spn_res_bridge_type <- file.path(
  spn_pkg_root, "res", "res_bridge_type",
  fsep = .Platform$file.sep
)
spt_res <- file.path(spn_pkg_root, "res", "res_bridge_type")
st_data_out <- file.path(
  spn_pkg_root,
  if (bl_replace_data_output) "data" else "data-temp",
  fsep = .Platform$file.sep
)
st_extdata_dir <- file.path(spn_pkg_root, "inst", "extdata")

# Per-table .tex/.csv save switches; all FALSE by default (display always shown).
ls_save_res <- list(
  example_table = FALSE,
  tab_inv_m4_counts = FALSE,
  tab_inv_m4_shares = FALSE,
  tab_inv_brg_m5_counts = FALSE,
  tab_inv_brg_m5_shares = FALSE,
  tab_inv_m8_counts = FALSE,
  tab_inv_m8_shares = FALSE,
  tab_roster_ivars = FALSE,
  tab_chars_m8 = FALSE,
  tab_roster_bl_informal = FALSE,
  tab_roster_m8_informal = FALSE
)

dir.create(spn_res_bridge_type, recursive = TRUE, showWarnings = FALSE)
for (st_dir in c(st_data_out, st_extdata_dir)) {
  if (!dir.exists(st_dir)) {
    dir.create(st_dir, recursive = TRUE)
  }
}

ar_tstm_save <- c(
  "tstm_loans_pn_nd",
  "tstm_loans_hooks",
  "tstm_loans_bridges_type",
  "tstm_invdates_uniq",
  "tstm_invest",
  "tstm_roster_invest_loan_linked",
  "tstm_roster_invest2loan2bridge",
  "tstm_invest2loan2bridge_chars"
)

ffv_save_gateway_tstm <- function(obj, name, out_dir, extdata_dir = st_extdata_dir) {
  if (!dir.exists(out_dir)) {
    dir.create(out_dir, recursive = TRUE)
  }
  if (!dir.exists(extdata_dir)) {
    dir.create(extdata_dir, recursive = TRUE)
  }

  st_rda <- file.path(out_dir, paste0(name, ".rda"))
  st_csv <- file.path(extdata_dir, paste0(name, ".csv"))
  st_dta <- file.path(extdata_dir, paste0(name, ".dta"))

  tmp_env <- new.env(parent = emptyenv())
  tmp_env[[name]] <- obj
  save(list = name, file = st_rda, envir = tmp_env)

  readr::write_csv(obj, st_csv)

  # Local-only .dta export (git-ignored). Stata caps variable names at 32
  # characters, so truncate long names (uniquely) for the .dta copy only; the
  # canonical .rda and the full-name .csv are unaffected. Never let a Stata
  # name/format edge case break the knit -> skip the .dta with a message.
  if (requireNamespace("haven", quietly = TRUE)) {
    tryCatch(
      {
        obj_dta <- obj
        nm <- names(obj_dta)
        if (any(nchar(nm) > 32)) {
          nm <- substr(nm, 1, 32)
          nm <- make.unique(nm, sep = "_")
          nm <- substr(nm, 1, 32)
          names(obj_dta) <- nm
        }
        haven::write_dta(obj_dta, st_dta)
      },
      error = function(e) {
        message(glue::glue(
          "skip .dta for {name}: {conditionMessage(e)} (csv + rda still written)"
        ))
        st_dta <<- NA_character_
      }
    )
  } else {
    message("package 'haven' not installed: skipping .dta export (csv + rda still written)")
    st_dta <- NA_character_
  }

  invisible(list(rda = st_rda, csv = st_csv, dta = st_dta))
}

ffv_inv_type_by_ivars <- function(df, type_var) {
  df %>%
    dplyr::group_by(ivars, .data[[type_var]]) %>%
    dplyr::tally(name = "n") %>%
    tidyr::pivot_wider(names_from = ivars, values_from = n, values_fill = 0)
}

ffv_inv_type_share_by_ivars <- function(df, type_var) {
  df %>%
    dplyr::group_by(ivars, .data[[type_var]]) %>%
    dplyr::tally(name = "n") %>%
    dplyr::group_by(ivars) %>%
    dplyr::mutate(share = n / sum(n)) %>%
    dplyr::select(-n) %>%
    tidyr::pivot_wider(
      id_cols = dplyr::all_of(type_var),
      names_from = ivars,
      values_from = share
    )
}

ffv_m8_cell <- function(tb, m8, ivar, kind = c("n", "pct")) {
  kind <- match.arg(kind)
  col <- paste0(ivar, if (kind == "n") "_n" else "_share")
  if (!m8 %in% tb$investloan_type_m8) {
    return(if (kind == "n") "0" else "0.0\\%")
  }
  val <- tb[[col]][tb$investloan_type_m8 == m8][1]
  if (kind == "n") {
    as.character(as.integer(val))
  } else {
    sprintf("%.1f\\%%", val * 100)
  }
}

ffv_m8_data_row <- function(tb, m8) {
  ivars <- c("agg_BS_1021", "agg_BS_1012", "agg_BS_1011")
  vals <- unlist(lapply(ivars, function(iv) {
    c(ffv_m8_cell(tb, m8, iv, "n"), ffv_m8_cell(tb, m8, iv, "pct"))
  }))
  paste0("& ", paste(vals, collapse = " \n& "), "\\\\")
}

# Write example_table.tex with fixed layout from res/res_bridge_type/example_table_ori.tex
ffv_write_example_table_tex <- function(tb, name = "example_table",
                                        spn_out = spt_res, df = NULL,
                                        bl_save = FALSE) {
  rows <- list(
    "1-investment-no-loan",
    "2-investment-loan-a",
    "3-investment-loan-hook",
    "4-brg-single-lender",
    "5-brg-baac-vilfund-combo",
    "6-brg-informal-quasifor-combo",
    "7-brg-has-informal-bridge",
    "8-brg-has-informal-in-bridge"
  )
  body_rows <- vapply(rows, function(m8) ffv_m8_data_row(tb, m8), character(1))

  tex <- c(
    "% Auto-generated from vignettes/ffv_invest_loan_bridge.qmd",
    "% Styling matches res/res_bridge_type/example_table_ori.tex",
    "%  it_gap_LBL_IL_min <- -6 horizon",
    "\\begin{tabular}[t]{",
    ">{\\centering\\arraybackslash}p{3.0cm}",
    ">{\\centering\\arraybackslash}p{2.0cm}",
    ">{\\centering\\arraybackslash}p{2.0cm}",
    ">{\\centering\\arraybackslash}p{2.0cm}",
    ">{\\centering\\arraybackslash}p{2.0cm}",
    ">{\\centering\\arraybackslash}p{2.0cm}",
    ">{\\centering\\arraybackslash}p{2.0cm}}",
    "\\toprule",
    "& \\multicolumn{6}{c}{",
    "\\textbf{Alternative investment definitions}",
    "}\\\\",
    "\\cmidrule(l{3pt}r{3pt}){2-7}",
    "\\multicolumn{1}{c}{ } & ",
    "\\multicolumn{2}{p{4cm}}{",
    "\\centering\\small Agricultural and business assets investments",
    "} & ",
    "\\multicolumn{2}{p{4cm}}{",
    "\\centering\\small Agricultural, business, and land assets investments",
    "} & ",
    "\\multicolumn{2}{p{4cm}}{",
    "\\centering\\small Agricultural, business, land, and household assets investments",
    "}\\\\",
    "\\cmidrule(l{3pt}r{3pt}){2-3} \\cmidrule(l{3pt}r{3pt}){4-5} \\cmidrule(l{3pt}r{3pt}){6-7}",
    "& \\small{\\#} & \\multicolumn{1}{p{2cm}}{\\centering\\small{\\%}} ",
    "& \\small{\\#} & \\multicolumn{1}{p{2cm}}{\\centering\\small{\\%}}",
    "& \\small{\\#} & \\multicolumn{1}{p{2cm}}{\\centering\\small{\\%}}\\\\",
    "\\midrule",
    "\\addlinespace[0.5em]",
    "\\multicolumn{7}{l}{\\textbf{",
    "Investments \\emph{not} time-linked to loan bridges",
    "}}\\\\",
    "\\multicolumn{7}{l}{",
    "\\hspace{1em}",
    "Invest. \\emph{not} time-linked any loans",
    "}\\\\",
    "\\addlinespace[0.1em]",
    body_rows[1],
    "\\addlinespace[0.2em]",
    "\\multicolumn{7}{l}{",
    "\\hspace{1em}",
    "Invest. time-linked to \\emph{standalone} loans (set A loans only)",
    "}\\\\",
    "\\addlinespace[0.1em]",
    body_rows[2],
    "\\addlinespace[0.2em]",
    "\\multicolumn{7}{l}{",
    "\\hspace{1em}",
    "Invest. time-linked to \\emph{``hooked''} loans (set A and B loans only)",
    "}\\\\",
    "\\addlinespace[0.1em]",
    body_rows[3],
    "\\addlinespace[0.2em]",
    "\\cmidrule(l{10pt}r{3pt}){1-7}",
    "\\multicolumn{7}{l}{\\textbf{",
    "Investments time-linked to \\emph{only} formal \\emph{or} \\emph{only} informal loan bridges",
    "}}\\\\",
    "\\multicolumn{7}{l}{",
    "\\hspace{1.0em}",
    "\\emph{Single lender} type only investment-loan bridge",
    "}\\\\",
    "\\addlinespace[0.1em]",
    body_rows[4],
    "\\addlinespace[0.2em]",
    "\\multicolumn{7}{l}{",
    "\\hspace{1.0em}",
    "\\emph{Multiple formal} lenders investment-loan bridge",
    "}\\\\",
    "\\addlinespace[0.1em]",
    body_rows[5],
    "\\addlinespace[0.2em]",
    "\\multicolumn{7}{l}{",
    "\\hspace{1.0em}",
    "\\emph{Multiple informal} lender types investment-loan bridge",
    "}\\\\",
    "\\addlinespace[0.1em]",
    body_rows[6],
    "\\addlinespace[0.2em]",
    "\\cmidrule(l{10pt}r{3pt}){1-7}",
    "\\multicolumn{7}{l}{\\textbf{",
    "Investments time-linked to \\emph{joint} formal \\emph{and} informal investment-loan bridges",
    "}}\\\\",
    "\\multicolumn{7}{l}{",
    "\\hspace{1.0em}",
    "Contains \\emph{formal---informal---formal} loan bridges",
    "}\\\\",
    "\\addlinespace[0.1em]",
    body_rows[7],
    "\\addlinespace[0.2em]",
    "\\multicolumn{7}{l}{",
    "\\hspace{1.0em}",
    "Do not contain formal---informal---formal loan bridges, but includes \\emph{informal links} in bridges",
    "}\\\\",
    "\\addlinespace[0.1em]",
    body_rows[8],
    "\\addlinespace[0.2em]",
    "\\bottomrule",
    "\\end{tabular}"
  )
  ffp_save_res_table(tex, name, spn_out, df = df, bl_save = bl_save)
  invisible(tex)
}

ffv_render_table <- function(
    df,
    caption,
    name = NULL,
    bl_save = FALSE,
    spn_res = spt_res,
    st_format = st_kableformat) {
  kt <- kbl(
    df,
    format = st_format,
    booktabs = TRUE,
    caption = caption,
    digits = 4
  ) %>%
    kable_styling(
      bootstrap_options = bs_style,
      full_width = FALSE,
      position = "left"
    )
  if (isTRUE(bl_save) && !is.null(name)) {
    tex <- knitr::kable(
      df,
      format = "latex",
      booktabs = TRUE,
      caption = caption,
      label = name,
      digits = 4
    )
    ffp_save_res_table(tex, name, spn_res, df = df, bl_save = bl_save)
  }
  kt
}

if (verbose) {
  print(glue::glue("f-{it_file_code}, controls"))
  print(glue::glue("  bl_replace_data_output: {bl_replace_data_output} -> {st_data_out}/"))
  print(glue::glue("  st_extdata_dir: {st_extdata_dir}"))
  print(glue::glue("  ls_save_res TRUE: {paste(names(which(unlist(ls_save_res))), collapse = ', ')}"))
  print(glue::glue("  it_mth_inv_start_min_cut: {it_mth_inv_start_min_cut}"))
  print(glue::glue("  it_mth_inv_start_max_cut: {it_mth_inv_start_max_cut}"))
  print(glue::glue("  fl_min_invest_size_cut: {fl_min_invest_size_cut}"))
  print(glue::glue("  ar_st_vars_to_keep: {paste(ar_st_vars_to_keep, collapse = ', ')}"))
}
#> f-494318, controls
#>   bl_replace_data_output: FALSE -> /home/runner/work/PrjThaiHFID/PrjThaiHFID/data-temp/
#>   st_extdata_dir: /home/runner/work/PrjThaiHFID/PrjThaiHFID/inst/extdata
#>   ls_save_res TRUE: 
#>   it_mth_inv_start_min_cut: 14
#>   it_mth_inv_start_max_cut: 144
#>   fl_min_invest_size_cut: 10000
#>   ar_st_vars_to_keep: agg_BS_1021, agg_BS_1012, agg_BS_1011

Run gateway

Same call as R-script/ffs_bridge_count/ffs_bridge_count_mbf.R.

if (verbose) {
  print(glue::glue("f-{it_file_code}, gateway: calling ffp_hfid_invest_loan_linked_abc_investloan_char_gateway"))
}
#> f-494318, gateway: calling ffp_hfid_invest_loan_linked_abc_investloan_char_gateway

ls_gateway_result <- PrjThaiHFID::ffp_hfid_invest_loan_linked_abc_investloan_char_gateway(
  svr_lender_var = "forinfm4",
  svr_principal = "bf5klm_bm6h_joint",
  svr_principal_last = "bm6h",
  svr_principal_interest_sum = "bm6b",
  bl_filter_bridge_grvgr0 = TRUE,
  it_ll_grv_min = -1,
  bl_filter_loan_duration_a = FALSE,
  bl_filter_loan_duration_b = FALSE,
  bl_filter_lender_type = FALSE,
  bl_filter_bridge_informal = FALSE,
  bl_filter_loan_size = FALSE,
  bl_filter_loan_duration_more = FALSE,
  fl_sd_ithres = stats::qnorm(0.99),
  it_thres_invest_mth_gap = 2,
  it_gap_LBL_IL_min = -6,
  ar_st_vars_to_keep = ar_st_vars_to_keep,
  fl_min_invest_size = fl_min_invest_size_cut,
  it_mth_inv_start_min = it_mth_inv_start_min_cut,
  it_mth_inv_start_max = it_mth_inv_start_max_cut,
  bl_drop_afrombc = TRUE,
  bl_drop_cfromb = TRUE,
  bl_compare2baserda = FALSE,
  verbose = verbose,
  verbose_detail = FALSE,
  it_verbose_detail_nrow = 100
)
#> 
#> ── char_gateway ────────────────────────────────────────────────────────────────
#> ℹ A.1 Loan non-duplicate
#> ✔ A.1 Loan non-duplicate [14ms]
#> 
#> ✔ tstm_loans_pn_nd: 19587 rows
#> ℹ A.2 Hook pairs
#> ✔ A.2 Hook pairs [6ms]
#> 
#> ✔ tstm_loans_hooks: 47754 rows
#> ℹ A.3 Bridges from hooks
#> ✔ A.3 Bridges from hooks [6ms]
#> 
#> ✔ tstm_loans_bridges: 94907 rows
#> ℹ A.4 Bridge type
#> ✔ A.4 Bridge type [6ms]
#> 
#> ✔ tstm_loans_bridges_type: 94907 rows
#> ℹ B Investment gateway
#> ✔ B Investment gateway [9ms]
#> 
#> ℹ Group B: processing 3 asset ivar(s): agg_BS_1021, agg_BS_1012, agg_BS_1011
#> ✔ tstm_invest: 4694 rows
#> ℹ C.1 Bridge roster
#> ✔ C.1 Bridge roster [6ms]
#> 
#> Adding missing grouping variables: `br_type`, `br_type_id`
#> ✔ tstm_roster_invest_loan_bridge: 82593 rows
#> ℹ C.2 Linker
#> ✔ C.2 Linker [6ms]
#> 
#> ✔ tstm_roster_invest_loan_linker: 4120 rows
#> ℹ C.3 Invest-loan linked
#> ✔ C.3 Invest-loan linked [6ms]
#> 
#> ✔ tstm_roster_invest_loan_linked: 145746 rows
#> ℹ D.1 abc_distinct filter
#> ✔ D.1 abc_distinct filter [6ms]
#> 
#> ✔ tstm_roster_invest2loan2bridge_clean: 10449 rows
#> ℹ D.1b NA synchronization
#> ✔ D.1b NA synchronization [6ms]
#> 
#> ℹ D.2 Bridge char
#> ✔ D.2 Bridge char [6ms]
#> 
#> ✔ tstm_roster_invest2loan2bridge: 10449 rows
#> ℹ D.3 Investment char
#> ✔ D.3 Investment char [6ms]
#> 
#> ✔ tstm_invest2loan2bridge_chars: 2990 rows
#> ℹ E Invest typing summary
#> ✔ E Invest typing summary [6ms]
#> 
#> ── Group E: invest typing summary
#> ──────────────────────────────────────────────
#> ℹ Thresholds: capital_invest >= 10000; mth_inv_start in [14, 144]
#> 
#> ── Invest rows not in bridge chars
#> Count not in chars: 1704
#> By exclusion reason (mutually exclusive):
#> # A tibble: 3 × 3
#>   st_unmatched_reason     n share
#>   <chr>               <int> <dbl>
#> 1 fail_size_and_start   190 0.112
#> 2 fail_size_only        966 0.567
#> 3 fail_start_only       548 0.322
#> 2x2 cross-tab (bl_meets_size x bl_meets_start):
#> # A tibble: 3 × 3
#>   bl_meets_size bl_meets_start     n
#>   <lgl>         <lgl>          <int>
#> 1 FALSE         FALSE            190
#> 2 FALSE         TRUE             966
#> 3 TRUE          FALSE            548
#> 
#> ── Pipeline consistency (per ivars)
#> # A tibble: 3 × 11
#>   ivars   n_invest n_pass_typing n_in_chars n_not_in_linked n_pass_not_in_linked
#>   <chr>      <int>         <int>      <int>           <int>                <int>
#> 1 agg_BS…     1390           736        736               0                    0
#> 2 agg_BS…     1414           821        821               0                    0
#> 3 agg_BS…     1890          1433       1433               0                    0
#> # ℹ 5 more variables: n_pass_not_in_chars <int>,
#> #   n_capital_mismatch_invest_roster <int>,
#> #   n_start_mismatch_invest_roster <int>, n_ctr_mismatch_invest_roster <int>,
#> #   n_capital_mismatch_chars_roster <int>
#> ✔ GATEWAY INVEST PIPELINE CONSISTENCY: PASS (all checks OK for ar_st_vars_to_keep)

for (nm in ar_tstm_save) {
  assign(nm, ls_gateway_result[[nm]], envir = .GlobalEnv)
}

tstm_loans_bridges_type <- ls_gateway_result$tstm_loans_bridges_type

if (verbose) {
  print(glue::glue("f-{it_file_code}, gateway: unpacked {length(ar_tstm_save)} tstm objects"))
  for (nm in ar_tstm_save) {
    obj <- ls_gateway_result[[nm]]
    print(glue::glue("  {nm}: {nrow(obj)} rows"))
  }
}
#> f-494318, gateway: unpacked 8 tstm objects
#>   tstm_loans_pn_nd: 19587 rows
#>   tstm_loans_hooks: 47754 rows
#>   tstm_loans_bridges_type: 94907 rows
#>   tstm_invdates_uniq: 3066 rows
#>   tstm_invest: 4694 rows
#>   tstm_roster_invest_loan_linked: 145746 rows
#>   tstm_roster_invest2loan2bridge: 10449 rows
#>   tstm_invest2loan2bridge_chars: 2990 rows

Save regenerated tstm_* objects

for (nm in ar_tstm_save) {
  ffv_save_gateway_tstm(
    obj = ls_gateway_result[[nm]],
    name = nm,
    out_dir = st_data_out
  )
}

if (verbose) {
  if (bl_replace_data_output) {
    print(glue::glue("f-{it_file_code}, save: replaced canonical outputs under {st_data_out}/"))
  } else {
    print(glue::glue("f-{it_file_code}, save: wrote regenerated outputs to {st_data_out}/ (data/ unchanged)"))
  }
}
#> f-494318, save: wrote regenerated outputs to /home/runner/work/PrjThaiHFID/PrjThaiHFID/data-temp/ (data/ unchanged)

Table: investment type by asset (wide)

Reproduces the summary in R-dev/ffs_hfid_gateway_ilhb.R (lines 477–511): counts and shares of investloan_type_m8 by ivars, pivoted wide.

tb_inv_m8_wide <- tstm_invest2loan2bridge_chars %>%
  dplyr::select(
    hhid_Num, ivars, hh_inv_asset_ctr,
    investloan_type_m8
  ) %>%
  dplyr::distinct() %>%
  dplyr::filter(ivars %in% ar_ivars_paper) %>%
  dplyr::group_by(ivars, investloan_type_m8) %>%
  dplyr::tally(name = "n") %>%
  dplyr::group_by(ivars) %>%
  dplyr::mutate(share = n / sum(n)) %>%
  dplyr::ungroup() %>%
  tidyr::pivot_wider(
    names_from = ivars,
    values_from = c(n, share),
    names_glue = "{ivars}_{.value}",
    values_fill = list(n = 0, share = 0)
  ) %>%
  dplyr::mutate(
    n_total = agg_BS_1021_n + agg_BS_1012_n + agg_BS_1011_n
  ) %>%
  dplyr::mutate(
    dplyr::across(dplyr::ends_with("_share"), ~ round(.x, 4))
  ) %>%
  dplyr::select(
    investloan_type_m8,
    n_total,
    agg_BS_1021_n,
    agg_BS_1021_share,
    agg_BS_1012_n,
    agg_BS_1012_share,
    agg_BS_1011_n,
    agg_BS_1011_share
  ) %>%
  dplyr::arrange(investloan_type_m8)

# HTML preview: paper-style row labels (same order as example_table_ori.tex)
ar_m8_paper_rows <- data.frame(
  investloan_type_m8 = c(
    "1-investment-no-loan",
    "2-investment-loan-a",
    "3-investment-loan-hook",
    "4-brg-single-lender",
    "5-brg-baac-vilfund-combo",
    "6-brg-informal-quasifor-combo",
    "7-brg-has-informal-bridge",
    "8-brg-has-informal-in-bridge"
  ),
  row_label = c(
    "Invest. not time-linked any loans",
    "Invest. time-linked to standalone loans (set A only)",
    "Invest. time-linked to hooked loans (set A and B only)",
    "Single lender type only investment-loan bridge",
    "Multiple formal lenders investment-loan bridge",
    "Multiple informal lender types investment-loan bridge",
    "Contains formal-informal-formal loan bridges",
    "Informal links in bridges (not formal-informal-formal)"
  ),
  stringsAsFactors = FALSE
)

tb_inv_m8_html <- tb_inv_m8_wide %>%
  dplyr::left_join(ar_m8_paper_rows, by = "investloan_type_m8") %>%
  dplyr::transmute(
    Category = row_label,
    `Ag+biz #` = agg_BS_1021_n,
    `Ag+biz %` = sprintf("%.1f", agg_BS_1021_share * 100),
    `Ag+biz+land #` = agg_BS_1012_n,
    `Ag+biz+land %` = sprintf("%.1f", agg_BS_1012_share * 100),
    `Ag+biz+land+hh #` = agg_BS_1011_n,
    `Ag+biz+land+hh %` = sprintf("%.1f", agg_BS_1011_share * 100)
  )

kbl(
  tb_inv_m8_html,
  format = st_kableformat,
  booktabs = TRUE,
  caption = "Investment–loan–bridge types by asset definition (counts and %)"
) %>%
  kable_styling(
    bootstrap_options = bs_style,
    full_width = FALSE,
    position = "left"
  )
Investment–loan–bridge types by asset definition (counts and %)
Category Ag+biz # Ag+biz % Ag+biz+land # Ag+biz+land % Ag+biz+land+hh # Ag+biz+land+hh %
Invest. not time-linked any loans 206 28.0 244 29.7 424 29.6
Invest. time-linked to standalone loans (set A only) 118 16.0 131 16.0 272 19.0
Invest. time-linked to hooked loans (set A and B only) 49 6.7 54 6.6 109 7.6
Single lender type only investment-loan bridge 28 3.8 27 3.3 61 4.3
Multiple formal lenders investment-loan bridge 29 3.9 31 3.8 41 2.9
Multiple informal lender types investment-loan bridge 2 0.3 1 0.1 4 0.3
Contains formal-informal-formal loan bridges 176 23.9 189 23.0 295 20.6
Informal links in bridges (not formal-informal-formal) 128 17.4 144 17.5 227 15.8

ffv_write_example_table_tex(
  tb_inv_m8_wide,
  name = "example_table",
  spn_out = spt_res,
  df = tb_inv_m8_wide,
  bl_save = ls_save_res[["example_table"]]
)

Tables: investment-level m4, brg_m5, and m8

Investment-level tabulations (same structure as R-script/ffs_bridge_type/ffs_bridge_type.R, Section A, it_ctr == 1).

Four categories (investloan_type_m4)

tb_inv_m4_counts <- ffv_inv_type_by_ivars(
  tstm_invest2loan2bridge_chars, "investloan_type_m4"
)
tb_inv_m4_shares <- ffv_inv_type_share_by_ivars(
  tstm_invest2loan2bridge_chars, "investloan_type_m4"
)

ffv_render_table(
  tb_inv_m4_counts,
  caption = "Investment-level counts by investloan_type_m4 and ivars",
  name = "tab_inv_m4_counts",
  bl_save = ls_save_res[["tab_inv_m4_counts"]]
)
Investment-level counts by investloan_type_m4 and ivars
investloan_type_m4 agg_BS_1011 agg_BS_1012 agg_BS_1021
1-investment-no-loan 424 244 206
2-investment-loan-a 272 131 118
3-investment-loan-hook 109 54 49
4-investment-loan-bridge 628 392 363
ffv_render_table(
  tb_inv_m4_shares,
  caption = "Investment-level shares by investloan_type_m4 and ivars",
  name = "tab_inv_m4_shares",
  bl_save = ls_save_res[["tab_inv_m4_shares"]]
)
Investment-level shares by investloan_type_m4 and ivars
investloan_type_m4 agg_BS_1011 agg_BS_1012 agg_BS_1021
1-investment-no-loan 0.2959 0.2972 0.2799
2-investment-loan-a 0.1898 0.1596 0.1603
3-investment-loan-hook 0.0761 0.0658 0.0666
4-investment-loan-bridge 0.4382 0.4775 0.4932

Five bridge categories (investloan_type_brg_m5)

tb_inv_brg_m5_counts <- ffv_inv_type_by_ivars(
  tstm_invest2loan2bridge_chars, "investloan_type_brg_m5"
)
tb_inv_brg_m5_shares <- ffv_inv_type_share_by_ivars(
  tstm_invest2loan2bridge_chars, "investloan_type_brg_m5"
)

ffv_render_table(
  tb_inv_brg_m5_counts,
  caption = "Investment-level counts by investloan_type_brg_m5 and ivars",
  name = "tab_inv_brg_m5_counts",
  bl_save = ls_save_res[["tab_inv_brg_m5_counts"]]
)
Investment-level counts by investloan_type_brg_m5 and ivars
investloan_type_brg_m5 agg_BS_1011 agg_BS_1012 agg_BS_1021
1-brg-single-lender 61 27 28
2-brg-baac-vilfund-combo 41 31 29
3-brg-informal-quasifor-combo 4 1 2
4-brg-has-informal-bridge 295 189 176
5-brg-has-informal-in-bridge 227 144 128
Not triply-bridged 805 429 373
ffv_render_table(
  tb_inv_brg_m5_shares,
  caption = "Investment-level shares by investloan_type_brg_m5 and ivars",
  name = "tab_inv_brg_m5_shares",
  bl_save = ls_save_res[["tab_inv_brg_m5_shares"]]
)
Investment-level shares by investloan_type_brg_m5 and ivars
investloan_type_brg_m5 agg_BS_1011 agg_BS_1012 agg_BS_1021
1-brg-single-lender 0.0426 0.0329 0.0380
2-brg-baac-vilfund-combo 0.0286 0.0378 0.0394
3-brg-informal-quasifor-combo 0.0028 0.0012 0.0027
4-brg-has-informal-bridge 0.2059 0.2302 0.2391
5-brg-has-informal-in-bridge 0.1584 0.1754 0.1739
Not triply-bridged 0.5618 0.5225 0.5068

Eight categories (investloan_type_m8)

tb_inv_m8_counts <- ffv_inv_type_by_ivars(
  tstm_invest2loan2bridge_chars, "investloan_type_m8"
)
tb_inv_m8_shares <- ffv_inv_type_share_by_ivars(
  tstm_invest2loan2bridge_chars, "investloan_type_m8"
)

ffv_render_table(
  tb_inv_m8_counts,
  caption = "Investment-level counts by investloan_type_m8 and ivars",
  name = "tab_inv_m8_counts",
  bl_save = ls_save_res[["tab_inv_m8_counts"]]
)
Investment-level counts by investloan_type_m8 and ivars
investloan_type_m8 agg_BS_1011 agg_BS_1012 agg_BS_1021
1-investment-no-loan 424 244 206
2-investment-loan-a 272 131 118
3-investment-loan-hook 109 54 49
4-brg-single-lender 61 27 28
5-brg-baac-vilfund-combo 41 31 29
6-brg-informal-quasifor-combo 4 1 2
7-brg-has-informal-bridge 295 189 176
8-brg-has-informal-in-bridge 227 144 128
ffv_render_table(
  tb_inv_m8_shares,
  caption = "Investment-level shares by investloan_type_m8 and ivars",
  name = "tab_inv_m8_shares",
  bl_save = ls_save_res[["tab_inv_m8_shares"]]
)
Investment-level shares by investloan_type_m8 and ivars
investloan_type_m8 agg_BS_1011 agg_BS_1012 agg_BS_1021
1-investment-no-loan 0.2959 0.2972 0.2799
2-investment-loan-a 0.1898 0.1596 0.1603
3-investment-loan-hook 0.0761 0.0658 0.0666
4-brg-single-lender 0.0426 0.0329 0.0380
5-brg-baac-vilfund-combo 0.0286 0.0378 0.0394
6-brg-informal-quasifor-combo 0.0028 0.0012 0.0027
7-brg-has-informal-bridge 0.2059 0.2302 0.2391
8-brg-has-informal-in-bridge 0.1584 0.1754 0.1739

Tables: roster diagnostics

Light tallies from gateway outputs (no triply-linked df_wrk prep). Merge investment-level types onto the loan-level roster for investloan_type_m8 tabulations.

tstm_roster_with_types <- tstm_roster_invest2loan2bridge %>%
  dplyr::left_join(
    tstm_invest2loan2bridge_chars %>%
      dplyr::select(
        hhid_Num, ivars, hh_inv_asset_ctr,
        investloan_type_m4, investloan_type_m8
      ),
    by = c("hhid_Num", "ivars", "hh_inv_asset_ctr")
  )

tb_roster_ivars <- tstm_roster_invest2loan2bridge %>%
  dplyr::group_by(ivars) %>%
  dplyr::tally(name = "n") %>%
  dplyr::arrange(ivars)

tb_chars_m8 <- tstm_invest2loan2bridge_chars %>%
  dplyr::group_by(investloan_type_m8) %>%
  dplyr::tally(name = "n") %>%
  dplyr::arrange(investloan_type_m8)

tb_roster_bl_informal <- tstm_roster_invest2loan2bridge %>%
  dplyr::group_by(bl_bridge_informal) %>%
  dplyr::tally(name = "n")

tb_roster_m8_informal <- tstm_roster_with_types %>%
  dplyr::group_by(investloan_type_m8, bl_bridge_informal) %>%
  dplyr::tally(name = "n") %>%
  dplyr::arrange(investloan_type_m8, bl_bridge_informal)

ffv_render_table(
  tb_roster_ivars,
  caption = "Roster row counts by ivars",
  name = "tab_roster_ivars",
  bl_save = ls_save_res[["tab_roster_ivars"]]
)
Roster row counts by ivars
ivars n
agg_BS_1011 4876
agg_BS_1012 2929
agg_BS_1021 2644
ffv_render_table(
  tb_chars_m8,
  caption = "Bridge-chars investment counts by investloan_type_m8",
  name = "tab_chars_m8",
  bl_save = ls_save_res[["tab_chars_m8"]]
)
Bridge-chars investment counts by investloan_type_m8
investloan_type_m8 n
1-investment-no-loan 874
2-investment-loan-a 521
3-investment-loan-hook 212
4-brg-single-lender 116
5-brg-baac-vilfund-combo 101
6-brg-informal-quasifor-combo 7
7-brg-has-informal-bridge 660
8-brg-has-informal-in-bridge 499
ffv_render_table(
  tb_roster_bl_informal,
  caption = "Roster counts by bl_bridge_informal",
  name = "tab_roster_bl_informal",
  bl_save = ls_save_res[["tab_roster_bl_informal"]]
)
Roster counts by bl_bridge_informal
bl_bridge_informal n
FALSE 7105
TRUE 1766
NA 1578
ffv_render_table(
  tb_roster_m8_informal,
  caption = "Roster counts by investloan_type_m8 and bl_bridge_informal",
  name = "tab_roster_m8_informal",
  bl_save = ls_save_res[["tab_roster_m8_informal"]]
)
Roster counts by investloan_type_m8 and bl_bridge_informal
investloan_type_m8 bl_bridge_informal n
1-investment-no-loan NA 874
2-investment-loan-a FALSE 381
2-investment-loan-a TRUE 78
2-investment-loan-a NA 386
3-investment-loan-hook FALSE 447
3-investment-loan-hook TRUE 71
3-investment-loan-hook NA 38
4-brg-single-lender FALSE 188
5-brg-baac-vilfund-combo FALSE 301
5-brg-baac-vilfund-combo TRUE 1
5-brg-baac-vilfund-combo NA 6
6-brg-informal-quasifor-combo FALSE 17
6-brg-informal-quasifor-combo NA 3
7-brg-has-informal-bridge FALSE 3125
7-brg-has-informal-bridge TRUE 1616
7-brg-has-informal-bridge NA 162
8-brg-has-informal-in-bridge FALSE 2646
8-brg-has-informal-in-bridge NA 109

Session info

sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] kableExtra_1.4.0  glue_1.8.1        tidyr_1.3.2       dplyr_1.2.1      
#> [5] PrjThaiHFID_0.2.0
#> 
#> loaded via a namespace (and not attached):
#>  [1] utf8_1.2.6         generics_0.1.4     xml2_1.6.0         stringi_1.8.7     
#>  [5] hms_1.1.4          digest_0.6.39      magrittr_2.0.5     evaluate_1.0.5    
#>  [9] RColorBrewer_1.1-3 fastmap_1.2.0      rprojroot_2.1.1    jsonlite_2.0.0    
#> [13] purrr_1.2.2        viridisLite_0.4.3  scales_1.4.0       textshaping_1.0.5 
#> [17] cli_3.6.6          rlang_1.2.0        crayon_1.5.3       bit64_4.8.2       
#> [21] withr_3.0.3        yaml_2.3.12        otel_0.2.0         tools_4.6.1       
#> [25] parallel_4.6.1     tzdb_0.5.0         forcats_1.0.1      vctrs_0.7.3       
#> [29] R6_2.6.1           lifecycle_1.0.5    stringr_1.6.0      bit_4.6.0         
#> [33] vroom_1.7.1        pkgconfig_2.0.3    pillar_1.11.1      systemfonts_1.3.2 
#> [37] haven_2.5.5        xfun_0.59          tibble_3.3.1       tidyselect_1.2.1  
#> [41] rstudioapi_0.19.0  knitr_1.51         farver_2.1.2       htmltools_0.5.9   
#> [45] rmarkdown_2.31     svglite_2.2.2      readr_2.2.0        compiler_4.6.1