write.csv() eksportuje ramkę danych
Przeniesienie ramki danych z R do pliku, który mogą otworzyć inne programy, to jedno wywołanie:
write.csv(df, "results.csv", row.names = FALSE)
To linia do zapamiętania, razem z row.names = FALSE, które za chwilę uzasadnimy. Pierwszy argument to ramka danych, drugi to nazwa pliku, a funkcja niczego nie zwraca: całe jej zadanie to efekt uboczny w postaci utworzenia pliku.
Możesz zobaczyć, co dokładnie tworzy write.csv(), bez dotykania dysku, bo w miejscu nazwy pliku przyjmuje dowolne połączenie, w tym stdout(), które po prostu wypisuje wynik:
Oto twój CSV: linia nagłówka, jedna linia na wiersz, wartości tekstowe w cudzysłowach, liczby bez nich. (Cudzysłowy to domyślne quote = TRUE; obsługuje je każdy popularny czytnik CSV, więc zostaw to bez zmian). Zapis do prawdziwego pliku to to samo wywołanie z "results.csv" zamiast stdout().
Dlaczego row.names = FALSE powinno być domyślne
Każda ramka danych ma nazwy wierszy, zwykle po prostu liczby 1, 2, 3..., a write.csv() domyślnie zapisuje je do pliku jako dodatkową pierwszą kolumnę z pustym nagłówkiem. Zobacz, jak wygląda domyślny wynik i co się dzieje, gdy ten plik wraca do R:
Eksport dostał bezimienną pierwszą kolumnę z wartościami "1", "2", a przy ponownym imporcie read.csv() musiała ją jakoś nazwać, więc wymyśliła X. To ta tajemnicza kolumna X, która zaskakuje każdego początkującego: to nie dane, tylko numeracja wierszy R, która wyciekła do pliku. Powtórz kilka razy cykl eksportu i importu, a nazbierasz X, X.1, X.2...
Nazwy wierszy prawie nigdy nie niosą prawdziwej informacji, a gdy niosą, ta informacja i tak powinna trafić do normalnej kolumny. Niech więc row.names = FALSE stanie się odruchem. Właściwie to powinno być ustawienie domyślne; nie jest z powodu zgodności wstecznej, więc nawyk zależy od ciebie.
Gdzie trafia plik
write.csv(df, "results.csv") zapisuje do katalogu roboczego R, a jeśli to nie jest miejsce, którego się spodziewasz, plik jakby znika. Zapis się udał; plik po prostu leży w folderze, który wypisuje getwd(). To ta sama pułapka co przy wczytywaniu, tylko w lustrzanym odbiciu, i rozwiązują ją te same dwa narzędzia:
getwd() # the folder your file went to
write.csv(df, "C:/Users/ada/results/out.csv", row.names = FALSE) # or: remove all doubt
Pełne ścieżki (ukośniki działają na każdej platformie, także w Windows) sprawiają, że skrypty są jednoznaczne. Jak ustawia się katalog roboczy i jak nim zarządzać, opisuje artykuł o katalogu roboczym.
write.table() dla innych separatorów i kwestia dopisywania
write.csv() to tak naprawdę cienka nakładka na ogólną funkcję write.table(), ustawioną pod przecinki. Wywołaj write.table() bezpośrednio, gdy potrzebujesz innego kształtu, najczęściej danych rozdzielanych tabulatorami:
Rozdzielane tabulatorami, bez cudzysłowów: format, który woli wiele narzędzi przetwarzających dane dalej. Zwróć uwagę, że w write.table() wszystko ustawiasz jawnie; funkcja nie dziedziczy domyślnych przecinków ani cudzysłowów z write.csv.
Jedna rzecz, której write.csv() odmawia, to dopisywanie: przekazanie append = TRUE daje ostrzeżenie, że ustawienie zostało zignorowane, bo dopisanie wstawiłoby linię nagłówka w środek pliku. Jeśli naprawdę musisz dodać wiersze do istniejącego pliku CSV, na przykład zapisując wyniki kolejnych uruchomień, zejdź poziom niżej do write.table() i ręcznie wyłącz nagłówek:
write.table(new_rows, "log.csv", sep = ",",
append = TRUE, col.names = FALSE, row.names = FALSE)
A jeśli pracujesz w tidyverse, readr::write_csv(df, "out.csv") to odpowiednik read_csv(): ten sam wynik, szybciej przy dużych ramkach, a nazw wierszy w ogóle nie zapisuje. O jeden argument mniej do zapamiętania.
saveRDS() i readRDS(): dokładny zapis i odczyt
CSV to format stratny. Przechowuje tekst i nic więcej, więc kolumna Date staje się napisem "2026-08-07", czynnik zamienia się w swoje etykiety bez kolejności poziomów, atrybuty znikają, a każdy typ trzeba przy imporcie zgadywać od nowa (zwykle poprawnie, czasem nie: tak, mowa o zerach na początku). Do tego CSV mieści tylko jedną prostokątną tabelę: dopasowany model, lista czy macierz z dimnames w ogóle nie mają reprezentacji w CSV.
Gdy plik jest dla R, na przykład gdy zapisujesz dzisiejsze oczyszczone dane na jutrzejszą sesję, całkowicie pomiń formaty tekstowe:
saveRDS(df, "clean_data.rds") # today
df <- readRDS("clean_data.rds") # tomorrow: identical object, types intact
saveRDS() serializuje jeden obiekt R, dowolny, dokładnie. readRDS() oddaje go bajt po bajcie: czynniki zachowują poziomy, daty wciąż są datami, niczego nie trzeba zgadywać. Działa na wszystkim: ramce danych, liście ramek danych, dopasowanym modelu regresji. Plik .rds to skompresowany format binarny, więc zwykle jest też mniejszy niż CSV. Jedyny koszt to to, że nic poza R go nie otworzy, i właśnie dlatego zasada praktyczna brzmi: CSV do udostępniania ludziom i innym narzędziom, RDS do zapisu dla siebie.
save() i load(), czyli dlaczego RDS zwykle wygrywa
Spotkasz też starszą parę. save() zapisuje wiele nazwanych obiektów do jednego pliku .RData, a load() je przywraca do twojej przestrzeni roboczej, pod oryginalnymi nazwami:
save(df, model, params, file = "session.RData")
load("session.RData") # df, model, params silently appear
Właśnie ta wygoda jest problemem. To load() decyduje o nazwach zmiennych, a nie ty: wrzuca obiekty do twojego środowiska w ciemno, po cichu nadpisując wszystko, co już miało te nazwy. Pół roku później nikt nie pamięta, co w ogóle zawiera session.RData, dopóki go nie wczyta, żeby to sprawdzić.
readRDS() ma uczciwy interfejs: to ty wybierasz nazwę (df <- readRDS(...)), nic nie jest nadpisywane za twoimi plecami, a jeden plik oznacza jeden obiekt. Jeśli nie musisz konkretnie połączyć kilku obiektów w jednym pliku, wybieraj RDS. (A jeśli potrzebujesz takiego pakietu, umieszczenie obiektów w nazwanej liście i zapisanie listy przez saveRDS() daje ten sam efekt bez niespodzianek).
Najważniejsze informacje
write.csv(df, "out.csv", row.names = FALSE): cały eksport i tak, zawsze zrow.names = FALSE.- Tajemnicza kolumna
Xpo ponownym imporcie to nazwy wierszy, które wyciekły do pliku; zapisało je ustawienie domyślne, a w twoim eksporcie nie powinno ich być. - Pliki trafiają do katalogu roboczego:
getwd()odnajduje „zagubione” eksporty, a pełne ścieżki im zapobiegają. write.table()obsługuje inne separatory i (zcol.names = FALSE) prawdziwe dopisywanie;write.csv()celowo nie potrafi dopisywać.saveRDS()/readRDS()dokładnie zapisują i odczytują dowolny obiekt R: typy, czynniki i atrybuty zostają nietknięte. CSV do udostępniania, RDS dla siebie.- Wybieraj RDS zamiast
save()/load(): jawne nazwy, żadnego cichego nadpisywania.
Dalej: typ danych, który zasługuje na własną stronę, zanim ugryzie cię przy imporcie, czyli daty.
Najczęściej zadawane pytania
Jak wyeksportować ramkę danych do CSV w R?
write.csv(df, "output.csv", row.names = FALSE). Część row.names = FALSE ma znaczenie: bez niej R zapisuje swoje wewnętrzne numery wierszy jako dodatkową pierwszą kolumnę, która po ponownym zaimportowaniu pliku pojawia się jako tajemnicza kolumna o nazwie X.
Dlaczego wyeksportowany CSV ma kolumnę X, gdy wczytuję go z powrotem?
Bo domyślne ustawienie write.csv to row.names = TRUE: funkcja zapisała numery wierszy jako pierwszą kolumnę bez nazwy, a read.csv przy wczytywaniu nazwała tę kolumnę X. Eksportuj z row.names = FALSE, a kolumna widmo zniknie.
Czym różni się write.csv od saveRDS w R?
write.csv tworzy tabelę w zwykłym tekście, którą każdy może otworzyć, ale przechowuje tylko tekst: poziomy czynników, typy Date, atrybuty i obiekty nietabelaryczne są spłaszczane albo gubione, a typy trzeba ponownie zgadywać przy imporcie. saveRDS zapisuje jeden obiekt R bajt po bajcie; readRDS zwraca go dokładnie takim, jaki był. Używaj CSV do udostępniania innym, a RDS do zapisu na potrzeby własnych sesji R.
Gdzie write.csv zapisuje plik?
W bieżącym katalogu roboczym R, chyba że podasz pełną ścieżkę. Uruchom getwd(), żeby zobaczyć, gdzie to jest: jeśli plik jakby zniknął, leży w folderze, który wypisuje getwd(). Podanie pełnej ścieżki, np. "C:/Users/ada/results/out.csv", usuwa niejasność.