---
title: "Analyzing the Survey of Consumer Finances"
author:
  - name: "Joseph N. Cohen"
    dept: "Department of Sociology"
    inst: "Queens College, City University of New York"
    email: "joseph.cohen@qc.cuny.edu"
    web: "jncohen.commons.gc.cuny.edu"
date: "October 18, 2026"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Analyzing the Survey of Consumer Finances}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  message = FALSE,
  warning = FALSE
)
library(scf)
```

# Introduction

The Survey of Consumer Finances (SCF) is a triennial survey of U.S. household finances conducted by the Federal Reserve Board. It is among the most detailed data sources on U.S. household wealth, income, and financial behavior. Valid estimation from the SCF requires handling two design features:

1. Survey design: the SCF uses a dual-frame sample with 999 bootstrap replicate weights, which are used to estimate sampling variance.
2. Multiple imputation: the SCF fills in missing answers five times, giving five implicates. Each is a complete version of the data with different filled-in values.

The `scf` package handles both. It wraps the five implicates in replicate-weighted `survey::svrepdesign()` objects, runs each estimate in every implicate, and pools the results. See `?scf_variance` for how standard errors are computed.

Throughout, the unit of analysis is the SCF's primary economic unit: the person or couple at the economic center of the household and everyone financially dependent on them. The Fed calls this unit a "family"; this package calls it a household.

This vignette demonstrates the core workflow. It runs on the package's mock data, a small sample of the 2022 survey, so the numbers shown are not estimates for the population; the same code runs on the full data. For methodological background, see the package paper listed under Learn More.


# Workflow

## 1. Downloading and Loading the Data

Download SCF data and load it into a multiply-imputed survey object using `scf_download()` and `scf_load()`.

```{r, include = FALSE}
vtd <- file.path(tempdir(), "scf_vig")
dir.create(vtd, showWarnings = FALSE)
src <- system.file("extdata", "scf2022_mock_raw.rds", package = "scf")
file.copy(src, file.path(vtd, "scf2022.rds"), overwrite = TRUE)
scf2022 <- scf_load(2022, data_directory = vtd)
```

```{r, eval = FALSE}
scf_download(2022)
scf2022 <- scf_load(2022)
```

The result is a `scf_mi_survey` object containing five `svyrep.design` objects, one per implicate, and survey-year metadata. The package covers every wave from 1989 to 2025; `scf_download(2025)` fetches the latest. Each object records the dollar year of the Fed's summary variables (see Dollar values below):

```{r}
scf2022
scf2022$dollar_year
```

## 2. Creating and Transforming Variables

`scf_update()` adds or modifies variables uniformly across all five implicates. Bottom-code skewed variables before logging to avoid `log(0)`.

```{r}
scf2022 <- scf_update(scf2022,
  senior       = age >= 65,
  female       = factor(hhsex, levels = 1:2, labels = c("Male", "Female")),
  rich         = networth > 1e6,
  networth     = ifelse(networth > 1, networth, 1),
  log_networth = log(networth),
  income       = ifelse(income > 1, income, 1),
  log_income   = log(income)
)
```

Use `names(scf2022$mi_design[[1]]$variables)` to inspect available variables.

When a transformation depends on the distribution within each implicate, such as ranks, percentile flags, or group z-scores, use `scf_update_by_implicate()` instead:

```{r, eval = FALSE}
scf2022 <- scf_update_by_implicate(scf2022, function(df) {
  df$wealth_rank <- rank(df$networth) / nrow(df)
  df
})
```

`scf_subset()` keeps the households that meet a condition, in every implicate. Estimates from a subset describe that group alone; to compare groups, use `by` in the estimating functions instead.

```{r}
seniors <- scf_subset(scf2022, age >= 65)
scf_median(seniors, ~networth)
```

### Dollar values

Summary variables made by the Fed, such as `income`, `networth`, and `asset`, are in the dollars of one year in every survey year: 2025 for files downloaded since October 2026, 2022 before that. `scf_load()` records this as `dollar_year`. Raw survey variables, named `x` followed by a number, are in dollars of the survey year. Before comparing a raw variable across years, or combining it with summary variables, convert it with `scf_real()`. List income items, which are reported for the year before the survey, in `income`:

```{r, eval = FALSE}
scf2016 <- scf_real(scf2016, ~x5702 + x3915, income = ~x5702)
```

To report a result in dollars of the survey year, as in the Fed's nominal-dollar tables, use `scf_nominal()` on the result. Both functions stop with an error if given the wrong kind of variable, unless `force = TRUE`.

## 3. Univariate and Bivariate Distributions

`scf_mean()`, `scf_median()`, and `scf_percentile()` return pooled population estimates with standard errors. The `by` argument produces group-level estimates.

```{r}
scf_mean(scf2022, ~networth, by = ~senior)
scf_median(scf2022, ~income, by = ~female)
scf_percentile(scf2022, ~networth, q = 0.9)
scf_percentile(scf2022, ~networth, q = 0.75, by = ~female)
```

`scf_pctile_sum()` groups households by their position in a variable's distribution and summarizes each group, as the Fed's tables do by income and net worth percentile.

```{r}
scf_pctile_sum(scf2022, ~networth, probs = c(0, 0.5, 0.9, 1),
               labels = c("bottom50", "50-90", "top10"),
               method = "stack", stat = "mean")
