TA1: Workflow in empirical research

Expected Outcome

  • How to think about the empirical aspect of research?
  • How to organize empirical workflow?
  • What is the most appropriate coding language for my project?
  • What are the descriptives telling me?
  • Is a regression the best idea?
  • How do the results change when introducing fixed effects?

Download TA1 Code

How to think about the empirical aspect of research

Empirical research is not “running regressions.” It is building a credible bridge from variation in the world to a precise claim, and making that bridge strong enough to survive a skeptical referee.

1) Start with an estimand, not a specification

Before code, write down the object you want to study:

  • Causal estimand: the effect of (X) on (Y) (under a clearly stated intervention).
  • Descriptive estimand: how (X) and (Y) co-move (useful, but not causal).
  • Predictive estimand: forecasting (Y) (rarely the main goal in this course).

A regression is one possible estimator. The estimand is the research.

2) Identification is a story about variation

The core question is always:

Which variation identifies your claim?

In practice:

  • What moves your “treatment” variable?
  • Why is that movement plausibly unrelated to the outcome except through your channel?
  • What would break this argument?

If you cannot answer these three, you do not have an empirical design yet.

3) Assumptions are the price you pay for inference

Every design buys inference by paying assumptions. Your job is to:

  • minimize the assumptions,
  • state them clearly,
  • test their implications wherever possible.

For example, fixed effects do not “solve endogeneity.” They change the comparison!

4) Data construction is part of the design

In corporate finance, many results are driven by measurement and sample choices:

  • definitions (capital, liquidity, lending),
  • winsorization and missing data rules,
  • the sample pipeline (who enters, who exits, mergers, breakpoints).

A credible workflow produces:

  • a transparent construction pipeline,
  • a variable dictionary,
  • a one-command replication script.

5) Estimation and inference are engineering

Once the design is clear, implementation must be correct:

  • fixed effects reflect the comparison you want,
  • clustering reflects the correlation structure of shocks,
  • robustness checks target specific threats, not a grab bag of controls.

Code is not decoration: it is the proof that you estimated what you claimed.

6) Interpretation is economic, not statistical

“Significant” is not the endpoint.

Translate coefficients into:

  • units that matter (percent, bps, dollars),
  • plausible mechanisms,
  • magnitudes relative to benchmarks.

If you cannot explain a coefficient in one sentence, the design is not finished.

Referee-mode checklist (use this every time)

You are ready to show results only if you can answer:

  1. What is the claim / estimand?
  2. What variation identifies it?
  3. Which assumption does the heavy lifting?
  4. What diagnostic would convince you that you are wrong?

How to organize empirical workflow

A good empirical workflow is not “clean data → run regressions.” It is a repeatable pipeline that turns raw inputs into claims, with enough structure that (i) you can debug, (ii) you can extend, and (iii) someone else can replicate.

The principle: one button replication

Your project should have a single entry point:

  • run_all.R rebuilds everything from scratch (data → tables/figures → site).
  • If run_all.R breaks, the project is not yet research-grade.

The pipeline (design → data → estimation → reporting)

Organize scripts as a sequence:

  1. 00_setup.R
    load packages, set paths, set seed, define global options

  2. 01_build_data.R
    read raw inputs, clean, construct variables, write data_clean/master_panel.csv

  3. 02_descriptives.R
    sample checks, missingness, summary tables, baseline plots

  4. 03_estimation.R
    main specifications, inference choices (FE, clustering), robustness targeted to threats

  5. 04_outputs.R
    export model tables, export figures, save objects needed by the site

  6. 05_render.R
    render the website / report

This is exactly how you keep analysis modular and debuggable.

“Logs” you should always produce

Every serious empirical project prints (and saves) the answers to:

  • What is the sample size at each filter step?
  • How many unique units (banks / firms) and time periods?
  • Are there duplicates in the panel key?
  • What is missing, and is it systematic?
  • What is the distribution of key variables (levels and changes)?

If you do not log these, you will waste days later.

Version control is part of the workflow

A clean Git routine:

  • commit after each meaningful, working change
  • commit messages describe what changed and why
  • keep large derived files out of Git (use .gitignore for data_clean/ if needed)
  • tag stable versions (e.g., v1_ta1_complete) so you can always revert

Dependency management (avoid “it works on my machine”)

Do not install packages inside scripts.

  • use renv (or equivalent) to lock package versions
  • record session info at the end of runs (sessionInfo())

The TA1 standard

In TA1, the workflow goal is:

Build a synthetic master panel once, then repeatedly reuse it for descriptives and fixed-effects models.

By TA2–TA6, you should still be using the same pipeline — you just add modules and extend the master dataset.

What is the most appropriate coding language for my project?

The “best” language is the one that minimizes total research cost: time to implement + time to debug + time to replicate + credibility with your audience.

Quick positioning

  • Stata: best for standard applied micro workflows and fast, conventional regression work; weakest for modern software engineering and large-scale data pipelines.
  • R: best all-around for econometrics + visualization + reproducible reporting; strong for high-dimensional fixed effects and modern causal packages.
  • Python: best for data engineering, web scraping, APIs, text/NLP, and scalable pipelines; econometrics is good but reporting and some “applied micro conventions” can be more work than in Stata/R.
  • Gretl: great teaching tool and lightweight econometrics; less common in top-tier research workflows and ecosystem is smaller.
  • Matlab: excellent for numerical methods, simulations, structural models, optimization; not the default for reduced-form empirical pipelines (data cleaning + causal inference + reporting).

