9 Wczytywanie i zapisywanie plików

Języki programowania mają na celu wykonywanie wielu złożonych operacji w relatywnie krótkim czasie. Często te działania oparte są o dane zewnętrzne, np. stworzone przez człowieka, automatyczny sensor, czy jako efekt działania innego programu. Czasem też konieczne jest przekazanie lub udostępnienie wyników obliczeń dla innych osób. Celem tego rozdziału jest wprowadzenie do zagadnień związanych z określaniem położenia plików na dysku komputera, wykonywaniu działań na plikach i folderach oraz pobieraniu danych z internetu. Posiadając taką wiedzę możliwe jest wczytywanie i zapisywanie danych, takich jak dane tekstowe, dane w formatach R, czy dane z arkuszy kalkulacyjnych.

9.1 Folder roboczy

Folder roboczy (ang. working directory) to miejsce na dysku, w którym aktualnie pracujemy. Folder roboczy można sprawdzić korzystając z funkcji getwd():

getwd()
#> [1] "/home/runner/work/elp/elp"

Zmienić folder roboczy można za pomocą skrótu Ctrl+Shift+H w RStudio (inaczej Session -> Set Working Directory -> Choose Directory..) lub też funkcji setwd():

setwd("home/jakub/Documents/elp/") #Linux
setwd("C:/Users/jakub/Documenty/elp/") #Windows
Ustawienie folderu roboczego ma też miejsce przy tworzeniu nowego lub otwieraniu istniejącego projektu RStudio. Dlatego też, używanie projektów RStudio jest rekomendowane przy pracy z R.

Folder roboczy jest ważny ponieważ pozwala na korzystanie z względnej ścieżki. Ścieżka względna oznacza określanie ścieżki pliku w odniesieniu do istniejącego folderu roboczego, podczas, gdy ścieżka bezwzględna opisuje pełne położenie pliku. Przykładowo, mamy plik dane_meteo.csv, którego pełna ścieżka to home/jakub/Documents/elp/pliki/dane_meteo.csv. Z poziomu R ten plik jest widoczny zarówno jako home/jakub/Documents/elp/pliki/dane_meteo.csv, ale też w postaci pliki/dane_meteo.csv39. Używanie ścieżek względnych jest rekomendowane, ponieważ znacząco upraszcza pracę, gdy dane/obliczenia przenosi się pomiędzy różnymi komputerami lub gdy współpracuje się z innymi osobami. Ścieżki względne są też używane w połączeniu z systemami kontroli wersji (rozdział 14).

Ścieżki folderów w systemach Windows są domyślnie rozdzielane ukośnikiem wstecznym (\, ang. backslash), jednak R pozwala także na użycie prawego ukośnika (/, ang. slash). Prawy ukośnik jest rekomendowany, ponieważ działa on zarówno na Windowsach, jak i komputerach z systemami MacOS czy Linux.

9.2 Działania na plikach i folderach

Z poziomu R możliwe jest również zarządzanie folderami i plikami na dysku. Do tworzenia nowych folderów służy funkcja dir.create(), np. dir.create("dane") stworzy nowy podfolder o nazwie "dane". Sprawdzenie czy folder już istnieje możliwe jest używając funkcji dir.exists(), np. dir.exists("dane"), która zwraca wartość TRUE gdy folder o tej nazwie istnieje lub FALSE gdy takiego folderu nie ma. Do usuwania istniejących folderów służy funkcja unlink(). W jej przypadku konieczne jest podanie, oprócz nazwy folder do usunięcia, argumentu recursive = TRUE. Przykładowo, aby usunąć folder "dane" należy wpisać unlink("dane", recursive = TRUE).

Sprawdzenie czy plik istnieje na dysku można wykonać używając file.exist(), a usunąć go za pomocą file.remove(). Obie funkcje przyjmują jako wejście wektor znakowy zawierający nazwy plików do sprawdzenia czy usunięcia.

W przypadkach, gdy konieczne jest stworzenie nowego archiwum ZIP lub rozpakowanie istniejącego pliku w tym formacie można użyć funkcji zip() oraz unzip()40.

9.3 Dane internetowe

