MatchIt Question - How to Access Distance Between Matched Units with Mahalanobis Dsitance

Viewed 99

Is it possible to get the distances between matched units using the MatchIt::matchit() function?

Here is a reproducible example. I can see the distances when I use distance = "glm" but not with distance = "mahalanobis".

If you have a recommendation for a different package I am also happy to try that. I am only looking to match to another unit and not, for example, to calculate an ATT. Thank you!

# Run nearest neighbor with "mahalanobis" distance
res_matchitmahalanobis <- matchit(
  data = df_example,
  formula = treat ~ age + male,
  method = "nearest", 
  distance = "mahalanobis",
  exact = ~ male,
  replace = TRUE 
)

# Note: No `distance` column
get_matches(res_matchitmahalanobis)

# Note: `distance` element is missing
res_matchitmahalanobis$distance


# Run nearest neighbor with "glm" distance
res_glm <- matchit(
  data = df_example,
  formula = treat ~ age + male,
  method = "nearest", 
  distance = "glm",
  exact = ~ male,
  replace = TRUE 
)

# Note: There is now a `distance` column
get_matches(res_glm)

# Note: `distance` element is now present
res_glm$distance
2 Answers

It looks like they don't give you the distances if you use Mahalanobis. They calculate the results using that metric, though.

If you'd like to use Mahalanobis, you can use it along with another metric (like 'glm'). Alternatively, you can collect the distances separately.

I ran the matchit function with both the glm and Mahalanobis distances. Then I collected the Mahalonbis distances separately. (Really, I wanted to see if the distances were Malahanobis or glm...but as expected, they were glm.)

To collect the Mahalanobis distances (even with factors and no extra work) you can use the package assertr and the function maha_dist. The base R function requires you to manually convert factors to values.

library(MatchIt)
library(tidyverse)
library(assertr)

data("lalonde")

m.out2 <- matchit(treat ~ age + educ + race, data = lalonde,
                  distance = "glm", method = "nearest",
                  exact = ~educ, replace = T,
                  mahvars = ~age + educ + race)
summary(m.out2)

la2 <- lalonde %>% select(age, educ, race)
head(la2) # as expected
# collect distances
vals <- maha_dist(la2, robust = T) # robust uses covariance matrix

# visualize it
plot(density(vals, bw = .5),
     main = "Mahal Sq Distances")
qqplot(qchisq(ppoints(100), df = 3), vals,
       main = "QQ Plot Mahal Sq Distances")
abline(0, 1, "gray")
# definately outside of the 'normal'

As @Kat pointed out, matchit() does not return this value. It would be inappropriate to have this in the distance column; see here for why. The distance output in the matchit object is a misnomer; it refers to the propensity score, and each unit has one distance value. This is why it shows up with distance = "glm"; you are estimating a propensity score, which is then used to compute the distance between units. No methods in matchit() will actually return the distance between two paired units.

It would take a fair bit of work to extract this information. matchit() does not provide the Mahalanobis distance matrix used in the matching (because this would be way too big for big datasets!). However, you can compute a distance matrix outside matchit(), supply it to the distance argument, and then access the distance between units by extracting those distances from the matrix after doing the pairing. You can compute the Mahalanobis distance using, e.g., optmatch::match_on(), though it is not guaranteed to be identical to the Mahalanobis distance matchit() uses internally. Here is how you would do this:

data("lalonde", package = "MatchIt")

#Create distance matrix
dist <- optmatch::match_on(treat ~ age + educ + race, data = lalonde,
                           method = "mahalanobis")

#Do matching on distance matrix
m <- MatchIt::matchit(treat ~ age + educ + race, data = lalonde,
                      distance = dist, exact = ~married,
                      replace = TRUE)

#Extract matched pairs
mm <- m$match.matrix

#Create data frame of pairs and distance
d <- data.frame(treated = rownames(mm), control = mm[,1],
                distance = dist[cbind(rownames(mm), mm[,1])])
head(d)
#>      treated control  distance
#> NSW1    NSW1 PSID368 0.3100525
#> NSW2    NSW2 PSID341 0.2067017
#> NSW3    NSW3  PSID99 0.2067017
#> NSW4    NSW4 PSID189 0.3900789
#> NSW5    NSW5 PSID400 0.4134033
#> NSW6    NSW6 PSID253 0.1033508

dist["NSW1", "PSID368"]
#> [1] 0.3100525

Created on 2022-02-24 by the reprex package (v2.0.1)

This works with replace = FALSE as well but would take a bit more work when k:1 matching or full matching. Although you are not matching using matchit()'s Mahalanobis distance, the distances produced in the output above do correspond to the distances used to pair.

Related