What each one is “for” in practice

Stata

  • you want the quickest path from cleaned panel → conventional tables,
  • you are following a classic applied micro/corp-fin template,
  • your coauthors/advisors are Stata-native.

Watch-outs: less natural for reproducible research engineering (project pipelines, dependency mgmt), more friction for scraping, APIs, and complex text processing.

R

  • you want research-grade reporting (Quarto/Rmd) + clean figures + tables,
  • you need high-dimensional fixed effects and modern causal inference tooling,
  • you want to teach / enforce a transparent workflow (scripts + outputs + site).

Watch-outs:

  • some data engineering tasks are easier in Python (scraping, messy parsing),
  • performance can require care for very large datasets.

Python

  • you need web scraping, APIs, NLP, and messy data ingestion,
  • you want a scalable pipeline mindset (testing, automation),
  • you’re building datasets more than running canned regressions.

Watch-outs:

  • for “applied micro style” regression tables and FE-heavy work, you may do more manual setup than in R/Stata,
  • the ecosystem is broader, so conventions vary more across labs.

Gretl

  • you want a simple environment for classic econometrics and teaching,
  • you need something lightweight and transparent for small projects.

Watch-outs:

  • much smaller ecosystem; less common in corp-fin research replication packages,
  • fewer cutting-edge methods implemented as quickly as R/Stata.

Matlab

  • you are doing structural estimation, numerical optimization, simulations, dynamic programming,
  • you need high-quality linear algebra tools and controlled numerical workflows.

Watch-outs:

  • not the easiest default for data cleaning + causal inference + reproducible reporting,
  • fewer “applied micro defaults” than Stata/R.

A good rule for this course

For Finance 4 empirical TA work, the lowest-friction, highest-credibility stack is:

  • R as the main analysis language (econometrics + reporting + figures)
  • Git for version control
  • Python as an add-on when we move to web scraping / text (later topics)

If you personally live in Stata, a perfectly fine alternative is:

  • Stata for estimation + core tables, plus
  • Python (or R) for scraping and data construction, and
  • still Git for version control.

The only “wrong” choice

Mixing languages without clear boundaries.

Good mixes:

  • Python → R (Python builds data, R estimates/reports)
  • Python → Stata (Python builds data, Stata estimates/tables)
  • SQL → (R/Stata/Python) (SQL builds analysis tables)

Bad mix:

  • switching languages mid-analysis for the same task (harder to replicate, harder to debug, harder to teach).

Time to Code!

We will use:

  • code/00_utils.R
  • code/01_build_teaching_data.R
  • code/02_ta1_fixed_effects.R

Data

TA1 uses a synthetic banking panel so everyone can run the full workflow locally without proprietary access, with:

  • one observation per bank-year,
  • balance-sheet variables such as assets, capital, liquidity, and lending growth,
  • threshold treatment based on bank size.

Core Idea

Fixed effects are not a magic solution to endogeneity. They answer a different question:

  • pooled OLS compares different banks to one another,
  • bank fixed effects compare a bank to itself over time,
  • year fixed effects purge common aggregate shocks.

Intuition

When we study lending, we usually do not observe pure credit supply. Loan growth reflects:

  • bank willingness to lend,
  • borrower demand for credit,
  • common macro conditions,
  • local shocks in the places where the bank operates.

Main script

fe_model <- fixest::feols(
  loan_growth ~ capital_ratio + liquidity_ratio + treated | bank_id + year,
  cluster = ~bank_id,
  data = bank_panel
)

Descriptive statistics

ta1_desc <- readr::read_csv("output/tables/ta1_descriptives.csv", show_col_types = FALSE) |>
  dplyr::mutate(dplyr::across(where(is.numeric), ~round(.x, 3)))

knitr::kable(ta1_desc, caption = "Selected descriptives from the master bank panel")
Selected descriptives from the master bank panel
variable mean sd p25 median p75
assets_bn 13.894 6.457 9.266 12.560 17.018
capital_ratio 0.114 0.015 0.104 0.114 0.125
chargeoff_rate 0.025 0.006 0.021 0.025 0.029
liquidity_ratio 0.176 0.035 0.155 0.175 0.196
loan_growth 0.061 0.076 0.024 0.070 0.112
roa 0.011 0.005 0.008 0.012 0.015
small_business_share 0.228 0.023 0.212 0.226 0.244

Main regression output

ta1_models <- readr::read_csv("output/tables/ta1_model_table.csv", show_col_types = FALSE) |>
  dplyr::filter(term != "(Intercept)") |>
  dplyr::select(model, term, estimate, std.error, statistic, p.value) |>
  dplyr::mutate(dplyr::across(c(estimate, std.error, statistic, p.value), ~round(.x, 3)))

knitr::kable(ta1_models, caption = "Pooled and fixed-effects estimates")
Pooled and fixed-effects estimates
model term estimate std.error statistic p.value
Pooled OLS capital_ratio 1.630 0.225 7.233 0.000
Pooled OLS liquidity_ratio 0.322 0.119 2.698 0.009
Pooled OLS treated -0.004 0.007 -0.569 0.571
Bank FE + Year FE capital_ratio 0.802 0.156 5.131 0.000
Bank FE + Year FE liquidity_ratio 0.145 0.130 1.116 0.268
Bank FE + Year FE treated 0.000 0.003 -0.125 0.901

Visual summary

TA1 loan growth trend