Pliki, które chcemy otworzyć nie muszą od razu znajdować się na dysku naszego komputera. Możliwe jest, między innymi, pobranie ich z poziomu R za pomocą funkcji download.file()41. Należy w niej podać adres URL pliku do pobrania, oraz nazwę pliku do zapisania.

download.file("https://raw.githubusercontent.com/Nowosad/elp/master/pliki/dane_meteo.csv", 
              destfile = "pliki/dane_meteo_url.csv")

W efekcie plik dane_meteo_url.csv zostanie zapisany w folderze pliki. Funkcja download.file() ma też szereg dodatkowych argumentów, między innymi method określającą metodę pobierania danych oraz mode określający sposób zapisu pliku.

9.4 Wczytywanie plików tekstowych

Podstawowymi sposobami przechowywania informacji są pliki tekstowe i binarne. Przykładowo, pliki tekstowe mogą przechowywać dane w postaci tabelarycznej, a jednym z najczęściej używanych formatów tekstowych jest CSV (ang. comma-separated values).

Wyobraźmy sobie, że otrzymaliśmy plik tekstowy zawierający wybrane pomiary meteorologiczne dla Poznania oraz Zakopanego w roku 2017.

#> [1] "kod_stacji,nazwa_stacji,rok,miesiac,dzien,tavg,precip"
#> [2] "352160330,POZNAŃ,2017,1,1,1.4,0"                      
#> [3] "352160330,POZNAŃ,2017,1,2,0.1,0"                      
#> [4] "352160330,POZNAŃ,2017,1,3,0.5,4.8"                    
#> [5] "352160330,POZNAŃ,2017,1,4,1.5,2.3"

Zapisaliśmy ten plik jako dane_meteo.csv w podfolderze pliki, więc jego ścieżka względna to "pliki/dane_meteo.csv". Po otworzeniu tego pliku w edytorze tekstu widzimy, że pierwszy jego wiersz zawiera nazwy kolumn, a następne wiersze to kolejne obserwacje. Dodatkowo można zobaczyć, że kolumny rozdzielane są przecinkami (,), natomiast wartości zmiennoprzecinkowe kropkami (.).

Do wczytania tego pliku możemy użyć wbudowanej w R funkcji read.csv(), podając w niej bezwzględną lub względną ścieżkę do pliku.

meteo = read.csv("pliki/dane_meteo.csv")
str(meteo)
#> 'data.frame':    730 obs. of  7 variables:
#>  $ kod_stacji  : int  352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 ...
#>  $ nazwa_stacji: chr  "POZNAŃ" "POZNAŃ" "POZNAŃ" "POZNAŃ" ...
#>  $ rok         : int  2017 2017 2017 2017 2017 2017 2017 2017 2017 2017 ...
#>  $ miesiac     : int  1 1 1 1 1 1 1 1 1 1 ...
#>  $ dzien       : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ tavg        : num  1.4 0.1 0.5 1.5 -3.5 -8.4 -7.9 -7.7 -5.8 -5 ...
#>  $ precip      : num  0 0 4.8 2.3 0 0 2.1 0 0 0 ...

Oprócz funkcji read.csv() istnieje również funkcja o nazwie read.csv2(). Pierwsza z nich jest przystosowana do wczytania danych dla których separator kolumn to , a separator dziesiętny to ., druga natomiast jest używana gdy wejściowe dane mają ; jak separator kolumn i , jako separator dziesiętny.

Pliki tekstowe mogą zawierać tekst używając różnych standardów kodowania znaków o których była mowa w rozdziale 2. Czasem więc konieczne jest podane odpowiedniego kodowania używając argumentu encoding, co pozwoli na poprawne wczytanie pliku do R.

meteo = read.csv("pliki/dane_meteo.csv", encoding = "UTF-8")
str(meteo)
#> 'data.frame':    730 obs. of  7 variables:
#>  $ kod_stacji  : int  352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 ...
#>  $ nazwa_stacji: chr  "POZNAŃ" "POZNAŃ" "POZNAŃ" "POZNAŃ" ...
#>  $ rok         : int  2017 2017 2017 2017 2017 2017 2017 2017 2017 2017 ...
#>  $ miesiac     : int  1 1 1 1 1 1 1 1 1 1 ...
#>  $ dzien       : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ tavg        : num  1.4 0.1 0.5 1.5 -3.5 -8.4 -7.9 -7.7 -5.8 -5 ...
#>  $ precip      : num  0 0 4.8 2.3 0 0 2.1 0 0 0 ...

