Joindre et combiner des data frames en R : merge, jointures dplyr, rbind & cbind

Mettre en correspondance des tables sur une clé partagée avec des jointures, ou les empiler avec rbind et cbind

Combinez deux data frames en R — mettez-les en correspondance sur une clé partagée avec les jointures dplyr (inner_join(), left_join(), right_join(), full_join()) et le merge() de base R, conservez ou supprimez des lignes avec les jointures filtrantes semi_join()/anti_join(), et empilez des tables avec rbind()/bind_rows() ou accolez des colonnes avec cbind()/bind_cols() — les deux approches, côte à côte.

Date de publication

17 juillet 2026

Modifié

17 juillet 2026

AstucePoints clés
  • Les jointures mutantes ajoutent des colonnes d’une table correspondante à partir d’une clé partagée : inner_join() / left_join() / right_join() / full_join() en dplyr équivalent à merge(all = FALSE) / merge(all.x = TRUE) / merge(all.y = TRUE) / merge(all = TRUE) en base R.
  • inner_join() ne conserve que les lignes présentes dans les deux tables ; left_join() conserve chaque ligne de la première table et remplit avec NA là où la seconde n’a pas de correspondance.
  • Les jointures filtrantes conservent ou suppriment des lignes sans ajouter de colonnes : semi_join() conserve les lignes de x qui ont une correspondance, anti_join() conserve les lignes qui n’en ont aucune — la façon propre de demander « quels clients n’ont aucune commande ? ».
  • Assembler empile, ça ne fait pas correspondre : rbind() / bind_rows() empilent des lignes (mêmes colonnes) ; cbind() / bind_cols() accolent des colonnes (même ordre de lignes).
  • Assembler est positionnel et fragile — un seul mauvais ordre de lignes désaligne tout en silence. Lorsqu’une clé partagée existe, faites plutôt une jointure dessus.

Introduction

Vos données vivent rarement dans une seule table. Les commandes se trouvent dans un data frame, les clients qui les ont passées dans un autre ; les mesures dans un fichier, les métadonnées des échantillons dans un second. Pour les analyser, il vous les faut côte à côte — le montant de la commande à côté du nom du client, mis en correspondance sur l’id qu’ils partagent.

Deux tâches, souvent confondues :

  • Jointure (join) — mettre en correspondance deux tables sur une colonne clé partagée et réunir leurs colonnes (merge(), ou la famille *_join() de dplyr). C’est ce que vous voulez presque à chaque fois.
  • Assemblage (bind) — empiler les tables par position : des lignes sous des lignes (rbind()), ou des colonnes à côté de colonnes (cbind()). Aucune clé, aucune correspondance — juste un collage.

Cette leçon couvre les deux, dplyr et base R côte à côte, sur de petites tables construites à la main pour que vous voyiez exactement quelles lignes survivent à chaque opération. Nous utilisons deux petites tables qui partagent une colonne id :

customers <- data.frame(
  id   = c("A", "B", "C"),
  name = c("Alice", "Bob", "Carol")
)
orders <- data.frame(
  id     = c("A", "B", "B", "D"),
  amount = c(50, 30, 20, 40)
)
customers
  id  name
1  A Alice
2  B   Bob
3  C Carol
orders
  id amount
1  A     50
2  B     30
3  B     20
4  D     40

Regardez les clés avant de faire la jointure. customers contient A, B, C. orders contient A, B (deux fois) et D. Ainsi, Carol (C) n’a aucune commande et la commande D n’a aucun client — les non-correspondances sont délibérées, car ce qui advient des lignes sans correspondance constitue toute la différence entre les jointures.

Jointures mutantes — apporter les colonnes d’une table correspondante

Une jointure mutante ajoute les colonnes d’une table à une autre, en les mettant en correspondance sur une clé. dplyr les nomme selon les lignes qu’elles conservent ; by = "id" désigne la clé partagée.

Inner join — ne conserver que les correspondances

inner_join() ne conserve que les lignes dont la clé apparaît dans les deux tables. Le merge() de base R fait la même chose avec sa valeur par défaut all = FALSE :

library(dplyr)
customers <- data.frame(id = c("A", "B", "C"), name = c("Alice", "Bob", "Carol"))
orders    <- data.frame(id = c("A", "B", "B", "D"), amount = c(50, 30, 20, 40))

