I am seeking help on using Matching with R on a particular data structure. I reproduce below the general idea how the data looks like.

I have a "pool of control" units that I want to re-used for each treated unit matching. The column id refers to the personal identifier, you can see that the controls are repeated for each treated unit. Only the treated unit changes.

Each treated unit "stratum" is denoted treated_id.

The data looks like this (in reality I have a huge dataset)

       id treated_id treated X1 X2          X3
1     bob        bob       1  0  0  0.91194397
2   peter        bob       0  1  1 -0.15348007
3    paul        bob       0  0  1 -1.57886169
4    mary        bob       0  1  1 -0.58267040
5  janice        bob       0  0  0 -0.03903861
6     jim        jim       1  0  0  1.30430649
7   peter        jim       0  1  1 -0.15348007
8    paul        jim       0  0  1 -1.57886169
9    mary        jim       0  1  1 -0.58267040
10  janice       jim       0  0  0 -0.03903861

One issue is that the matching does not recognise that each rows contain the same repeated controls.

One idea for this issue is to do the matching separately for each treated unit, which is also much less computationally intensive

For instance using matchit, I perform the matching for each treated case separately and then put the dataset together

# using map #
df %>% 
  split(.$treated_id) %>% 
  map(~ matchit(treated ~ X1 + X2 + X3, data = .), 
      exact = ~X1+X2, caliper = c(X3 = 1)) %>% 
  map(~ match.data(.)) %>% 
  bind_rows() %>% 
  mutate(model = "Model A")

I get something like this

    id treated_id treated X1 X2         X3     distance weights subclass   model
1   bob        bob       1  0  0  0.9119440 1.000000e+00       1        1 Model A
2 peter        bob       0  1  1 -0.1534801 5.898404e-11       1        1 Model A
3   jim        jim       1  0  0  1.3043065 1.000000e+00       1        1 Model A
4 peter        jim       0  0  0  1.1233024 1.250973e-10       1        1 Model A

Which basically output each treated and their closest controls.

But I still need to correct the fact that some controls will be re-used. What would be the best way to do it?

And if I do it the traditional way

matchit(treated ~ X1 + X2 + X3, data = df, exact = ~X1+X2, 
            caliper = c(X3 = 1))

How can I indicate to matching to account the control ids? Matching seems to take each row for a new id.

  • $\begingroup$ You could try the Matching package, though I don't know if it would do any better than the one you're using already. This package also requires the rgenoud package. $\endgroup$ Feb 18, 2022 at 15:43
  • $\begingroup$ Out of curiosity, what makes matching a good choice for you? $\endgroup$ Feb 19, 2022 at 23:13
  • $\begingroup$ Dear @FrankHarrell that is a fair question. The main reason is, believe it or not, simplicity, because my data are longitudinal and my outcome categorical. So working with a 1:1 matched dataset is the easiest way to compute the causal effects but if you have other ideas, please do let me know. If you are interested in this issue, I can also drop you a personal email. $\endgroup$
    – giac
    Feb 21, 2022 at 10:56
  • $\begingroup$ I assume the matching is done only on the baseline variables. 1:1 matching is not appropriate as this causes very good potential matches to be ignored, and matching in general lowers your sample size which lowers power and precision. More details are here. $\endgroup$ Feb 21, 2022 at 13:16
  • $\begingroup$ Well, when I checked K:1 matching and then using the appropriate weights the results were very similar. In my case, matching does not prune the dataset dramatically, 20 cases over 3000 cases. $\endgroup$
    – giac
    Feb 21, 2022 at 15:20

1 Answer 1


Following Noah's post Estimating Effects After Matching, we are in a situation of "After Pair Matching With Replacement".

It seems to me that I could simply correct the SE using clustering.

This gives me the following code

# Computing the matching using map #
match_df = df %>% 
  split(.$treated_id) %>% 
  map(~ matchit(treated ~ X1 + X2 + X3, data = .), 
      exact = ~X1+X2, caliper = c(X3 = 1)) %>% 
  map(~ match.data(.)) %>% 
  bind_rows() %>% 
  mutate(model = "Model A")

Computing a regression (no weights needed here)

fit1 = lm(Y ~ treated, data = match_df)

Correcting the SE

coeftest(fit1, vcov. = vcovHC)

Accounting for the repeated controls with a SE clustering

coeftest(fit1, vcov. = vcovCL, cluster = ~treated_pid)

That would be my take

# the data 
df = structure(list(id = structure(c(1L, 6L, 5L, 4L, 2L, 3L, 6L, 5L, 
4L, 2L), .Label = c("bob", "janice", "jim", "mary", "paul", "peter"
), class = "factor"), treated_id = structure(c(1L, 1L, 1L, 1L, 
1L, 2L, 2L, 2L, 2L, 2L), .Label = c("bob", "jim"), class = "factor"), 
    treated = c(1L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L), X1 = c(0L, 
    1L, 0L, 1L, 0L, 0L, 1L, 0L, 1L, 0L), X2 = c(0L, 1L, 1L, 1L, 
    0L, 0L, 1L, 1L, 1L, 0L), X3 = c(0.911, -0.15, -1.57, -0.58, 
    -0.039, 1.3, -0.15, -1.57, -0.58, -0.039), Y = c(0.8, 0.2, 
    0.3, 0.5, 0.2, 1.2, 0.2, 0.3, 0.5, 0.2)), class = "data.frame",     row.names = c(NA, 
  • 1
    $\begingroup$ Instead of bind_rows(), use rbind() because match.data() objects have a special rbind() method that preserves certain characteristics of the matched dataset. In this case, it ensures that the subclass value is unique for each pair, whereas in your implementation, they are all the same. You can use subclass as an additional clustering variable. $\endgroup$
    – Noah
    Feb 23, 2022 at 15:31
  • $\begingroup$ great @Noah thank you and thank you for your work on MatchIt, and the documentation. So, you think it is a reasonable strategy here? thanks $\endgroup$
    – giac
    Feb 23, 2022 at 16:24
  • 1
    $\begingroup$ I think so. I don't exactly understand your dataset or why you can't just do normal matching with replacement, but if this feels like matching with replacement to you then the vignette should help. $\endgroup$
    – Noah
    Feb 23, 2022 at 17:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.