```

Discrete distributions are summarized with `scf_freq()` and cross-tabulated with `scf_xtab()`.

```{r}
scf_freq(scf2022, ~senior)
scf_xtab(scf2022, ~senior, ~female, scale = "col")
```

`scf_ratio()` estimates the ratio of two totals, which is how the Fed computes shares and ratios such as wealth to income. `scf_corr()` gives the weighted correlation of two variables.

```{r}
scf_ratio(scf2022, ~networth, ~income, by = ~senior)
scf_corr(scf2022, ~log_income, ~log_networth)
```

## 4. Hypothesis Tests

`scf_ttest()` and `scf_prop_test()` pool results across implicates and use Rubin's degrees of freedom.

```{r}
scf_ttest(scf2022, ~networth, mu = 250000)
scf_ttest(scf2022, ~networth, group = ~senior)
scf_prop_test(scf2022, ~senior, p = 0.25)
scf_prop_test(scf2022, ~rich, ~female)
```

## 5. Regression Modeling

`scf_ols()`, `scf_glm()`, and `scf_logit()` fit replicate-weighted models to each implicate and pool coefficients via Rubin's Rules.

```{r}
scf_ols(scf2022, log_networth ~ age + log_income)
scf_logit(scf2022, rich ~ age + log_income, odds = TRUE)
scf_glm(scf2022, own ~ age, family = binomial())
```

`scf_regtable()` formats results from one or more models for publication:

```{r}
m1 <- scf_ols(scf2022, log_networth ~ age)
m2 <- scf_ols(scf2022, log_networth ~ age + log_income)
scf_regtable(m1, m2, model.names = c("Model 1", "Model 2"), digits = 3)
```

## 6. Quantile Regression

OLS models the conditional mean, which is sensitive to outliers and skew. Wealth and income distributions in the SCF are highly right-skewed: a small number of high-wealth households can dominate mean estimates. Quantile regression estimates the conditional quantile of the outcome at a user-specified probability `tau`. Estimating several quantiles shows how a predictor's effect differs across the distribution.

`scf_quantreg()` fits `quantreg::rq()` with SCF final sampling weights to each implicate, then pools coefficients and variance-covariance matrices across implicates via `scf_MIcombine()`.

```{r}
# Median regression
m_med <- scf_quantreg(scf2022, log_networth ~ age + senior, tau = 0.50)
print(m_med)
```

```{r}
# 75th percentile, with fast standard errors (see below)
m_75 <- scf_quantreg(scf2022, log_networth ~ age + senior, tau = 0.75,
                     se = "nid")