# dplyr
inner_join(customers, orders, by = "id")
  id  name amount
1  A Alice     50
2  B   Bob     30
3  B   Bob     20
# base R — same result
merge(customers, orders, by = "id")            # all = FALSE is the default
  id  name amount
1  A Alice     50
2  B   Bob     30
3  B   Bob     20

Trois lignes reviennent : A une fois et B deux fois (Bob a passé deux commandes). Carol a disparu (aucune commande) et la commande D a disparu (aucun client) — une jointure interne supprime toute non-correspondance des deux côtés.

Left join — conserver chaque ligne de la première table

left_join() conserve toutes les lignes de la table de gauche (la première) et récupère les colonnes correspondantes de celle de droite ; là où la droite n’a pas de correspondance, les nouvelles colonnes sont remplies de NA. En base R, c’est merge(..., all.x = TRUE) :

library(dplyr)
customers <- data.frame(id = c("A", "B", "C"), name = c("Alice", "Bob", "Carol"))
orders    <- data.frame(id = c("A", "B", "B", "D"), amount = c(50, 30, 20, 40))

# dplyr
left_join(customers, orders, by = "id")
  id  name amount
1  A Alice     50
2  B   Bob     30
3  B   Bob     20
4  C Carol     NA
# base R — same result
merge(customers, orders, by = "id", all.x = TRUE)
  id  name amount
1  A Alice     50
2  B   Bob     30
3  B   Bob     20
4  C Carol     NA

Cette fois, Carol reste, avec amount = NA — c’est une cliente sans commande, et la jointure l’enregistre au lieu de la supprimer. La commande D a toujours disparu (elle est à droite). left_join() est le cheval de bataille du quotidien : « garder ma table, ajouter ce qui correspond ».

Right et full joins — conserver l’autre côté, ou les deux

right_join() est le miroir de left_join() — elle conserve chaque ligne de la table de droite (merge(..., all.y = TRUE)). full_join() conserve chaque ligne des deux, en remplissant de NA le côté manquant (merge(..., all = TRUE)) :

library(dplyr)
customers <- data.frame(id = c("A", "B", "C"), name = c("Alice", "Bob", "Carol"))
orders    <- data.frame(id = c("A", "B", "B", "D"), amount = c(50, 30, 20, 40))

# right join — keep every ORDER, even the one with no customer (D)
right_join(customers, orders, by = "id")
  id  name amount
1  A Alice     50
2  B   Bob     30
3  B   Bob     20
4  D  <NA>     40
# full join — keep everything from both (Carol AND order D)
full_join(customers, orders, by = "id")
  id  name amount
1  A Alice     50
2  B   Bob     30
3  B   Bob     20
4  C Carol     NA
5  D  <NA>     40

La jointure droite fait apparaître la commande D avec name = NA (une commande dont le client est manquant). La jointure complète conserve les deux extrémités isolées — Carol (aucune commande) et la commande D (aucun client). Voici toute la famille en un coup d’œil :

dplyr base R merge() Conserve
inner_join(x, y) merge(x, y) uniquement les lignes présentes dans les deux
left_join(x, y) merge(x, y, all.x = TRUE) tout x, correspondances de y (sinon NA)
right_join(x, y) merge(x, y, all.y = TRUE) tout y, correspondances de x (sinon NA)
full_join(x, y) merge(x, y, all = TRUE) toutes les lignes des deux (sinon NA)

Une différence à connaître : merge() trie le résultat selon la clé par défaut, tandis que les jointures dplyr conservent l’ordre de lignes d’origine de la table de gauche. Passez sort = FALSE à merge() si vous devez préserver l’ordre.

Jointures filtrantes — conserver ou supprimer des lignes selon une correspondance, sans nouvelles colonnes

Parfois, vous ne voulez pas du tout des colonnes de l’autre table — vous voulez seulement filtrer une table selon l’existence ou non d’une correspondance. C’est une jointure filtrante, une commodité propre à dplyr (la base R le fait avec %in%).

  • semi_join(x, y) conserve les lignes de x qui ont une correspondance dans y — et, contrairement à inner_join(), elle renvoie une ligne par ligne de x et n’ajoute aucune colonne.
  • anti_join(x, y) conserve les lignes de x qui n’ont aucune correspondance dans y.
