Day 10 of 30·Data Manipulation
Joins & Stacking: from MERGE/SET to dplyr joins & bind_rows()
Combine datasets horizontally and vertically with explicit, key-safe joins.
Learning objectives
- 1Replace MERGE...BY with left_join(), inner_join(), full_join().
- 2Replace SET (stacking) with bind_rows().
- 3Understand how join keys prevent accidental many-to-many merges.
- 4Use anti_join() / semi_join() for filtering joins.
Key SAS® → R comparison
| Task | SAS® | R |
|---|---|---|
| Match-merge | merge a b; by usubjid; | left_join(a, b, by='usubjid') |
| Inner merge | merge + if a and b; | inner_join(a, b, by=...) |
| Stack datasets | set a b; | bind_rows(a, b) |
| Find non-matches | if a and not b; | anti_join(a, b, by=...) |
Code examples
SAS®
proc sort data=adlb; by usubjid; run;
proc sort data=adsl; by usubjid; run;
data adlb; merge adlb adsl(keep=usubjid trt01p age);
by usubjid;
run;R
adlb <- adlb |>
left_join(
adsl |> select(usubjid, trt01p, age),
by = "usubjid"
)Note: Joins don't require pre-sorting and won't silently retain unmatched rows the way a careless MERGE can.
Explanation
dplyr joins name the type of join explicitly — left, inner, full, right — so the intent is unambiguous and there is no reliance on a prior PROC SORT. The by argument states the key, and a mismatch in keys raises a clear error rather than a silent Cartesian blow-up.
bind_rows() stacks datasets and aligns columns by name, adding NA where a column is absent, which is safer than SET's positional behavior. anti_join() and semi_join() express the common 'in A but not B' filtering patterns directly.
Practice exercise
Left-join treatment and age from ADSL onto a lab BDS dataset, stack two adverse-event extracts with bind_rows(), and use anti_join() to find subjects in ADSL with no AE records.