summary(m_75)
```

The `se` argument controls within-implicate variance estimation. All methods feed a covariance matrix into `scf_MIcombine()`, so between-implicate (imputation) variance is always incorporated.

- `"replicate"` (default): re-fits the model with each of the SCF's 999 replicate weights in each implicate, via `survey::withReplicates()`. The variance is the spread of the replicate coefficients around their mean, as the Fed computes it. This is the only method that uses the SCF's sample design. It refits the model about 5,000 times, so it can take several minutes on the full data.
- `"nid"`: non-iid sandwich estimator. Allows the conditional sparsity (the density of the error distribution at the quantile) to vary across observations.
- `"iid"`: the Koenker-Bassett (1978) covariance formula, which assumes the error distribution is the same for all observations. Fastest option.
- `"ker"`: kernel density estimate of the conditional sparsity.
- `"boot"`: pairs bootstrap.

The last four come from `quantreg::summary.rq()` and treat the weighted sample as a simple random sample, so they ignore the SCF's design. They are useful for quick exploration; report results with `"replicate"`.

```{r, eval = FALSE}
# Fast standard errors while exploring
m_quick <- scf_quantreg(scf2022, log_networth ~ age + senior,
                        tau = 0.50, se = "nid")
```

A note on design: the `survey` package provides `svyquantile()` for estimating marginal quantiles of a single variable, but it has no function for regression quantiles (conditional quantiles given covariates). `scf_quantreg()` therefore uses `quantreg::rq()` for point estimation, with `survey::withReplicates()` providing the design-based variance wrapper for the `"replicate"` path.

Koenker and Bassett (1978) established the asymptotic theory for the unweighted, i.i.d. case. Applying `rq()` with survey sampling weights extends the estimator to probability-weighted samples, which is standard empirical practice; the `"iid"` and `"nid"` standard error formulas extend correspondingly to the weighted design matrix `X'WX`. The `"replicate"` option sidesteps this by using the SCF's own replication scheme to estimate variance directly, without relying on distributional assumptions about the errors.

Results are compatible with `scf_regtable()`:

```{r}
scf_regtable(m_med, m_75,
             model.names = c("Median", "75th Pct"),
             digits = 3)
```

To study how associations vary across the outcome distribution, estimate the same model at multiple quantiles and compare:

```{r, eval = FALSE}
taus <- c(0.25, 0.50, 0.75, 0.90)
models <- lapply(taus, function(t) {
  scf_quantreg(scf2022, log_networth ~ age + senior, tau = t)
})
names(models) <- paste0("tau=", taus)
do.call(scf_regtable, c(models, list(digits = 3)))
```

## 7. Visualization

`scf` plotting functions account for survey weights and multiply-imputed data.

```{r}
scf_plot_dbar(scf2022, ~senior)
scf_plot_bbar(scf2022, ~female, ~rich, scale = "percent")
scf_plot_cbar(scf2022, ~networth, ~edcl, stat = "median")
scf_plot_dist(scf2022, ~age, bins = 10)
scf_plot_hist(scf2022, ~networth)
scf_plot_smooth(scf2022, ~age)
scf_plot_hex(scf2022, ~income, ~networth)
```

## 8. Inspecting Implicates

Use `scf_implicates()` to retrieve implicate-level estimates from any `scf_*` result for sensitivity analysis or custom pooling.

```{r}
freq_table <- scf_freq(scf2022, ~rich)
scf_implicates(freq_table, long = TRUE)
```

Implicate-level regression model objects are stored directly in the result:

```{r, eval = FALSE}
m <- scf_ols(scf2022, log_networth ~ age)
summary(m$imps[[1]])   # first implicate svyglm object
```

The usual model methods work on the pooled result: `coef()`, `SE()`, `vcov()`, `confint()`, `AIC()`, `predict()`, and `residuals()`. `SE()` also returns the standard errors of descriptive results.

```{r}
SE(m2)
confint(m2)
SE(scf_mean(scf2022, ~networth, by = ~senior))
```


# Learn More

- Cohen JN. *Analyzing the Survey of Consumer Finances with scf: Methodology, User Guide, and Package Validation*. Working paper, CUNY Queens College; 2026. <https://academicworks.cuny.edu/qc_pubs/695/>
- Federal Reserve Board. *Survey of Consumer Finances*. <https://www.federalreserve.gov/econres/aboutscf.htm>
- Koenker R, Bassett G. Regression quantiles. *Econometrica*. 1978;46(1):33-50.
- Rubin DB. *Multiple Imputation for Nonresponse in Surveys*. Wiley; 1987.

```{r, include = FALSE}
try(unlink(file.path(vtd, "scf2022.rds"), force = TRUE), silent = TRUE)
try(unlink(vtd, recursive = TRUE, force = TRUE), silent = TRUE)
rm(vtd)
```