library(dplyr)
customers <- data.frame(id = c("A", "B", "C"), name = c("Alice", "Bob", "Carol"))
orders    <- data.frame(id = c("A", "B", "B", "D"), amount = c(50, 30, 20, 40))

# which customers HAVE placed an order?
semi_join(customers, orders, by = "id")
  id  name
1  A Alice
2  B   Bob
# which customers have NO order? (the classic question)
anti_join(customers, orders, by = "id")
  id  name
1  C Carol

semi_join() renvoie Alice et Bob — une fois chacun, même si Bob a deux commandes (une jointure filtrante ne duplique jamais et n’ajoute jamais de colonnes). anti_join() ne renvoie que Carol : la cliente sans commande. Les équivalents en base R sont customers[customers$id %in% orders$id, ] et customers[!customers$id %in% orders$id, ].

Empiler des lignes — rbind() et bind_rows()

La jointure met en correspondance sur une clé. L’assemblage ignore les clés et empile par position. Pour placer les lignes d’une table sous une autre — mêmes colonnes, plus de lignes — utilisez rbind() (base R) ou bind_rows() (dplyr) :

library(dplyr)
q1 <- data.frame(id = c("A", "B"), amount = c(50, 30))
q2 <- data.frame(id = c("C", "D"), amount = c(20, 40))

# base R
rbind(q1, q2)
  id amount
1  A     50
2  B     30
3  C     20
4  D     40
# dplyr — same result
bind_rows(q1, q2)
  id amount
1  A     50
2  B     30
3  C     20
4  D     40

Les deux empilent les quatre lignes en une seule table. La différence apparaît quand les colonnes ne s’alignent pas : rbind() génère une erreur, tandis que bind_rows() comble les manques avec NA et poursuit —

library(dplyr)
q1 <- data.frame(id = c("A", "B"), amount = c(50, 30))
q3 <- data.frame(id = c("C"), amount = c(20), note = "late")   # extra column

bind_rows(q1, q3)   # 'note' is NA for the q1 rows
  id amount note
1  A     50 <NA>
2  B     30 <NA>
3  C     20 late

Cette tolérance rend bind_rows() pratique pour empiler de nombreux fichiers dont les colonnes se correspondent presque — mais pas exactement.

Accoler des colonnes — cbind() et bind_cols()

Pour placer des colonnes côte à côte — mêmes lignes, plus de colonnes — utilisez cbind() (base R) ou bind_cols() (dplyr) :

library(dplyr)
people <- data.frame(id = c("A", "B", "C"), name = c("Alice", "Bob", "Carol"))
extra  <- data.frame(age = c(30, 25, 40))

# base R
cbind(people, extra)
  id  name age
1  A Alice  30
2  B   Bob  25
3  C Carol  40
# dplyr — same result
bind_cols(people, extra)
  id  name age
1  A Alice  30
2  B   Bob  25
3  C Carol  40

Les deux accolent age à people. Mais remarquez ce qui rend cela fragile : l’alignement des colonnes se fait par position — ligne 1 avec ligne 1, ligne 2 avec ligne 2 — en supposant que les deux tables sont déjà dans le même ordre. Si extra était trié différemment, vous attacheriez en silence le mauvais âge à chaque personne, sans aucune erreur. Dès que les deux tables partagent une clé, préférez un left_join() sur cette clé : il fait correspondre par identité, non par position, et ne peut donc pas se désaligner.

Laquelle, et quand

Un repère rapide pour choisir le bon outil :

Vous voulez… Utilisez
Ajouter les colonnes d’une autre table, sur une clé partagée left_join() / merge() (une jointure mutante)
Ne conserver que les lignes présentes dans les deux tables inner_join()
Conserver chaque ligne de votre table principale, NA là où rien ne correspond left_join()
Filtrer les lignes qui ont (ou non) une correspondance — sans nouvelles colonnes semi_join() / anti_join()
Empiler plus de lignes (mêmes colonnes) rbind() / bind_rows()
Ajouter plus de colonnes (même ordre de lignes) cbind() / bind_cols()

