Skip to contents

HCbeta is the default estimator in hcinfer(). This vignette shows how to use it, inspect the stored quantities, and run small sensitivity checks with its method-specific arguments.

Run HCbeta

Fit an OLS model and request HCbeta explicitly. This is equivalent to the default hcinfer(fit) call.

library(hcinfer)

schools <- PublicSchools |>
  dplyr::mutate(
    income_scaled = income / 10000,
    income_scaled_sq = income_scaled^2
  )

fit <- lm(expenditure ~ income_scaled + income_scaled_sq, data = schools)
result <- hcinfer(fit, type = "hcbeta")

summary(result)
#> 
#> ── 🔎 HCbeta robust inference summary ──────────────────────────────────────────
#> 
#> ── 📐 Model ──
#> 
#> Formula: `expenditure ~ income_scaled + income_scaled_sq`
#> Observations: 50 | Parameters: 3 | Residual df: 47
#> 
#> ── 🥪 Robust covariance ──
#> 
#> Estimator: HCbeta
#> Confidence level: 95.0% | Normal critical value: 1.9600
#> Tests are two-sided normal Wald tests, one coefficient at a time.
#> Test results use alpha = 0.050.
#> 
#> ── 🎯 Leverage diagnostics ──
#> 
#> # A tibble: 6 × 2
#>   statistic value  
#>   <chr>     <chr>  
#> 1 minimum   0.02669
#> 2 q1        0.03106
#> 3 median    0.03912
#> 4 mean      0.06   
#> 5 q3        0.04962
#> 6 maximum   0.6508
#> Maximum leverage: observation 2 (index 2), value 0.6508
#> Average leverage: 0.0600
#> Concentration: 10.85 x average leverage
#> 
#> ── ⚖️ Robust weights ──
#> 
#> # A tibble: 6 × 2
#>   statistic value
#>   <chr>     <chr>
#> 1 minimum   1.156
#> 2 q1        1.167
#> 3 median    1.187
#> 4 mean      1.276
#> 5 q3        1.212
#> 6 maximum   4.581
#> Maximum weight: observation 2 (index 2), value 4.5807
#> Median weight: 1.1869
#> Concentration: 3.86 x median weight
#> 
#> ── ⚙️ Method parameters ──
#> 
#> # A tibble: 12 × 3
#>    parameter value    role              
#>    <chr>     <chr>    <chr>             
#>  1 c1        7        method constant   
#>  2 c2        0.75     method constant   
#>  3 lower     0.01     method constant   
#>  4 upper     0.99     method constant   
#>  5 mu_hat    0.94     estimated quantity
#>  6 s2_w      0.008504 estimated quantity
#>  7 phi_hat   5.632    estimated quantity
#>  8 a_hat     5.294    estimated quantity
#>  9 b_hat     0.3379   estimated quantity
#> 10 zeta      0.5      estimated quantity
#> 11 a_tilde   3.147    estimated quantity
#> 12 b_tilde   0.669    estimated quantity
#> 
#> ── 🔎 Coefficient tests ──
#> 
#> # A tibble: 3 × 9
#>   term             estimate robust_se z       p_value alpha test_result        
#>   <chr>            <chr>    <chr>     <chr>   <chr>   <chr> <chr>              
#> 1 (Intercept)      832.9    850.7     0.9791  0.328   0.050 ✅ do not reject H0
#> 2 income_scaled    -1834    2309      -0.7945 0.427   0.050 ✅ do not reject H0
#> 3 income_scaled_sq 1587     1547      1.026   0.305   0.050 ✅ do not reject H0
#>   ci             ci_relation  
#>   <chr>          <chr>        
#> 1 [-834.3, 2500] includes null
#> 2 [-6359, 2691]  includes null
#> 3 [-1446, 4620]  includes null
#> 
#> ── 📏 Confidence intervals ──
#> 
#> # A tibble: 3 × 4
#>   term             null_value interval       interpretation
#>   <chr>            <chr>      <chr>          <chr>         
#> 1 (Intercept)      0          [-834.3, 2500] includes null 
#> 2 income_scaled    0          [-6359, 2691]  includes null 
#> 3 income_scaled_sq 0          [-1446, 4620]  includes null
#> 💡 test_result is based on p_value < alpha. Do not reject H0 does not mean that
#> H0 is true.

Extract coefficient test results

Use tests() to extract the coefficient-level Wald results as a tibble.

tests(result)
#> # A tibble: 3 × 8
#>   term             estimate null_value std_error z_value p_value alpha reject
#>   <chr>               <dbl>      <dbl>     <dbl>   <dbl>   <dbl> <dbl> <lgl> 
#> 1 (Intercept)          833.          0      851.   0.979   0.328  0.05 FALSE 
#> 2 income_scaled      -1834.          0     2309.  -0.794   0.427  0.05 FALSE 
#> 3 income_scaled_sq    1587.          0     1547.   1.03    0.305  0.05 FALSE

For example, extract the robust standard error and p-value for the quadratic income term.