Czasem dane tekstowe posiadają inne znaki służące jako separatory, czy też nie posiadają nazw kolumn. W takich sytuacjach można użyć funkcji read.table(), która zawiera cały szereg argumentów, które można dopasować, aby poprawnie wczytać dane tekstowe.

meteo = read.table("pliki/dane_meteo.csv", 
                   sep = ",",
                   header = TRUE)
str(meteo)
#> 'data.frame':    730 obs. of  7 variables:
#>  $ kod_stacji  : int  352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 ...
#>  $ nazwa_stacji: chr  "POZNAŃ" "POZNAŃ" "POZNAŃ" "POZNAŃ" ...
#>  $ rok         : int  2017 2017 2017 2017 2017 2017 2017 2017 2017 2017 ...
#>  $ miesiac     : int  1 1 1 1 1 1 1 1 1 1 ...
#>  $ dzien       : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ tavg        : num  1.4 0.1 0.5 1.5 -3.5 -8.4 -7.9 -7.7 -5.8 -5 ...
#>  $ precip      : num  0 0 4.8 2.3 0 0 2.1 0 0 0 ...
R posiada kilka dodatkowych funkcji pozwalających na odczytywanie plików tekstowych, tj. read.delim(), read.delim2(), read.fwf(), czy readLines(). Funkcje read.delim() oraz read.delim2() są odpowiednikami read.csv() i read.csv2() dla plików, gdzie kolejne zmienne są oddzielane tabulatorami. Funkcja read.fwf() służy do odczytywania danych o ustalonej długości kolejnych zmiennych. Funkcja readLines() wczytuje kolejne linie z pliku tekstowego. Efektem jej działania w przeciwieństwie do poprzednich funkcji nie jest ramka danych, ale wektor tekstowy, gdzie każdy kolejny element wektora to tekst z kolejnych linii.

9.5 Zapisywanie plików tekstowych

Plik pliki/dane_meteo.csv zawiera pomiary ze stacji Poznań oraz Zakopane. W przypadku, gdy interesują nas tylko informacje dla Poznania możemy wydzielić odpowiednie wiersze używając funkcji subset() (sekcja 7.2.3).

meteo_pzn = subset(meteo, nazwa_stacji == "POZNAŃ")
str(meteo_pzn)
#> 'data.frame':    365 obs. of  7 variables:
#>  $ kod_stacji  : int  352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 ...
#>  $ nazwa_stacji: chr  "POZNAŃ" "POZNAŃ" "POZNAŃ" "POZNAŃ" ...
#>  $ rok         : int  2017 2017 2017 2017 2017 2017 2017 2017 2017 2017 ...
#>  $ miesiac     : int  1 1 1 1 1 1 1 1 1 1 ...
#>  $ dzien       : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ tavg        : num  1.4 0.1 0.5 1.5 -3.5 -8.4 -7.9 -7.7 -5.8 -5 ...
#>  $ precip      : num  0 0 4.8 2.3 0 0 2.1 0 0 0 ...

Następnie możemy zapisać obiekt meteo_pzn do nowego pliku używając funkcji write.csv()42, poprzez podanie nazwy obiektu do zapisania oraz ścieżki zapisu wynikowego pliku. Dodatkowo możliwe jest pominięcie zapisania nazw wierszy (row.names = FALSE).

write.csv(meteo_pzn,
          file = "pliki/dane_meteo_pzn.csv",
          row.names = FALSE)
Funkcje read.csv() czy write.csv() są domyślnie dostępne w języku R. Ich wydajność nie jest niestety najlepsza w przypadku plików tekstowych o dużej wielkości. W takich przypadkach warto użyć alternatywnych funkcji, np. read_csv() i write_csv() z pakietu readr (Wickham, Hester, and Francois 2018) lub fread() i fwrite() z pakietu data.table (Dowle and Srinivasan 2019).

