write.csv() exporta un data frame
Sacar un data frame de R a un archivo que otros programas puedan abrir es una sola llamada:
write.csv(df, "results.csv", row.names = FALSE)
Esa es la línea a memorizar — incluido el row.names = FALSE, que justificaremos en un momento. El primer argumento es el data frame, el segundo es el nombre del archivo, y la función no devuelve nada: todo su trabajo es el efecto secundario de crear el archivo.
Puedes ver exactamente lo que produce write.csv() sin tocar el disco, porque acepta cualquier conexión donde espera un nombre de archivo — incluido stdout(), que simplemente imprime:
Ahí está tu CSV: una línea de encabezado, una línea por fila, valores de texto entre comillas, números a pelo. (El entrecomillado es el valor por defecto quote = TRUE; todo lector de CSV convencional lo maneja, así que déjalo en paz.) Escribir a un archivo real es la misma llamada con "results.csv" en lugar de stdout().
Por qué row.names = FALSE debería ser tu valor por defecto
Todo data frame lleva nombres de fila — normalmente solo los números 1, 2, 3... — y el valor por defecto de write.csv() es escribirlos en el archivo como una primera columna extra con encabezado vacío. Observa cómo se ve la salida por defecto, y qué pasa cuando ese archivo hace el viaje de vuelta a R:
A la exportación le creció una primera columna sin nombre con "1", "2" — y al re-importar, read.csv() tuvo que llamarla de alguna manera, así que se inventó X. Esa es la misteriosa columna X que desconcierta a todo principiante: no son datos, es la numeración de filas de R filtrándose a tu archivo. Haz esto unas cuantas veces en un ciclo de exportación-importación y acumularás X, X.1, X.2...
Los nombres de fila casi nunca llevan información real — y cuando la llevan, esa información pertenece de todos modos a una columna propia. Así que convierte row.names = FALSE en un reflejo. Podría decirse que debería haber sido el valor por defecto; no lo es, por razones de retrocompatibilidad, así que el hábito corre de tu cuenta.
Dónde acaba el archivo
write.csv(df, "results.csv") escribe en el directorio de trabajo de R — y si ese no es el que esperabas, el archivo parece esfumarse. No falló; simplemente está en la carpeta que imprima getwd(). Es la misma trampa que al leer, en imagen especular, y las mismas dos herramientas la resuelven:
getwd() # the folder your file went to
write.csv(df, "C:/Users/ada/results/out.csv", row.names = FALSE) # or: remove all doubt
Las rutas completas (las barras normales funcionan en todas las plataformas, incluido Windows) hacen los scripts inequívocos. Cómo se establece y gestiona el directorio de trabajo se cubre en directorio de trabajo.
write.table() para otros delimitadores — y la cuestión del append
write.csv() es en realidad un envoltorio fino sobre la función de propósito general write.table(), preconfigurada para comas. Llama a write.table() directamente cuando necesites otra forma — lo más común, separado por tabuladores:
Separado por tabuladores, sin comillas — el formato que muchas herramientas posteriores prefieren. Ten en cuenta que con write.table() lo estableces todo explícitamente; no hereda la coma ni los valores por defecto de entrecomillado de write.csv.
Una cosa que write.csv() se niega a hacer es append: pasar append = TRUE te da una advertencia de que la opción fue ignorada, porque anexar reescribiría la línea de encabezado en medio del archivo. Si de verdad necesitas añadir filas a un CSV existente — por ejemplo, registrar resultados entre ejecuciones — baja a write.table() y suprime el encabezado tú mismo:
write.table(new_rows, "log.csv", sep = ",",
append = TRUE, col.names = FALSE, row.names = FALSE)
Y si estás en el tidyverse, readr::write_csv(df, "out.csv") es la contraparte de read_csv(): misma salida, más rápida con data frames grandes, y para empezar nunca escribe nombres de fila — un argumento menos que recordar.
saveRDS() y readRDS(): viajes de ida y vuelta exactos
CSV es un formato con pérdidas. Almacena texto, y punto — así que una columna Date se convierte en la cadena "2026-08-07", un factor se convierte en sus etiquetas con el orden de niveles perdido, los atributos se esfuman y cada tipo debe adivinarse de nuevo al importar (normalmente bien, a veces no — ceros a la izquierda, os estamos mirando). Y un CSV solo puede contener una tabla rectangular: un modelo ajustado, una lista, una matriz con dimnames no tienen representación en CSV.
Cuando el archivo es para R — guardar los datos limpios de hoy para la sesión de mañana — sáltate los formatos de texto por completo:
saveRDS(df, "clean_data.rds") # today
df <- readRDS("clean_data.rds") # tomorrow: identical object, types intact
saveRDS() serializa un objeto de R, cualquier objeto, exactamente. readRDS() lo devuelve byte a byte: los factores conservan sus niveles, las fechas siguen siendo fechas, nada se adivina de nuevo. Funciona con cualquier cosa — un data frame, una lista de data frames, un modelo de regresión ajustado. El archivo .rds es binario comprimido, así que normalmente es más pequeño que el CSV además. El único coste es que nada salvo R puede abrirlo — que es precisamente por lo que la regla general es: CSV para compartir con humanos y otras herramientas, RDS para guardar para ti.
save() y load() — y por qué RDS suele ganar
También te encontrarás con un par más antiguo. save() escribe varios objetos con nombre en un archivo .RData, y load() los restaura — en tu espacio de trabajo, con sus nombres originales:
save(df, model, params, file = "session.RData")
load("session.RData") # df, model, params silently appear
Esa comodidad es el problema. load() decide los nombres de las variables, no tú — planta objetos en tu entorno sin que los veas, sobrescribiendo en silencio cualquier cosa que ya tuviera esos nombres. Seis meses después, nadie recuerda qué contiene siquiera session.RData sin cargarlo para averiguarlo.
readRDS() tiene la interfaz honesta: tú eliges el nombre (df <- readRDS(...)), nada se sobrescribe a tus espaldas, y un archivo significa un objeto. Salvo que necesites específicamente agrupar varios objetos en un solo archivo, prefiere RDS. (Si de verdad necesitas un paquete de objetos, meterlos en una lista con nombres y hacer saveRDS() de la lista consigue el mismo efecto sin ninguna sorpresa.)
Lo que te llevas
write.csv(df, "out.csv", row.names = FALSE)— la exportación entera, y sí, siempre elrow.names = FALSE.- La misteriosa columna
Xal re-importar son los nombres de fila filtrándose al archivo; el valor por defecto los escribió, tú no deberías. - Los archivos acaban en el directorio de trabajo —
getwd()encuentra las exportaciones "perdidas", las rutas completas las previenen. write.table()maneja otros delimitadores y (concol.names = FALSE) el append de verdad;write.csv()deliberadamente no puede anexar.saveRDS()/readRDS()hacen el viaje de ida y vuelta de cualquier objeto de R exactamente — tipos, factores, atributos intactos. CSV para compartir, RDS para ti.- Prefiere RDS sobre
save()/load(): nombres explícitos, sin sobrescrituras silenciosas.
A continuación: un tipo de datos que merece su propia página antes de que te muerda en una importación — las fechas.
Preguntas frecuentes
¿Cómo se exporta un data frame a CSV en R?
write.csv(df, "salida.csv", row.names = FALSE). La parte de row.names = FALSE importa: sin ella, R escribe sus números de fila internos como una primera columna extra, que aparece como una columna misteriosa llamada X cuando alguien re-importa el archivo.
¿Por qué mi CSV exportado tiene una columna X cuando lo vuelvo a leer?
Porque el valor por defecto de write.csv es row.names = TRUE — escribió los números de fila como una primera columna sin nombre, y read.csv la llamó X en el camino de vuelta. Exporta con row.names = FALSE y la columna fantasma desaparece.
¿Cuál es la diferencia entre write.csv y saveRDS en R?
write.csv produce una tabla de texto plano que cualquiera puede abrir, pero solo almacena texto: los niveles de los factores, los tipos Date, los atributos y los objetos no tabulares se aplanan o se pierden, y los tipos deben adivinarse de nuevo al importar. saveRDS almacena un objeto de R byte a byte; readRDS lo devuelve exactamente como era. Usa CSV para compartir con otros, RDS para guardar para tus propias sesiones de R.
¿Dónde guarda write.csv el archivo?
En el directorio de trabajo actual de R, salvo que des una ruta completa. Ejecuta getwd() para ver cuál es — si tu archivo parece haber desaparecido, está en la carpeta que imprima getwd(). Pasar una ruta completa como "C:/Users/ada/results/out.csv" elimina la ambigüedad.