dplyr::select(tests(result, parm = "income_scaled_sq"), term, std_error, p_value)
#> # A tibble: 1 × 3
#>   term             std_error p_value
#>   <chr>                <dbl>   <dbl>
#> 1 income_scaled_sq     1547.   0.305

Inspect HCbeta parameters

HCbeta stores both user-facing constants and estimated quantities in method_params.

result$method_params
#> $c1
#> [1] 7
#> 
#> $c2
#> [1] 0.75
#> 
#> $lower
#> [1] 0.01
#> 
#> $upper
#> [1] 0.99
#> 
#> $mu_hat
#> [1] 0.94
#> 
#> $s2_w
#> [1] 0.008503804
#> 
#> $phi_hat
#> [1] 5.632326
#> 
#> $a_hat
#> [1] 5.294386
#> 
#> $b_hat
#> [1] 0.3379395
#> 
#> $zeta
#> [1] 0.5
#> 
#> $a_tilde
#> [1] 3.147193
#> 
#> $b_tilde
#> [1] 0.6689698

The most useful entries for routine inspection are:

Entry Meaning
c1, c2 Constants controlling the HCbeta exponent.
lower, upper Truncation limits for leverage complements.
a_tilde, b_tilde Adjusted Beta shape parameters.
zeta Shrinkage weight used in the Beta parameter adjustment.

These values are diagnostics. In routine use, the defaults should usually be left unchanged.

Inspect leverage and weights

HCbeta, like the other estimators, stores leverage values and robust weights. This table shows the observations with the largest leverages.

diagnostics <- tibble::tibble(
  observation = result$observation,
  leverage = result$leverage,
  weight = result$weights,
  residual = result$residuals
) |>
  dplyr::mutate(
    state = schools$state[as.integer(observation)],
    .before = leverage
  )

diagnostics |>
  dplyr::arrange(dplyr::desc(leverage)) |>
  dplyr::slice_head(n = 5)
#> # A tibble: 5 × 5
#>   observation state          leverage weight residual
#>   <chr>       <chr>             <dbl>  <dbl>    <dbl>
#> 1 2           Alaska           0.651    4.58   110.  
#> 2 48          Washington DC    0.208    1.61  -161.  
#> 3 24          Mississippi      0.200    1.59   -44.0 
#> 4 4           Arkansas         0.0887   1.30   -30.5 
#> 5 40          South Carolina   0.0794   1.28     8.64

You can also sort by robust weight.

diagnostics |>
  dplyr::arrange(dplyr::desc(weight)) |>
  dplyr::slice_head(n = 5)
#> # A tibble: 5 × 5
#>   observation state          leverage weight residual
#>   <chr>       <chr>             <dbl>  <dbl>    <dbl>
#> 1 2           Alaska           0.651    4.58   110.  
#> 2 48          Washington DC    0.208    1.61  -161.  
#> 3 24          Mississippi      0.200    1.59   -44.0 
#> 4 4           Arkansas         0.0887   1.30   -30.5 
#> 5 40          South Carolina   0.0794   1.28     8.64

The covariance object can be plotted directly to display adjustment factors against leverages.

plot(vcov_hc(fit, type = "hcbeta"))

Scatterplot of HCbeta adjustment factors against leverage values for the public-schools model.

Use the covariance-only interface

Use vcov_hc() when you only need the HCbeta covariance matrix and diagnostics.

hcbeta_cov <- vcov_hc(fit, type = "hcbeta")
hcbeta_cov
#> 
#> ── 🥪 HCbeta robust covariance ─────────────────────────────────────────────────
#> 📐 Model: `expenditure ~ income_scaled + income_scaled_sq`
#> Dimension: 3 x 3
#> Observations: 50
#> Parameters: 3
#> 🎯 Maximum leverage: 0.6508
#> ⚖️ Maximum robust weight: 4.5807
#> Use `vcov()` to extract the stored covariance matrix.
vcov(hcbeta_cov)
#>                  (Intercept) income_scaled income_scaled_sq
#> (Intercept)         723617.6      -1962262          1312195
#> income_scaled     -1962262.2       5329884         -3569755
#> income_scaled_sq   1312195.3      -3569755          2394627

The covariance object stores the same method parameters and diagnostics.