9.6 Formaty R

Formaty tekstowe są bardzo uniwersalne pozwalając na odczyt, zapis czy przenoszenie danych pomiędzy różnymi komputerami, programami czy językami programowania. Mają one jednak pewne ograniczenia. Wielkość pliku tekstowego rośnie bardzo szybko wraz z liczbą elementów, a plik tekstowy nie przechowuje dodatkowych informacji specyficznych dla języków programowania. Wczytanie czy zapis dużego pliku tekstowego zabiera też relatywnie dużo czasu.

Przykładowo, chcemy aby kolumna nazwa_stacji była reprezentowana jako wektor czynnikowy.

meteo$nazwa_stacji = as.factor(meteo$nazwa_stacji)
str(meteo)
#> 'data.frame':    730 obs. of  7 variables:
#>  $ kod_stacji  : int  352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 ...
#>  $ nazwa_stacji: Factor w/ 2 levels "POZNAŃ","ZAKOPANE": 1 1 1 1 1 1 1 1 1 1 ...
#>  $ rok         : int  2017 2017 2017 2017 2017 2017 2017 2017 2017 2017 ...
#>  $ miesiac     : int  1 1 1 1 1 1 1 1 1 1 ...
#>  $ dzien       : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ tavg        : num  1.4 0.1 0.5 1.5 -3.5 -8.4 -7.9 -7.7 -5.8 -5 ...
#>  $ precip      : num  0 0 4.8 2.3 0 0 2.1 0 0 0 ...

W przypadku zapisania nowego obiektu do pliku tekstowego ta informacja zostanie utracona. Alternatywnie, jeżeli chcemy używać tych danych tylko w języku R możemy zapisać je do pliku w binarnym formacie RDS. Taki zapis można wykonać używając funkcji saveRDS() podając nazwę obiektu do zapisu oraz ścieżkę do nowego pliku.

saveRDS(meteo, file = "pliki/dane_meteo.rds")

Taki plik będzie domyślnie mniejszy (nastąpi jego kompresja przy zapisie) niż tekstowy i będzie posiadał on wszelkie informacje o klasie tego obiektu. Ponowne wczytanie obiektu można wykonać używając funkcji readRDS().

meteo_rds = readRDS("pliki/dane_meteo.rds")
str(meteo_rds)
#> 'data.frame':    730 obs. of  7 variables:
#>  $ kod_stacji  : int  352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 352160330 ...
#>  $ nazwa_stacji: Factor w/ 2 levels "POZNAŃ","ZAKOPANE": 1 1 1 1 1 1 1 1 1 1 ...
#>  $ rok         : int  2017 2017 2017 2017 2017 2017 2017 2017 2017 2017 ...
#>  $ miesiac     : int  1 1 1 1 1 1 1 1 1 1 ...
#>  $ dzien       : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ tavg        : num  1.4 0.1 0.5 1.5 -3.5 -8.4 -7.9 -7.7 -5.8 -5 ...
#>  $ precip      : num  0 0 4.8 2.3 0 0 2.1 0 0 0 ...

Powyżej można zobaczyć, że została zachowana wcześniejsza zmiana - kolumna nazwa_stacji nadal jest reprezentowana poprzez wektor czynnikowy.

Dodatkowo możliwe jest zapisywanie wielu obiektów do jednego pliku w formacie o rozszerzeniu .rda a następnie ich odczytanie używając wbudowanych funkcji save() i load().

Pakiety to zorganizowany zbiór funkcji rozszerzający możliwości R (sekcja 3.5). Istotnym elementem każdego pakietu jest jego dokumentacja, która ułatwia użytkownikom zrozumienie i wykorzystanie możliwości danego pakietu. Często, aby pokazać szczegółowo działanie danej funkcji wykorzystywane są przykładowe dane, które można dołączyć do pakietu.

Przykładowo, wraz z R domyślnie instalowany jest pakiet o nazwie datasets. Aby wyświetlić listę zbiorów danych w tym pakiecie można użyć funkcji data() i podać w niej nazwę konkretnego pakietu.

data(package = "datasets")