La règle générale : faites correspondre sur une clé avec une jointure chaque fois que vous le pouvez ; ne recourez à rbind()/cbind() que lorsque les tables s’alignent réellement par position.

Essayez en direct

Les blocs ci-dessus ont été exécutés au moment de la génération. Modifiez celui-ci et appuyez sur Run pour faire des jointures et empiler des tables par vous-même — il s’exécute dans votre navigateur via webR (sans installation).

🟢 Avec un agent IA

Collez un head() de deux de vos propres tables et demandez à Prova « fais la jointure de celles-ci sur customer_id, en conservant chaque ligne de la première, puis montre-moi quelles lignes n’ont pas correspondu » ; elle écrit les appels left_join() + anti_join(). Comme le runtime est juste là, vous les exécutez pour confirmer que les lignes correspondantes et le nombre de lignes sont ceux que vous attendez sur vos données, et non une supposition d’apparence plausible. Demander à Prova →

Vous travaillez en Python ? Un guide pandas merge() / concat() arrive dans la série Python.

Problèmes fréquents

Une jointure a renvoyé bien plus de lignes que l’une ou l’autre table — une explosion plusieurs-à-plusieurs. Lorsque la clé est dupliquée des deux côtés, chaque paire correspondante est renvoyée, si bien que deux-fois-deux devient quatre lignes :