hcbeta_cov$method_params
#> $c1
#> [1] 7
#> 
#> $c2
#> [1] 0.75
#> 
#> $lower
#> [1] 0.01
#> 
#> $upper
#> [1] 0.99
#> 
#> $mu_hat
#> [1] 0.94
#> 
#> $s2_w
#> [1] 0.008503804
#> 
#> $phi_hat
#> [1] 5.632326
#> 
#> $a_hat
#> [1] 5.294386
#> 
#> $b_hat
#> [1] 0.3379395
#> 
#> $zeta
#> [1] 0.5
#> 
#> $a_tilde
#> [1] 3.147193
#> 
#> $b_tilde
#> [1] 0.6689698
summary(hcbeta_cov)
#> 
#> ── 🥪 HCbeta robust covariance summary ─────────────────────────────────────────
#> 
#> ── 📐 Model ──
#> 
#> Formula: `expenditure ~ income_scaled + income_scaled_sq`
#> Observations: 50 | Parameters: 3 | Residual df: 47
#> 
#> ── 🎯 Leverage diagnostics ──
#> 
#> # A tibble: 6 × 2
#>   statistic value  
#>   <chr>     <chr>  
#> 1 minimum   0.02669
#> 2 q1        0.03106
#> 3 median    0.03912
#> 4 mean      0.06   
#> 5 q3        0.04962
#> 6 maximum   0.6508
#> Maximum leverage: observation 2 (index 2), value 0.6508
#> Average leverage: 0.0600
#> Concentration: 10.85 x average leverage
#> 
#> ── ⚖️ Robust weights ──
#> 
#> # A tibble: 6 × 2
#>   statistic value
#>   <chr>     <chr>
#> 1 minimum   1.156
#> 2 q1        1.167
#> 3 median    1.187
#> 4 mean      1.276
#> 5 q3        1.212
#> 6 maximum   4.581
#> Maximum weight: observation 2 (index 2), value 4.5807
#> Median weight: 1.1869
#> Concentration: 3.86 x median weight
#> 
#> ── ⚙️ Method parameters ──
#> 
#> # A tibble: 12 × 3
#>    parameter value    role              
#>    <chr>     <chr>    <chr>             
#>  1 c1        7        method constant   
#>  2 c2        0.75     method constant   
#>  3 lower     0.01     method constant   
#>  4 upper     0.99     method constant   
#>  5 mu_hat    0.94     estimated quantity
#>  6 s2_w      0.008504 estimated quantity
#>  7 phi_hat   5.632    estimated quantity
#>  8 a_hat     5.294    estimated quantity
#>  9 b_hat     0.3379   estimated quantity
#> 10 zeta      0.5      estimated quantity
#> 11 a_tilde   3.147    estimated quantity
#> 12 b_tilde   0.669    estimated quantity

Run a sensitivity check

The HCbeta constants can be passed through .... A sensitivity check compares the default result with a small set of alternative settings.

sensitivity_results <- list(
  default = hcinfer(fit, type = "hcbeta"),
  c1_5 = hcinfer(fit, type = "hcbeta", c1 = 5),
  tighter_truncation = hcinfer(fit, type = "hcbeta", lower = 0.02, upper = 0.98)
)

sensitivity <- purrr::imap(sensitivity_results, \(res, setting) {
  row <- tests(res, parm = "income_scaled_sq")
  ci <- confint(res, parm = "income_scaled_sq")

  tibble::tibble(
    setting = setting,
    std_error = row$std_error,
    p_value = row$p_value,
    conf_low = ci$conf_low,
    conf_high = ci$conf_high,
    max_weight = max(res$weights)
  )
})
sensitivity <- dplyr::bind_rows(sensitivity)
sensitivity
#> # A tibble: 3 × 6
#>   setting            std_error p_value conf_low conf_high max_weight
#>   <chr>                  <dbl>   <dbl>    <dbl>     <dbl>      <dbl>
#> 1 default                1547.   0.305   -1446.     4620.       4.58
#> 2 c1_5                   1292.   0.219    -945.     4119.       3.02
#> 3 tighter_truncation     1547.   0.305   -1446.     4620.       4.58

This table helps identify whether the reported inference is sensitive to small changes in HCbeta tuning constants. The defaults remain the recommended starting point.

Compare HCbeta with one classical estimator

For a quick comparison, put HCbeta next to a classical estimator such as HC3.

hc3 <- hcinfer(fit, type = "hc3")

extract_income <- function(res, method) {
  row <- tests(res, parm = "income_scaled_sq")
  ci <- confint(res, parm = "income_scaled_sq")
  tibble::tibble(
    method = method,
    estimate = row$estimate,
    std_error = row$std_error,
    p_value = row$p_value,
    conf_low = ci$conf_low,
    conf_high = ci$conf_high
  )
}

compare_hc3 <- dplyr::bind_rows(
  extract_income(result, "hcbeta"),
  extract_income(hc3, "hc3")
)
compare_hc3
#> # A tibble: 2 × 6
#>   method estimate std_error p_value conf_low conf_high
#>   <chr>     <dbl>     <dbl>   <dbl>    <dbl>     <dbl>
#> 1 hcbeta    1587.     1547.   0.305   -1446.     4620.
#> 2 hc3       1587.     1995.   0.426   -2324.     5498.

Practical workflow

For routine analyses:

  1. Fit an OLS model with lm().
  2. Run hcinfer(fit) or hcinfer(fit, type = "hcbeta").
  3. Use summary(result) for readable output.
  4. Use tests(result), confint(result), coef(result), and vcov(result) for extracted values.
  5. Inspect result$weights, result$leverage, and result$method_params when diagnostics are needed.

Use vignette("hcinfer-comparison") when you want to compare HCbeta with other estimators implemented in the package.