Wczytanie zbioru danych z wybranego pakietu odbywa się poprzez wybór nazwy zbioru (np. "faithful") oraz nazwy pakietu "datasets".

data("faithful", package = "datasets")

Po wykonaniu tej funkcji zbiór danych jest dostępny z poziomu R. Zawiera on ramkę danych z dwoma kolumnami opisującymi gejzer Old Faithful (rycina 9.1): eruptions - czas erupcji oraz waiting - czas oczekiwania na kolejną erupcję.

head(faithful)
#>   eruptions waiting
#> 1      3.60      79
#> 2      1.80      54
#> 3      3.33      74
#> 4      2.28      62
#> 5      4.53      85
#> 6      2.88      55
Obraz Alberta Bierstadta przedstawiający gejzer Old Faithful około roku 1881. Źródło: https://commons.wikimedia.org/wiki/File:Bierstadt_Albert_Old_Faithful.jpg

Rycina 9.1: Obraz Alberta Bierstadta przedstawiający gejzer Old Faithful około roku 1881. Źródło: https://commons.wikimedia.org/wiki/File:Bierstadt_Albert_Old_Faithful.jpg

9.7 Arkusze kalkulacyjne

Powszechnym sposobem przechowywania danych tabelarycznych są arkusze kalkulacyjne tworzone w programie Microsoft Excel. Tego typu dane można wczytać do R używając pakietu readxl (Wickham and Bryan 2019). Główną funkcją tego pakietu jest read_excel(), przyjmująca ścieżkę pliku do wczytania. Dodatkowe argumenty tej funkcji pozwalają, np. na zdefiniowanie arkusza do wczytania (sheet) czy zasięgu komórek (range).

library(readxl)
meteo_z_xl = read_excel("pliki/dane_meteo.xlsx")
head(meteo_z_xl)
#> # A tibble: 6 × 7
#>   kod_stacji nazwa_st…¹   rok miesiac dzien  tavg precip
#>        <dbl> <chr>      <dbl>   <dbl> <dbl> <dbl>  <dbl>
#> 1  352160330 POZNAŃ      2017       1     1   1.4    0  
#> 2  352160330 POZNAŃ      2017       1     2   0.1    0  
#> 3  352160330 POZNAŃ      2017       1     3   0.5    4.8
#> 4  352160330 POZNAŃ      2017       1     4   1.5    2.3
#> 5  352160330 POZNAŃ      2017       1     5  -3.5    0  
#> 6  352160330 POZNAŃ      2017       1     6  -8.4    0  
#> # … with abbreviated variable name ¹​nazwa_stacji

Do zapisania ramki danych do formatu Excel można użyć funkcji write_xlsx() z pakietu writexl (Ooms 2019)43.

9.8 Inne formaty

Powyżej można było zobaczyć jaki sposób można wczytać dane z różnorodnych plików tekstowych, R, czy arkuszy kalkulacyjnych. R pozwala jednocześnie na otworzenie wielu innych formatów plików używając dodatkowych pakietów. Przykładowo, hierarchiczne formaty danych można wczytać używając pakietu jsonlite (format .json) (Ooms 2020) czy xml2 (format .xml) (Wickham, Hester, and Ooms 2020), a formaty danych przestrzennych używając pakietu sf (dane wektorowe) (Pebesma 2020) czy raster (dane rastrowe) (Hijmans 2020). Zestawienie zawierające dodatkowe przykłady można znaleźć pod adresem https://github.com/leeper/rio.

Powszechną sytuacją jest przechowywanie danych w różnego rodzaju bazach danych. Ma to miejsce, kiedy dane są znacznej wielkości, mają złożone relacje, czy też muszą być jednocześnie dostępne dla wielu osób. Dostęp do baz danych w R możliwy jest używając pakietu DBI (R Special Interest Group on Databases (R-SIG-DB), Wickham, and Müller 2019) wraz z dodatkowym pakietem dla konkretnego systemu bazodanowego (np. RPostgreSQL dla baz PostgreSQL (Conway et al. 2017) czy RSQLite dla baz SQLite (Müller et al. 2020)).44

