write.csv() exporte un data frame
Sortir un data frame de R vers un fichier que d'autres programmes peuvent ouvrir tient en un appel :
write.csv(df, "results.csv", row.names = FALSE)
Voilà la ligne à mémoriser - y compris le row.names = FALSE, qu'on justifiera dans un instant. Le premier argument est le data frame, le second le nom du fichier, et la fonction ne renvoie rien : tout son travail est l'effet de bord de créer le fichier.
Tu peux voir exactement ce que produit write.csv() sans toucher au disque, parce qu'il accepte n'importe quelle connexion là où il attend un nom de fichier - y compris stdout(), qui se contente d'afficher :
Voilà ton CSV : une ligne d'en-tête, une ligne par enregistrement, les valeurs texte entre guillemets, les nombres nus. (Le guillemetage vient du quote = TRUE par défaut ; tous les lecteurs de CSV courants le gèrent, alors n'y touche pas.) Écrire dans un vrai fichier, c'est le même appel avec "results.csv" à la place de stdout().
Pourquoi row.names = FALSE devrait être ton réflexe
Chaque data frame porte des noms de lignes - généralement juste les nombres 1, 2, 3... - et le comportement par défaut de write.csv() est de les écrire dans le fichier comme une colonne supplémentaire en tête, avec un en-tête vide. Regarde à quoi ressemble la sortie par défaut, et ce qui arrive quand ce fichier fait l'aller-retour vers R :
L'export a fait pousser une première colonne sans nom contenant "1", "2" - et à la réimportation, read.csv() a bien dû l'appeler quelque chose, alors il a inventé X. C'est cette mystérieuse colonne X qui intrigue tous les débutants : ce ne sont pas des données, c'est la numérotation des lignes de R qui fuit dans ton fichier. Fais ça quelques fois dans un cycle export-import et tu accumuleras X, X.1, X.2...
Les noms de lignes ne portent presque jamais de vraie information - et quand c'est le cas, cette information a de toute façon sa place dans une vraie colonne. Fais donc de row.names = FALSE un réflexe. On pourrait soutenir que ça aurait dû être la valeur par défaut ; ça ne l'est pas, pour des raisons de compatibilité ascendante, donc l'habitude te revient.
Où atterrit le fichier
write.csv(df, "results.csv") écrit dans le répertoire de travail de R - et si ce n'est pas là où tu l'attendais, le fichier semble s'évaporer. Il n'a pas échoué ; il est simplement dans le dossier qu'affiche getwd(). C'est le même piège qu'à la lecture, en miroir, et les deux mêmes outils le résolvent :
getwd() # the folder your file went to
write.csv(df, "C:/Users/ada/results/out.csv", row.names = FALSE) # or: remove all doubt
Les chemins complets (les barres obliques marchent sur toutes les plateformes, y compris Windows) rendent les scripts sans ambiguïté. Comment le répertoire de travail est défini et géré est traité dans le répertoire de travail.
write.table() pour d'autres délimiteurs - et la question de l'ajout
write.csv() est en réalité une fine enveloppe autour du write.table() généraliste, préconfigurée pour les virgules. Appelle write.table() directement quand tu as besoin d'une autre forme - le plus souvent une séparation par tabulations :
Séparé par des tabulations, sans guillemets - le format que préfèrent beaucoup d'outils en aval. Note qu'avec write.table() tu règles tout explicitement ; il n'hérite pas de la virgule ni du guillemetage par défaut de write.csv.
Une chose que write.csv() refuse de faire, c'est ajouter à la fin : passer append = TRUE te vaut un avertissement disant que le réglage a été ignoré, parce qu'ajouter réécrirait la ligne d'en-tête au milieu du fichier. Si tu as réellement besoin d'ajouter des lignes à un CSV existant - par exemple pour journaliser des résultats au fil des exécutions - descends jusqu'à write.table() et supprime toi-même l'en-tête :
write.table(new_rows, "log.csv", sep = ",",
append = TRUE, col.names = FALSE, row.names = FALSE)
Et si tu es dans le tidyverse, readr::write_csv(df, "out.csv") est le pendant de read_csv() : même sortie, plus rapide sur de gros tableaux, et il n'écrit jamais de noms de lignes en premier lieu - un argument de moins à retenir.
saveRDS() et readRDS() : des allers-retours exacts
Le CSV est un format à perte. Il stocke du texte, point - donc une colonne Date devient la chaîne "2026-08-07", un facteur devient ses étiquettes sans l'ordre des niveaux, les attributs s'évaporent, et chaque type doit être redeviné à l'import (généralement correctement, parfois non - les zéros en tête, on te regarde). Et un CSV ne peut contenir qu'un seul tableau rectangulaire : un modèle ajusté, une liste, une matrice avec des dimnames n'ont aucune représentation CSV.
Quand le fichier est pour R - enregistrer les données nettoyées d'aujourd'hui pour la session de demain - saute complètement les formats texte :
saveRDS(df, "clean_data.rds") # today
df <- readRDS("clean_data.rds") # tomorrow: identical object, types intact
saveRDS() sérialise un objet R, n'importe lequel, exactement. readRDS() te le rend octet pour octet : les facteurs gardent leurs niveaux, les dates sont toujours des dates, rien n'est redeviné. Ça marche sur n'importe quoi - un data frame, une liste de data frames, un modèle de régression ajusté. Le fichier .rds est du binaire compressé, donc typiquement plus petit que le CSV en prime. Le seul coût, c'est que rien d'autre que R ne peut l'ouvrir - ce qui est précisément pourquoi la règle empirique est : le CSV pour partager avec des humains et d'autres outils, le RDS pour toi-même.
save() et load() - et pourquoi le RDS gagne généralement
Tu croiseras aussi un duo plus ancien. save() écrit plusieurs objets nommés dans un seul fichier .RData, et load() les restaure - dans ton espace de travail, sous leurs noms d'origine :
save(df, model, params, file = "session.RData")
load("session.RData") # df, model, params silently appear
Cette commodité est le problème. C'est load() qui décide des noms de variables, pas toi - il plante des objets dans ton environnement sans que tu les voies, écrasant silencieusement tout ce qui portait déjà ces noms. Six mois plus tard, personne ne se souvient de ce que contient session.RData sans le charger pour aller voir.
readRDS() a l'interface honnête : c'est toi qui choisis le nom (df <- readRDS(...)), rien n'est écrasé dans ton dos, et un fichier signifie un objet. Sauf si tu as spécifiquement besoin de regrouper plusieurs objets dans un seul fichier, préfère le RDS. (Et si tu as besoin d'un regroupement, mettre les objets dans une liste nommée et faire un saveRDS() de la liste donne le même effet sans aucune surprise.)
Ce que tu retiens
write.csv(df, "out.csv", row.names = FALSE)- tout l'export, et oui, toujours lerow.names = FALSE.- La mystérieuse colonne
Xà la réimportation, ce sont les noms de lignes qui fuient dans le fichier ; le réglage par défaut les a écrits, toi tu ne devrais pas. - Les fichiers atterrissent dans le répertoire de travail -
getwd()retrouve les exports « perdus », les chemins complets les évitent. write.table()gère d'autres délimiteurs et (aveccol.names = FALSE) le vrai ajout en fin de fichier ;write.csv()ne peut délibérément pas ajouter.saveRDS()/readRDS()font l'aller-retour de n'importe quel objet R à l'identique - types, facteurs, attributs intacts. Le CSV pour partager, le RDS pour toi.- Préfère le RDS à
save()/load(): noms explicites, pas d'écrasement silencieux.
Prochaine étape : un type de données qui mérite sa propre page avant qu'il ne te morde lors d'un import - les dates.
Questions fréquentes
Comment exporter un data frame en CSV en R ?
write.csv(df, "output.csv", row.names = FALSE). La partie row.names = FALSE compte : sans elle, R écrit ses numéros de lignes internes comme une colonne supplémentaire en tête, qui apparaît comme une mystérieuse colonne nommée X quand quelqu'un réimporte le fichier.
Pourquoi mon CSV exporté a-t-il une colonne X quand je le relis ?
Parce que la valeur par défaut de write.csv est row.names = TRUE - il a écrit les numéros de lignes comme première colonne sans nom, et read.csv a nommé cette colonne X au retour. Exporte avec row.names = FALSE et la colonne fantôme disparaît.
Quelle est la différence entre write.csv et saveRDS en R ?
write.csv produit un tableau en texte brut que tout le monde peut ouvrir, mais il ne stocke que du texte - les niveaux de facteurs, les types Date, les attributs et les objets non tabulaires sont aplatis ou perdus, et les types doivent être redevinés à l'import. saveRDS stocke un objet R octet pour octet ; readRDS le renvoie exactement tel qu'il était. Utilise le CSV pour partager avec d'autres, le RDS pour tes propres sessions R.
Où write.csv enregistre-t-il le fichier ?
Dans le répertoire de travail courant de R, sauf si tu donnes un chemin complet. Lance getwd() pour voir où c'est - si ton fichier semble avoir disparu, il est dans le dossier qu'affiche getwd(). Passer un chemin complet comme "C:/Users/ada/results/out.csv" lève l'ambiguïté.