library(dplyr)
x <- data.frame(id = c("A", "A"), val = c(1, 2))
y <- data.frame(id = c("A", "A"), tag = c("p", "q"))
inner_join(x, y, by = "id")     # 4 rows — every combination, plus a dplyr warning
Warning in inner_join(x, y, by = "id"): Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 1 of `x` matches multiple rows in `y`.
ℹ Row 1 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
  "many-to-many"` to silence this warning.
  id val tag
1  A   1   p
2  A   1   q
3  A   2   p
4  A   2   q

C’est une véritable relation plusieurs-à-plusieurs, et dplyr vous en avertit parce que c’est généralement une erreur — une clé que vous pensiez unique ne l’est pas. Dédupliquez la clé d’au moins un côté (par ex. distinct(y, id, .keep_all = TRUE)), ou ajoutez une seconde colonne au by pour que la correspondance soit unique.

Un left_join() a perdu des correspondances dont vous étiez sûr — les clés ne concordent pas vraiment. Les jointures font correspondre des valeurs exactes et de même type, si bien que "B" ne correspond jamais à "b", " B", ni le nombre 2 à la chaîne "2". Les lignes survivent (c’est une jointure gauche) mais leurs nouvelles colonnes reviennent à NA. Vérifiez avec str() que les deux colonnes clés sont du même type, et normalisez d’abord — trimws(), toupper() ou as.character() — avant de faire la jointure.

rbind() échoue avec « names do not match previous names ». rbind() exige que les deux tables aient les mêmes colonnes dans le même ordre. Réordonnez ou ajoutez la colonne manquante, ou passez à dplyr::bind_rows(), qui fait correspondre les colonnes par nom et comble tout manque avec NA.

Questions fréquentes

Mettez-les en correspondance sur une colonne clé partagée. En base R : merge(x, y, by = "id") (une jointure interne par défaut). En dplyr : inner_join(x, y, by = "id"), ou left_join() pour conserver chaque ligne de x. Utilisez by = c("col1", "col2") quand la clé s’étend sur plusieurs colonnes.

inner_join() ne conserve que les lignes dont la clé apparaît dans les deux tables — les non-correspondances de chaque côté sont supprimées. left_join() conserve chaque ligne de la première table (celle de gauche) et remplit les nouvelles colonnes avec NA là où la seconde table n’a pas de correspondance. Utilisez left_join() lorsque vous ne devez pas perdre de lignes de votre table principale.

Utilisez rbind(df1, df2) pour placer les lignes de df2 sous df1 — les deux doivent avoir les mêmes colonnes dans le même ordre. Le bind_rows(df1, df2) de dplyr fait la même chose mais fait correspondre les colonnes par nom et comble les colonnes manquantes avec NA, tolérant ainsi les tables qui ne s’alignent pas exactement.

Ils font le même travail — combiner deux tables sur une clé. merge() est en base R ; les verbes *_join() de dplyr sont les équivalents tidyverse aux noms plus clairs (left_join() = merge(all.x = TRUE), full_join() = merge(all = TRUE)). Deux différences pratiques : merge() trie le résultat selon la clé par défaut (sort = FALSE pour garder l’ordre), tandis que les jointures conservent l’ordre de lignes de la table de gauche ; et dplyr ajoute les jointures filtrantes semi_join()/anti_join(), auxquelles la base merge() n’a pas d’équivalent direct.

Utilisez le anti_join(x, y, by = "id") de dplyr — il renvoie les lignes de x dont la clé n’a aucune correspondance dans y. En base R, l’équivalent est x[!x$id %in% y$id, ]. C’est la façon standard de répondre à des questions comme « quels clients n’ont aucune commande ? ».

Testez vos connaissances

Vous avez une table employees et une table salaries qui partagent un id. (1) Utilisez une jointure qui conserve chaque employé et attache son salaire (NA là où il n’y en a pas), puis (2) listez les employés qui n’ont aucune ligne de salaire.

# (1) needs the join that keeps all of the FIRST table -> left_join().
# (2) needs the filtering join that keeps rows with NO match -> anti_join().
library(dplyr) employees <- data.frame(id = c(1, 2, 3), name = c("Ann", "Ben", "Cara")) salaries <- data.frame(id = c(1, 2), salary = c(50000, 60000)) # 1) keep every employee, add salary joined <- left_join(employees, salaries, by = "id") joined # 2) which employees have NO salary row? anti_join(employees, salaries, by = "id")
library(dplyr)

employees <- data.frame(id = c(1, 2, 3), name = c("Ann", "Ben", "Cara"))
salaries  <- data.frame(id = c(1, 2),    salary = c(50000, 60000))

# 1) keep every employee, add salary
joined <- left_join(employees, salaries, by = "id")
joined

# 2) which employees have NO salary row?
anti_join(employees, salaries, by = "id")

Vous voulez conserver chaque ligne de la table x, ajouter les colonnes de y là où la clé correspond, et NA là où elle ne correspond pas. Quel appel le fait ?

A. inner_join(x, y, by = "id") B. left_join(x, y, by = "id") C. semi_join(x, y, by = "id")

Afficher la réponse

B. left_join() conserve tout x et remplit les colonnes sans correspondance avec NA. A (inner_join) supprime les lignes de x qui n’ont pas de correspondance ; C (semi_join) ne conserve que les lignes correspondantes de x et n’ajoute aucune colonne de y.

Conclusion

Combiner des data frames se résume à deux idées. La jointure quand les tables partagent une clé : left_join() / merge() pour ajouter des colonnes (jointures mutantes), semi_join() / anti_join() pour filtrer les lignes selon une correspondance. L’assemblage seulement quand les tables s’alignent par position : rbind() / bind_rows() pour empiler des lignes, cbind() / bind_cols() pour ajouter des colonnes. Optez par défaut pour une jointure fondée sur une clé — elle fait correspondre par identité et ne peut donc pas se désaligner en silence comme le fait un assemblage positionnel. Les approches dplyr et base R donnent le même résultat ; choisissez celle qui convient à votre code.

Leçons connexes

Cette page vous a-t-elle été utile ?

Prouvez que vous savez le faire. Maîtrisez toute la série Manipulation de données en R et Python — suivez votre parcours, construisez des projets et obtenez un certificat.

Commencer gratuitement →

Passez à Pro — Prova illimité sur vos propres données et un certificat vérifiable qui atteste la compétence.

dès 15 $/mois facturé annuellement

Passer à Pro →

✓ Vous êtes Pro — continuez. The runtime is the judge.

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN
Note

Cette leçon est reproductible : chaque résultat de cette page a été produit par le code affiché — modifiez n’importe quel bloc et appuyez sur Run pour le reproduire dans votre navigateur. The runtime is the judge.

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {Joindre et combiner des data frames en R : merge, jointures
    dplyr, rbind \& cbind},
  date = {2026-07-17},
  url = {https://www.datanovia.com/learn/programming/data-wrangling/combine-data-frames-in-r},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“Joindre et combiner des data frames en R : merge, jointures dplyr, rbind & cbind.” 2026. July 17. https://www.datanovia.com/learn/programming/data-wrangling/combine-data-frames-in-r.