9.9 Zadania

  1. Stwórz nowy projekt RStudio o nazwie “IO”. Wszystkie kolejne zadania wykonuj wewnątrz tego projektu.

  2. Sprawdź jaki jest obecny folder roboczy.

  3. Stwórz z poziomu R dwa nowe foldery, jeden o nazwie "data" i drugi o nazwie "R".

  4. Pobierz plik pliki.zip znajdujący się pod adresem https://github.com/Nowosad/elp/raw/master/. Rozpakuj go do podfolderu "data" i usuń pobrane archiwum z poziomu R.

  5. Wczytaj dane z pliku dane_meteo.xlsx. Usuń z nowego obiektu pierwszą kolumnę i zapisz go jako plik w binarnym formacie RDS.

  6. Wczytaj pliki dane_meteo.csv oraz dane_meteo2.csv do R. Połącz te dwa obiekty łącząc kolumny, a następnie zapisz nowy obiekt do pliku met.xlsx.

  7. Napisz funkcję, która przyjmuje jako argument nazwę folderu, a następnie wczytuje wszystkie pliki o rozszerzeniu .csv znajdujące się w tym folderze i łączy je kolumnami.

  8. W pliku list.txt znajduje się zaszyfrowana wiadomość. Aby ją odczytać należy stworzyć odwrotność jej zawartości, tj. pierwsza linia tekstu w pliku wejściowym ma być ostatnią linią tekstu w przetworzonym obiekcie, a pierwszy znak w danej linii ma stać się ostatnim, itd. Napisz funkcję, która odszyfruje tą wiadomość.

Bibliografia

Conway, Joe, Dirk Eddelbuettel, Tomoaki Nishiyama, Sameer Kumar Prayaga, and Neil Tiffin. 2017. RPostgreSQL: R Interface to the ’Postgresql’ Database System. https://CRAN.R-project.org/package=RPostgreSQL.

Dowle, Matt, and Arun Srinivasan. 2019. Data.table: Extension of ‘Data.frame‘. https://CRAN.R-project.org/package=data.table.

Hijmans, Robert J. 2020. Raster: Geographic Data Analysis and Modeling. https://CRAN.R-project.org/package=raster.

Müller, Kirill, Hadley Wickham, David A. James, and Seth Falcon. 2020. RSQLite: ’SQLite’ Interface for R. https://CRAN.R-project.org/package=RSQLite.

Ooms, Jeroen. 2019. Writexl: Export Data Frames to Excel ’Xlsx’ Format. https://CRAN.R-project.org/package=writexl.

Ooms, Jeroen. 2020. Jsonlite: A Robust, High Performance Json Parser and Generator for R. https://CRAN.R-project.org/package=jsonlite.

Pebesma, Edzer. 2020. Sf: Simple Features for R. https://CRAN.R-project.org/package=sf.

R Special Interest Group on Databases (R-SIG-DB), Hadley Wickham, and Kirill Müller. 2019. DBI: R Database Interface. https://CRAN.R-project.org/package=DBI.

Wickham, Hadley, and Jennifer Bryan. 2019. Readxl: Read Excel Files. https://CRAN.R-project.org/package=readxl.

Wickham, Hadley, Jim Hester, and Romain Francois. 2018. Readr: Read Rectangular Text Data. https://CRAN.R-project.org/package=readr.

Wickham, Hadley, Jim Hester, and Jeroen Ooms. 2020. Xml2: Parse Xml. https://CRAN.R-project.org/package=xml2.


  1. Jeżeli poprawnie ustawiliśmy folder roboczy.↩︎

  2. Na komputerach z systemem Windows wymagane jest posiadanie zainstalowanego programu do rozpakowywania plików ZIP.↩︎

  3. Funkcje takie jak read.csv() pozwalają na otworzenie pliku tekstowego bezpośrednio z adresu internetowego.↩︎

  4. Istnieje również jej odpowiednik write.csv2().↩︎

  5. Po więcej informacji otwórz plik pomocy tej funkcji ?write_xlsx().↩︎

  6. Więcej informacji o łączeniu się z bazami danych można znaleść na stronie https://db.rstudio.com/getting-started/connect-to-database.↩︎